Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit dfcda2a

Browse filesBrowse files
[HttpClient] fix resetting DNS/etc when calling CurlHttpClient::reset()
1 parent 9f5238d commit dfcda2a
Copy full SHA for dfcda2a

File tree

4 files changed

+56
-70
lines changed
Filter options

4 files changed

+56
-70
lines changed

‎src/Symfony/Component/HttpClient/CurlHttpClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CurlHttpClient.php
+3-3Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Internal/CurlClientState.php
+23-27Lines changed: 23 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,26 @@ 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);
97+
if (\is_resource($this->handle) || $this->handle instanceof \CurlMultiHandle) {
98+
$this->__construct($this->maxHostConnections, $this->maxPendingPushes);
10199
}
102-
103-
$this->__construct($this->maxHostConnections, $this->maxPendingPushes);
104100
}
105101

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

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/CurlResponse.php
+28-38Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ public function testHandleIsReinitOnReset()
143143
$r = new \ReflectionProperty($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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.