Ich habe Jobs, die auf mehreren Warteschlangenarbeitern ausgeführt werden und einige HTTP-Anforderungen mit Guzzle enthalten. Der Try-Catch-Block in diesem Job scheint jedoch nicht zu funktionieren, GuzzleHttp\Exception\RequestException
wenn ich diesen Job im Hintergrund ausführe. Der laufende Prozess ist ein php artisan queue:work
Laravel-Warteschlangensystemarbeiter, der die Warteschlange überwacht und die Jobs aufnimmt.
Stattdessen wird eine Ausnahme GuzzleHttp\Promise\RejectionException
mit der folgenden Meldung ausgelöst :
Das Versprechen wurde mit folgendem Grund abgelehnt: cURL-Fehler 28: Zeitüberschreitung nach 30001 Millisekunden mit 0 empfangenen Bytes (siehe https://curl.haxx.se/libcurl/c/libcurl-errors.html )
Dies ist eigentlich eine Verkleidung GuzzleHttp\Exception\ConnectException
(siehe https://github.com/guzzle/promises/blob/master/src/RejectionException.php#L22 ), denn wenn ich einen ähnlichen Job in einem regulären PHP-Prozess ausführe, der durch den Besuch eines ausgelöst wird URL, ich bekomme die ConnectException
wie vorgesehen mit der Nachricht:
cURL-Fehler 28: Zeitüberschreitung des Vorgangs nach 100 Millisekunden mit 0 von 0 empfangenen Bytes (siehe https://curl.haxx.se/libcurl/c/libcurl-errors.html )
Beispielcode, der dieses Timeout auslösen würde:
try {
$c = new \GuzzleHttp\Client([
'timeout' => 0.1
]);
$response = (string) $c->get('https://example.com')->getBody();
} catch(GuzzleHttp\Exception\RequestException $e) {
// This occasionally gets catched when a ConnectException (child) is thrown,
// but it doesnt happen with RejectionException because it is not a child
// of RequestException.
}
Der obige Code löst entweder ein RejectionException
oder ein, ConnectException
wenn es im Worker-Prozess ausgeführt wird, aber immer ein, ConnectException
wenn es manuell über den Browser getestet wird (soweit ich das beurteilen kann).
Im Grunde genommen leite ich daraus ab, dass dies RejectionException
die Nachricht von der ConnectException
umschließt, ich jedoch nicht die asynchronen Funktionen von Guzzle verwende. Meine Anfragen werden einfach in Serie gemacht. Das einzige, was sich unterscheidet, ist, dass mehrere PHP-Prozesse möglicherweise Guzzle-HTTP-Aufrufe ausführen oder dass die Jobs selbst eine Zeitüberschreitung aufweisen (was zu einer anderen Ausnahme führen sollte als bei Laravel Illuminate\Queue\MaxAttemptsExceededException
), aber ich sehe nicht, wie sich der Code dadurch anders verhält.
Ich konnte keinen Code in den Guzzle-Paketen finden, der php_sapi_name()
/ PHP_SAPI
(der die verwendete Schnittstelle bestimmt) verwendet, um andere Dinge auszuführen, wenn er über die CLI ausgeführt wird, im Gegensatz zu einem Browser-Trigger.
tl; dr
Warum wirft Guzzle mich RejectionException
auf meine Arbeitsprozesse, aber ConnectException
auf normale PHP-Skripte, die über den Browser ausgelöst werden?
Bearbeiten 1
Leider kann ich kein minimal reproduzierbares Beispiel erstellen. Ich sehe viele Fehlermeldungen in meinem Sentry Issue Tracker, mit der genauen Ausnahme, die oben gezeigt wird. Die Quelle wird angegeben als Starting Artisan command: horizon:work
(Laravel Horizon überwacht die Laravel-Warteschlangen). Ich habe erneut überprüft, ob zwischen den PHP-Versionen eine Diskrepanz besteht, aber sowohl auf der Website als auch in den Arbeitsprozessen wird dasselbe PHP ausgeführt, 7.3.14
was korrekt ist:
PHP 7.3.14-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Jan 23 2020 13:59:16) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.14, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.14-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
- Die cURL-Version ist
cURL 7.58.0
. - Guzzle-Version ist
guzzlehttp/guzzle 6.5.2
- Laravel Version ist
laravel/framework 6.12.0
Bearbeiten 2 (Stack-Trace)
GuzzleHttp\Promise\RejectionException: The promise was rejected with reason: cURL error 28: Operation timed out after 30000 milliseconds with 0 bytes received (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)
#44 /vendor/guzzlehttp/promises/src/functions.php(112): GuzzleHttp\Promise\exception_for
#43 /vendor/guzzlehttp/promises/src/Promise.php(75): GuzzleHttp\Promise\Promise::wait
#42 /vendor/guzzlehttp/guzzle/src/Client.php(183): GuzzleHttp\Client::request
#41 /app/Bumpers/Client.php(333): App\Bumpers\Client::callRequest
#40 /app/Bumpers/Client.php(291): App\Bumpers\Client::callFunction
#39 /app/Bumpers/Client.php(232): App\Bumpers\Client::bumpThread
#38 /app/Models/Bumper.php(206): App\Models\Bumper::post
#37 /app/Jobs/PostBumper.php(59): App\Jobs\PostBumper::handle
#36 [internal](0): call_user_func_array
#35 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}
#34 /vendor/laravel/framework/src/Illuminate/Container/Util.php(36): Illuminate\Container\Util::unwrapIfClosure
#33 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): Illuminate\Container\BoundMethod::callBoundMethod
#32 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\Container\BoundMethod::call
#31 /vendor/laravel/framework/src/Illuminate/Container/Container.php(590): Illuminate\Container\Container::call
#30 /vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(94): Illuminate\Bus\Dispatcher::Illuminate\Bus\{closure}
#29 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
#28 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline::then
#27 /vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(98): Illuminate\Bus\Dispatcher::dispatchNow
#26 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(83): Illuminate\Queue\CallQueuedHandler::Illuminate\Queue\{closure}
#25 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
#24 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline::then
#23 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(85): Illuminate\Queue\CallQueuedHandler::dispatchThroughMiddleware
#22 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(59): Illuminate\Queue\CallQueuedHandler::call
#21 /vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(88): Illuminate\Queue\Jobs\Job::fire
#20 /vendor/laravel/framework/src/Illuminate/Queue/Worker.php(354): Illuminate\Queue\Worker::process
#19 /vendor/laravel/framework/src/Illuminate/Queue/Worker.php(300): Illuminate\Queue\Worker::runJob
#18 /vendor/laravel/framework/src/Illuminate/Queue/Worker.php(134): Illuminate\Queue\Worker::daemon
#17 /vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(112): Illuminate\Queue\Console\WorkCommand::runWorker
#16 /vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(96): Illuminate\Queue\Console\WorkCommand::handle
#15 /vendor/laravel/horizon/src/Console/WorkCommand.php(46): Laravel\Horizon\Console\WorkCommand::handle
#14 [internal](0): call_user_func_array
#13 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}
#12 /vendor/laravel/framework/src/Illuminate/Container/Util.php(36): Illuminate\Container\Util::unwrapIfClosure
#11 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): Illuminate\Container\BoundMethod::callBoundMethod
#10 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\Container\BoundMethod::call
#9 /vendor/laravel/framework/src/Illuminate/Container/Container.php(590): Illuminate\Container\Container::call
#8 /vendor/laravel/framework/src/Illuminate/Console/Command.php(201): Illuminate\Console\Command::execute
#7 /vendor/symfony/console/Command/Command.php(255): Symfony\Component\Console\Command\Command::run
#6 /vendor/laravel/framework/src/Illuminate/Console/Command.php(188): Illuminate\Console\Command::run
#5 /vendor/symfony/console/Application.php(1012): Symfony\Component\Console\Application::doRunCommand
#4 /vendor/symfony/console/Application.php(272): Symfony\Component\Console\Application::doRun
#3 /vendor/symfony/console/Application.php(148): Symfony\Component\Console\Application::run
#2 /vendor/laravel/framework/src/Illuminate/Console/Application.php(93): Illuminate\Console\Application::run
#1 /vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(131): Illuminate\Foundation\Console\Kernel::handle
#0 /artisan(37): null
Die Client::callRequest()
Funktion enthält einfach einen Guzzle Client, auf dem ich anrufe $client->request($request['method'], $request['url'], $request['options']);
(also benutze ich nicht requestAsync()
). Ich denke, es hat etwas mit dem parallelen Ausführen von Jobs zu tun, das dieses Problem verursacht.
Edit 3 (Lösung gefunden)
Betrachten Sie den folgenden Testfall, der eine HTTP-Anfrage stellt (die eine reguläre 200-Antwort zurückgeben sollte):
try {
$c = new \GuzzleHttp\Client([
'base_uri' => 'https://example.com'
]);
$handler = $c->getConfig('handler');
$handler->push(\GuzzleHttp\Middleware::mapResponse(function(ResponseInterface $response) {
// Create a fake connection exception:
$e = new \GuzzleHttp\Exception\ConnectException('abc', new \GuzzleHttp\Psr7\Request('GET', 'https://example.com/2'));
// These 2 lines both cascade as `ConnectException`:
throw $e;
return \GuzzleHttp\Promise\rejection_for($e);
// This line cascades as a `RejectionException`:
return \GuzzleHttp\Promise\rejection_for($e->getMessage());
}));
$c->get('');
} catch(\Exception $e) {
var_dump($e);
}
Was ich ursprünglich getan habe, war ein Aufruf, rejection_for($e->getMessage())
der RejectionException
basierend auf der Nachrichtenzeichenfolge eine eigene erstellt . Anrufen rejection_for($e)
war hier die richtige Lösung. Es bleibt nur zu antworten, ob diese rejection_for
Funktion mit einer einfachen identisch ist throw $e
.
HandlerStack
?