[11.x] Support retry and throw on async http client request (in a http client request pool) #48906
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Issue
The Http Client allows you to add "retry" functionality and "throw" functionality to a request.
These are synchronous requests, but the Http Client also gives you the option to create asynchronous requests by creating a pool. The problem is asynchronous requests in a pool do not support the retry and throw functionality. If you want that functionality, then you would have to write that yourself which is not easy to do (especially if you want the retries to also happen asynchronously).
This issue has also been reported by others: #44955 and #41790 (comment)
Solution
This PR fixes that by adding all that behavior to an asynchronous request, so that it behaves exactly the same as a synchronous request.
We first add an extra
then
to the request promise. This allows us to add the same retry and throw functionality as a synchronous request.First, we check if the response has a successful status code. In that case, we can return that response.
If the response is not successful, we need to check if the request needs to be retried. The execution of the "retryWhenCallback" is wrapped in a try-catch because an exception that occurs in that closure should just cause the return of that exception (exactly the same as a synchronous request, except the exception is returned instead of throw)
Next, we need to check if our current attempt does not exceed our maximum tries. If that is the case, we can start a new attempt by creating and returning a new promise by calling the
makePromise
method again. Guzzle also provides the option to provide a delay so the new attempt is not immediately executed if that is what is defined on the pending request.Next, we need to check if we need to return the response or the exception. If the exception needs to be returned, we wrap the
$response->throw($this->throwCallback)
code in a try-catch. The$response->throw()
method immediately throws the generated exception but in the case of async requests we need to return the exception instead. The try-catch enables us to do that.Finally, if we exceed our maximum tries, we have to return the final exception (if the $response is not a response object, then it's a connection exception so we can just return that exception). Otherwise, the response should just be returned.
Tests
All the tests in this PR are copies of existing tests but the synchronous requests are converted to asynchronous requests so we test the exact same cases.
Breaking Changes
\Illuminate\Http\Client\ConnectionException
is returned instead of theGuzzleHttp\Exception\ConnectException
. This makes the behavior the same as the synchronous requests.throw
method is used on an async request in a pool, the response exception will be returned instead of always returning the response if a response is available. This makes the behavior the same as the synchronous requests.retry
method is used on an async request in a pool, the method will now work.makePromise
method on thePendingRequest
class has an extra parameter calledattempt