Skip to content

Commit f4381dc

Browse files
Merge branch '4.4' into 5.3
* 4.4: [Cache] Make sure PdoAdapter::prune() always returns a bool [HttpKernel] Fix broken mock [HttpClient] Fix handling timeouts when responses are destructed [PropertyInfo] Support for intersection types
2 parents da8638f + 6b900ff commit f4381dc

8 files changed

+54
-7
lines changed

Internal/ClientState.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ class ClientState
2222
{
2323
public $handlesActivity = [];
2424
public $openHandles = [];
25+
public $lastTimeout;
2526
}

Response/AsyncResponse.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ public function __construct(HttpClientInterface $client, string $method, string
5757
}
5858
$this->response = $client->request($method, $url, ['buffer' => false] + $options);
5959
$this->passthru = $passthru;
60-
$this->initializer = static function (self $response) {
60+
$this->initializer = static function (self $response, float $timeout = null) {
6161
if (null === $response->shouldBuffer) {
6262
return false;
6363
}
6464

6565
while (true) {
66-
foreach (self::stream([$response]) as $chunk) {
66+
foreach (self::stream([$response], $timeout) as $chunk) {
6767
if ($chunk->isTimeout() && $response->passthru) {
6868
foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) {
6969
if ($chunk->isFirst()) {
@@ -179,6 +179,7 @@ public function __destruct()
179179

180180
if ($this->initializer && null === $this->getInfo('error')) {
181181
try {
182+
self::initialize($this, -0.0);
182183
$this->getHeaders(true);
183184
} catch (HttpExceptionInterface $httpException) {
184185
// no-op

Response/CommonResponseTrait.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ public function __wakeup()
145145
*/
146146
abstract protected function close(): void;
147147

148-
private static function initialize(self $response): void
148+
private static function initialize(self $response, float $timeout = null): void
149149
{
150150
if (null !== $response->getInfo('error')) {
151151
throw new TransportException($response->getInfo('error'));
152152
}
153153

154154
try {
155-
if (($response->initializer)($response)) {
156-
foreach (self::stream([$response]) as $chunk) {
155+
if (($response->initializer)($response, $timeout)) {
156+
foreach (self::stream([$response], $timeout) as $chunk) {
157157
if ($chunk->isFirst()) {
158158
break;
159159
}

Response/TransportResponseTrait.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ private function doDestruct()
138138
$this->shouldBuffer = true;
139139

140140
if ($this->initializer && null === $this->info['error']) {
141-
self::initialize($this);
141+
self::initialize($this, -0.0);
142142
$this->checkStatusCode();
143143
}
144144
}
@@ -159,6 +159,12 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
159159
$lastActivity = microtime(true);
160160
$elapsedTimeout = 0;
161161

162+
if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) {
163+
$timeout = null;
164+
} elseif ($fromLastTimeout = 0 > $timeout) {
165+
$timeout = -$timeout;
166+
}
167+
162168
while (true) {
163169
$hasActivity = false;
164170
$timeoutMax = 0;
@@ -172,15 +178,21 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
172178
foreach ($responses as $j => $response) {
173179
$timeoutMax = $timeout ?? max($timeoutMax, $response->timeout);
174180
$timeoutMin = min($timeoutMin, $response->timeout, 1);
181+
182+
if ($fromLastTimeout && null !== $multi->lastTimeout) {
183+
$elapsedTimeout = microtime(true) - $multi->lastTimeout;
184+
}
185+
175186
$chunk = false;
176187

177188
if (isset($multi->handlesActivity[$j])) {
178-
// no-op
189+
$multi->lastTimeout = null;
179190
} elseif (!isset($multi->openHandles[$j])) {
180191
unset($responses[$j]);
181192
continue;
182193
} elseif ($elapsedTimeout >= $timeoutMax) {
183194
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))];
195+
$multi->lastTimeout ?? $multi->lastTimeout = $lastActivity;
184196
} else {
185197
continue;
186198
}

Tests/AsyncDecoratorTraitTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
namespace Symfony\Component\HttpClient\Tests;
1313

1414
use Symfony\Component\HttpClient\AsyncDecoratorTrait;
15+
use Symfony\Component\HttpClient\CurlHttpClient;
1516
use Symfony\Component\HttpClient\DecoratorTrait;
17+
use Symfony\Component\HttpClient\HttpClient;
1618
use Symfony\Component\HttpClient\Response\AsyncContext;
1719
use Symfony\Component\HttpClient\Response\AsyncResponse;
1820
use Symfony\Contracts\HttpClient\ChunkInterface;
@@ -29,6 +31,10 @@ protected function getHttpClient(string $testCase, \Closure $chunkFilter = null,
2931
$this->markTestSkipped("AsyncDecoratorTrait doesn't cache handles");
3032
}
3133

34+
if ('testTimeoutOnDestruct' === $testCase) {
35+
return new CurlHttpClient();
36+
}
37+
3238
$chunkFilter = $chunkFilter ?? static function (ChunkInterface $chunk, AsyncContext $context) { yield $chunk; };
3339

3440
return new class($decoratedClient ?? parent::getHttpClient($testCase), $chunkFilter) implements HttpClientInterface {
@@ -49,6 +55,15 @@ public function request(string $method, string $url, array $options = []): Respo
4955
};
5056
}
5157

58+
public function testTimeoutOnDestruct()
59+
{
60+
if (HttpClient::create() instanceof NativeHttpClient) {
61+
parent::testTimeoutOnDestruct();
62+
} else {
63+
HttpClientTestCase::testTimeoutOnDestruct();
64+
}
65+
}
66+
5267
public function testRetry404()
5368
{
5469
$client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) {

Tests/HttpClientTestCase.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ abstract class HttpClientTestCase extends BaseHttpClientTestCase
3232
{
3333
private static $vulcainStarted = false;
3434

35+
public function testTimeoutOnDestruct()
36+
{
37+
if (!method_exists(parent::class, 'testTimeoutOnDestruct')) {
38+
$this->markTestSkipped('BaseHttpClientTestCase doesn\'t have testTimeoutOnDestruct().');
39+
}
40+
41+
parent::testTimeoutOnDestruct();
42+
}
43+
3544
public function testAcceptHeader()
3645
{
3746
$client = $this->getHttpClient(__FUNCTION__);

Tests/MockHttpClientTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface
236236
$this->markTestSkipped('Real transport required');
237237
break;
238238

239+
case 'testTimeoutOnDestruct':
240+
$this->markTestSkipped('Real transport required');
241+
break;
242+
239243
case 'testDestruct':
240244
$this->markTestSkipped("MockHttpClient doesn't timeout on destruct");
241245
break;

Tests/NativeHttpClientTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ public function testInformationalResponseStream()
2626
$this->markTestSkipped('NativeHttpClient doesn\'t support informational status codes.');
2727
}
2828

29+
public function testTimeoutOnDestruct()
30+
{
31+
$this->markTestSkipped('NativeHttpClient doesn\'t support opening concurrent requests.');
32+
}
33+
2934
public function testHttp2PushVulcain()
3035
{
3136
$this->markTestSkipped('NativeHttpClient doesn\'t support HTTP/2.');

0 commit comments

Comments
 (0)