8000 [HttpClient] fix resetting DNS/etc when calling CurlHttpClient::reset() · symfony/symfony@d956af9 · GitHub
[go: up one dir, main page]

Skip to content

Commit d956af9

Browse files
[HttpClient] fix resetting DNS/etc when calling CurlHttpClient::reset()
1 parent 9f5238d commit d956af9

File tree

4 files changed

+60
-70
lines changed

4 files changed

+60
-70
lines changed

src/Symfony/Component/HttpClient/CurlHttpClient.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ public function request(string $method, string $url, array $options = []): Respo
168168

169169
if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) {
170170
// DNS cache removals require curl 7.42 or higher
171-
// On lower versions, we have to create a new multi handle
172171
$this->multi->reset();
173172
}
174173

@@ -280,6 +279,7 @@ public function request(string $method, string $url, array $options = []): Respo
280279
if (!$pushedResponse) {
281280
$ch = curl_init();
282281
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url));
282+
$curlopts += [\CURLOPT_SHARE => $this->multi->share];
283283
}
284284

285285
foreach ($curlopts as $opt => $value) {
@@ -306,9 +306,9 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa
306306
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of CurlResponse objects, "%s" given.', __METHOD__, \is_object($responses) ? \get_class($responses) : \gettype($responses)));
307307
}
308308

309-
if (\is_resource($mh = $this->multi->handles[0] ?? null) || $mh instanceof \CurlMultiHandle) {
309+
if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) {
310310
$active = 0;
311-
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $active)) {
311+
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) {
312312
}
313313
}
314314

src/Symfony/Component/HttpClient/Internal/CurlClientState.php

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
*/
2424
final class CurlClientState extends ClientState
2525
{
26-
/** @var array<\CurlMultiHandle|resource> */
27-
public $handles = [];
26+
/** @var \CurlMultiHandle|resource */
27+
public $handle;
28+
/** @var \CurlShareHandle|resource */
29+
public $share;
2830
/** @var PushedResponse[] */
2931
public $pushedResponses = [];
3032
/** @var DnsCache */
@@ -41,20 +43,28 @@ public function __construct(int $maxHostConnections, int $maxPendingPushes)
4143
{
4244
self::$curlVersion = self::$curlVersion ?? curl_version();
4345

44-
array_unshift($this->handles, $mh = curl_multi_init());
46+
$this->handle = $this->handle ?? curl_multi_init();
47+
$this->share = curl_share_init();
4548
$this->dnsCache = new DnsCache();
4649
$this->maxHostConnections = $maxHostConnections;
4750
$this->maxPendingPushes = $maxPendingPushes;
4851

52+
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS);
53+
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION);
54+
55+
if (\defined('CURL_LOCK_DATA_CONNECT')) {
56+
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT);
57+
}
58+
4959
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
5060
if (\defined('CURLPIPE_MULTIPLEX')) {
51-
curl_multi_setopt($mh, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
61+
curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
5262
}
5363
if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
54-
$maxHostConnections = curl_multi_setopt($mh, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections;
64+
$maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections;
5565
}
5666
if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) {
57-
curl_multi_setopt($mh, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
67+
curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
5868
}
5969

6070
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
@@ -67,40 +77,30 @@ public function __construct(int $maxHostConnections, int $maxPendingPushes)
6777
return;
6878
}
6979

70-
// Clone to prevent a circular reference
71-
$multi = clone $this;
72-
$multi->handles = [$mh];
73-
$multi->pushedResponses = &$this->pushedResponses;
74-
$multi->logger = &$this->logger;
75-
$multi->handlesActivity = &$this->handlesActivity;
76-
$multi->openHandles = &$this->openHandles;
77-
$multi->lastTimeout = &$this->lastTimeout;
78-
79-
curl_multi_setopt($mh, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) {
80-
return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
80+
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, function ($parent, $pushed, array $requestHeaders) use ($maxPendingPushes) {
81+
return $this->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
8182
});
8283
}
8384

8485
public function reset()
8586
{
86-
foreach ($this->pushedResponses as $url => $response) {
87-
$this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
88-
89-
foreach ($this->handles as $mh) {
90-
curl_multi_remove_handle($mh, $response->handle);
87+
if ($this->logger) {
88+
foreach ($this->pushedResponses as $url => $response) {
89+
$this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
9190
}
92-
curl_close($response->handle);
9391
}
9492

9593
$this->pushedResponses = [];
9694
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
9795
$this->dnsCache->removals = $this->dnsCache->hostnames = [];
9896

99-
if (\defined('CURLMOPT_PUSHFUNCTION')) {
100-
curl_multi_setopt($this->handles[0], \CURLMOPT_PUSHFUNCTION, null);
101-
}
97+
if (\is_resource($this->handle) || $this->handle instanceof \CurlMultiHandle) {
98+
if (\defined('CURLMOPT_PUSHFUNCTION')) {
99+
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, null);
100+
}
102101

103-
$this->__construct($this->maxHostConnections, $this->maxPendingPushes);
102+
$this->__construct($this->maxHostConnections, $this->maxPendingPushes);
103+
}
104104
}
105105

106106
private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int

src/Symfony/Component/HttpClient/Response/CurlResponse.php

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
150150
// Schedule the request in a non-blocking way
151151
$multi->lastTimeout = null;
152152
$multi->openHandles[$id] = [$ch, $options];
153-
curl_multi_add_handle($multi->handles[0], $ch);
153+
curl_multi_add_handle($multi->handle, $ch);
154154

155155
$this->canary = new Canary(static function () use ($ch, $multi, $id) {
156156
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
@@ -160,9 +160,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
160160
return;
161161
}
162162

163-
foreach ($multi->handles as $mh) {
164-
curl_multi_remove_handle($mh, $ch);
165-
}
163+
curl_multi_remove_handle($multi->handle, $ch);
166164
curl_setopt_array($ch, [
167165
\CURLOPT_NOPROGRESS => true,
168166
\CURLOPT_PROGRESSFUNCTION => null,
@@ -244,7 +242,7 @@ public function __destruct()
244242
*/
245243
private static function schedule(self $response, array &$runningResponses): void
246244
{
247-
if (isset($runningResponses[$i = (int) $response->multi->handles[0]])) {
245+
if (isset($runningResponses[$i = (int) $response->multi->handle])) {
248246
$runningResponses[$i][1][$response->id] = $response;
249247
} else {
250248
$runningResponses[$i] = [$response->multi, [$response->id => $response]];
@@ -276,47 +274,39 @@ private static function perform(ClientState $multi, array &$responses = null): v
276274

277275
try {
278276
self::$performing = true;
277+
$active = 0;
278+
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) {
279+
}
279280

280-
foreach ($multi->handles as $i => $mh) {
281-
$active = 0;
282-
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($mh, $active))) {
283-
}
281+
if (\CURLM_OK !== $err) {
282+
throw new TransportException(curl_multi_strerror($err));
283+
}
284284

285-
if (\CURLM_OK !== $err) {
286-
throw new TransportException(curl_multi_strerror($err));
285+
while ($info = curl_multi_info_read($multi->handle)) {
286+
if (\CURLMSG_DONE !== $info['msg']) {
287+
continue;
287288
}
289+
$result = $info['result'];
290+
$id = (int) $ch = $info['handle'];
291+
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
288292

289-
while ($info = curl_multi_info_read($mh)) {
290-
if (\CURLMSG_DONE !== $info['msg']) {
291-
continue;
292-
}
293-
$result = $info['result'];
294-
$id = (int) $ch = $info['handle'];
295-
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
296-
297-
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
298-
curl_multi_remove_handle($mh, $ch);
299-
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
300-
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
301-
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
302-
303-
if (0 === curl_multi_add_handle($mh, $ch)) {
304-
continue;
305-
}
306-
}
293+
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
294+
curl_multi_remove_handle($multi->handle, $ch);
295+
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
296+
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
297+
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
307298

308-
if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
309-
$multi->handlesActivity[$id][] = new FirstChunk();
299+
if (0 === curl_multi_add_handle($multi->handle, $ch)) {
300+
continue;
310301
}
311-
312-
$multi->handlesActivity[$id][] = null;
313-
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(sprintf('%s for "%s".', curl_strerror($result), curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
314302
}
315303

316-
if (!$active && 0 < $i) {
317-
curl_multi_close($mh);
318-
unset($multi->handles[$i]);
304+
if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
305+
$multi->handlesActivity[$id][] = new FirstChunk();
319306
}
307+
308+
$multi->handlesActivity[$id][] = null;
309+
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(sprintf('%s for "%s".', curl_strerror($result), curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
320310
}
321311
} finally {
322312
self::$performing = false;
@@ -335,7 +325,7 @@ private static function select(ClientState $multi, float $timeout): int
335325
$timeout = min($timeout, 0.01);
336326
}
337327

338-
return curl_multi_select($multi->handles[array_key_last($multi->handles)], $timeout);
328+
return curl_multi_select($multi->handle, $timeout);
339329
}
340330

341331
/**

src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ public function testHandleIsReinitOnReset()
143143
$r = new \ReflectionPrope 8180 rty($httpClient, 'multi');
144144
$r->setAccessible(true);
145145
$clientState = $r->getValue($httpClient);
146-
$initialHandleId = (int) $clientState->handles[0];
146+
$initialShareId = $clientState->share;
147147
$httpClient->reset();
148-
self::assertNotSame($initialHandleId, (int) $clientState->handles[0]);
148+
self::assertNotSame($initialShareId, $clientState->share);
149149
}
150150

151151
public function testProcessAfterReset()

0 commit comments

Comments
 (0)
0