Skip to content

Commit 670aed4

Browse files
[HttpClient] resolve promise chains on HttplugClient::wait()
1 parent 6e7f325 commit 670aed4

File tree

3 files changed

+45
-28
lines changed

3 files changed

+45
-28
lines changed

src/Symfony/Component/HttpClient/HttplugClient.php

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function __construct(HttpClientInterface $client = null, ResponseFactoryI
6868
$this->client = $client ?? HttpClient::create();
6969
$this->responseFactory = $responseFactory;
7070
$this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
71-
$this->promisePool = new \SplObjectStorage();
71+
$this->promisePool = \function_exists('GuzzleHttp\Promise\queue') ? new \SplObjectStorage() : null;
7272

7373
if (null !== $this->responseFactory && null !== $this->streamFactory) {
7474
return;
@@ -102,7 +102,7 @@ public function sendRequest(RequestInterface $request): Psr7ResponseInterface
102102
*/
103103
public function sendAsyncRequest(RequestInterface $request): Promise
104104
{
105-
if (!class_exists(GuzzlePromise::class)) {
105+
if (!$this->promisePool) {
106106
throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__));
107107
}
108108

@@ -112,19 +112,17 @@ public function sendAsyncRequest(RequestInterface $request): Promise
112112
return new RejectedPromise($e);
113113
}
114114

115-
$cancel = function () use ($response) {
116-
$response->cancel();
117-
unset($this->promisePool[$response]);
118-
};
119-
120115
$promise = new GuzzlePromise(function () use ($response) {
121116
$this->pendingResponse = $response;
122117
$this->wait();
123-
}, $cancel);
118+
}, function () use ($response) {
119+
$response->cancel();
120+
unset($this->promisePool[$response]);
121+
});
124122

125123
$this->promisePool[$response] = [$request, $promise];
126124

127-
return new HttplugPromise($promise, $cancel);
125+
return new HttplugPromise($promise);
128126
}
129127

130128
/**
@@ -136,13 +134,19 @@ public function sendAsyncRequest(RequestInterface $request): Promise
136134
*/
137135
public function wait(float $maxDuration = null, float $idleTimeout = null): int
138136
{
137+
if (!$this->promisePool) {
138+
return 0;
139+
}
140+
139141
$pendingResponse = $this->pendingResponse;
140142
$this->pendingResponse = null;
143+
$guzzleQueue = \GuzzleHttp\Promise\queue();
141144

142-
if (null !== $maxDuration) {
145+
if (0.0 === $remainingDuration = $maxDuration) {
146+
$idleTimeout = 0.0;
147+
} elseif (null !== $maxDuration) {
143148
$startTime = microtime(true);
144149
$idleTimeout = max(0.0, min($maxDuration / 5, $idleTimeout ?? $maxDuration));
145-
$remainingDuration = $maxDuration;
146150
}
147151

148152
do {
@@ -177,8 +181,10 @@ public function wait(float $maxDuration = null, float $idleTimeout = null): int
177181
}
178182
}
179183

184+
$guzzleQueue->run();
185+
180186
if ($pendingResponse === $response) {
181-
return \count($this->promisePool);
187+
return $this->promisePool->count();
182188
}
183189

184190
check_duration:
@@ -188,7 +194,7 @@ public function wait(float $maxDuration = null, float $idleTimeout = null): int
188194
}
189195
}
190196

191-
if (!$count = \count($this->promisePool)) {
197+
if (!$count = $this->promisePool->count()) {
192198
return 0;
193199
}
194200
} while (null !== $maxDuration && 0 < $remainingDuration);

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

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@
2323
final class HttplugPromise implements HttplugPromiseInterface
2424
{
2525
private $promise;
26-
private $cancel;
2726

28-
public function __construct(GuzzlePromiseInterface $promise, callable $cancel = null)
27+
public function __construct(GuzzlePromiseInterface $promise)
2928
{
3029
$this->promise = $promise;
31-
$this->cancel = $cancel;
3230
}
3331

3432
public function then(callable $onFulfilled = null, callable $onRejected = null): self
@@ -58,16 +56,4 @@ public function wait($unwrap = true)
5856
{
5957
return $this->promise->wait($unwrap);
6058
}
61-
62-
public function __destruct()
63-
{
64-
if ($this->cancel) {
65-
($this->cancel)();
66-
}
67-
}
68-
69-
public function __wakeup()
70-
{
71-
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
72-
}
7359
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,31 @@ public function testSendAsyncRequest()
7575
$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
7676
}
7777

78+
public function testWait()
79+
{
80+
$client = new HttplugClient(new NativeHttpClient());
81+
82+
$successCallableCalled = false;
83+
$failureCallableCalled = false;
84+
$client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/timeout-body'))
85+
->then(function (ResponseInterface $response) use (&$successCallableCalled) {
86+
$successCallableCalled = true;
87+
88+
return $response;
89+
}, function (\Exception $exception) use (&$failureCallableCalled) {
90+
$failureCallableCalled = true;
91+
92+
throw $exception;
93+
});
94+
95+
$client->wait(0);
96+
$this->assertFalse($successCallableCalled, '$promise->then() should not be called yet.');
97+
98+
$client->wait();
99+
$this->assertTrue($successCallableCalled, '$promise->then() should have been called.');
100+
$this->assertFalse($failureCallableCalled, 'Failure callable should not be called when request is successful.');
101+
}
102+
78103
public function testPostRequest()
79104
{
80105
$client = new HttplugClient(new NativeHttpClient());

0 commit comments

Comments
 (0)