Tôi có các công việc chạy trên nhiều nhân viên xếp hàng, có chứa một số yêu cầu HTTP bằng cách sử dụng Guheads. Tuy nhiên, khối thử bắt trong công việc này dường như không nhận được GuzzleHttp\Exception\RequestException
khi tôi đang chạy các công việc này trong quá trình nền. Quá trình đang chạy là một php artisan queue:work
nhân viên hệ thống hàng đợi của Laravel theo dõi hàng đợi và chọn công việc.
Thay vào đó, ngoại lệ được ném là một trong GuzzleHttp\Promise\RejectionException
những thông báo:
Lời hứa đã bị từ chối với lý do: lỗi cURL 28: Đã hết thời gian hoạt động sau 30001 mili giây với 0 byte nhận được (xem https://curl.haxx.se/libcurl/c/libcurl-errors.html )
Đây thực sự là một sự ngụy trang GuzzleHttp\Exception\ConnectException
(xem https://github.com/guzz/promises/blob/master/src/RejectionException.php#L22 ), bởi vì nếu tôi chạy một công việc tương tự trong một quy trình PHP thông thường được kích hoạt bằng cách truy cập một URL, tôi nhận được ConnectException
như dự định với thông báo:
Lỗi cURL 28: Đã hết thời gian hoạt động sau 100 mili giây với 0 trong số 0 byte nhận được (xem https://curl.haxx.se/libcurl/c/libcurl-errors.html )
Mã mẫu sẽ kích hoạt thời gian chờ này:
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.
}
Đoạn mã trên ném một RejectionException
hoặc ConnectException
khi được chạy trong quy trình worker, nhưng luôn luôn là ConnectException
khi được kiểm tra thủ công thông qua trình duyệt (từ những gì tôi có thể nói).
Vì vậy, về cơ bản những gì tôi rút ra, là điều này RejectionException
bao bọc thông điệp từ ConnectException
, tuy nhiên tôi không sử dụng các tính năng không đồng bộ của Guheads. Yêu cầu của tôi chỉ đơn giản là được thực hiện trong loạt. Điều duy nhất khác biệt là nhiều quy trình PHP có thể thực hiện các cuộc gọi HTTP Guheads hoặc bản thân các công việc đã hết thời gian (điều này sẽ dẫn đến một ngoại lệ khác là của LaravelIlluminate\Queue\MaxAttemptsExceededException
), nhưng tôi không thấy cách này khiến mã hoạt động khác đi.
Tôi không thể tìm thấy bất kỳ mã nào bên trong các gói Guheads đang sử dụng php_sapi_name()
/PHP_SAPI
(xác định giao diện đã sử dụng) để thực thi các nội dung khác nhau khi chạy từ CLI thay vì kích hoạt trình duyệt.
tl; dr
Tại sao Guheads ném tôi RejectionException
vào quy trình công nhân của tôi, nhưngConnectException
trên các tập lệnh PHP thông thường được kích hoạt thông qua trình duyệt?
Chỉnh sửa 1
Đáng buồn là tôi không thể tạo ra một ví dụ tái sản xuất tối thiểu. Tôi thấy nhiều thông báo lỗi trong trình theo dõi vấn đề Sentry của tôi, với ngoại lệ chính xác được hiển thị ở trên. Nguồn được nêu là Starting Artisan command: horizon:work
(đó là Laravel Horizon, nó giám sát các hàng đợi của Laravel). Tôi đã kiểm tra lại để xem liệu có sự khác biệt giữa các phiên bản PHP hay không, nhưng cả quy trình trang web và công nhân đều chạy cùng một PHP 7.3.14
đúng:
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
- Phiên bản cURL là
cURL 7.58.0
. - Phiên bản hướng dẫn là
guzzlehttp/guzzle 6.5.2
- Phiên bản của Laravel là
laravel/framework 6.12.0
Chỉnh sửa 2 (theo dõi ngăn xếp)
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
Các Client::callRequest()
chức năng đơn giản chứa một Client tật ham ăn mà tôi gọi $client->request($request['method'], $request['url'], $request['options']);
(để im không sử dụngrequestAsync()
). Tôi nghĩ rằng nó có liên quan đến việc chạy các công việc song song gây ra vấn đề này.
Chỉnh sửa 3 (tìm thấy giải pháp)
Hãy xem xét các testcase sau đây thực hiện một yêu cầu HTTP (sẽ trả về 200 phản hồi thông thường):
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);
}
Bây giờ, những gì tôi đã làm ban đầu là gọi rejection_for($e->getMessage())
nó tạo ra RejectionException
dựa trên chuỗi tin nhắn. Gọi rejection_for($e)
là giải pháp chính xác ở đây. Điều duy nhất còn lại để trả lời là nếu rejection_for
chức năng này giống như một đơn giản throw $e
.
HandlerStack
:)?