Skip to content

Commit 936d845

Browse files
Alexey PortnovAlexey Portnov
authored andcommitted
fix(beanstalk): upgrade Pheanstalk 4.x → 8.x, kill socket-read fatals (#1010)
Sentry cluster #27085 (35 events, 21 users, since 2026-03-24) kept reporting `FatalErrorException: Maximum execution time of 30 seconds exceeded` in `vendor/pda/pheanstalk/src/Socket/SocketSocket.php` — Beanstalk workers hung indefinitely on `socket_read()` whenever beanstalkd slowed down or restarted, then got killed by the PHP execution time limit. Three more clusters (#27116 / #27284 / #27283) reported `Pheanstalk\Exception\JobNotFoundException` from `BeanstalkClient::cleanTubes`, `::wait` and `::buryJob` — ~86 events of pure noise from expected TTR races between peek/delete. Root cause of the fatal: Pheanstalk 4.x's `SocketSocket::__construct()` sets `SO_SNDTIMEO`/`SO_RCVTIMEO` for the `connect()` phase and then *restores the default (unbounded)* send/receive timeouts right after connecting, so every subsequent `socket_read()` blocks forever. The php-fpm / worker `max_execution_time` is the only thing that eventually breaks that wait, and it breaks it with an uncatchable fatal. Pheanstalk 8.x fixes this: `SocketSocket::__construct()` takes separate `connectTimeout`/`sendTimeout`/`receiveTimeout` `Timeout` value objects and keeps the receive timeout on the socket for its entire lifetime, so `socket_read()` aborts cleanly with `EAGAIN` → `ConnectionException` well before any fatal fires. `Pheanstalk::create()` exposes those through the public API, and `disconnect()` (added in 7.0) lets workers release the socket explicitly on shutdown. Changes: - composer.json: `pda/pheanstalk ^4.0` → `^8.0` (PHP 8.3+ compatible, matches MikoPBX's `php: ^8.4` constraint). - src/Core/System/BeanstalkClient.php: full rewrite against the 8.x API. * `reconnect()` now calls `Pheanstalk::create($host, $port, new Timeout(2), new Timeout(10))` — 2 s connect timeout, 10 s receive timeout, the latter being the real issue #1010 fix. * All string-typed `useTube/watch/ignore` arguments wrapped in `Pheanstalk\Values\TubeName` value objects (8.x API requirement). * `cleanTubes()` uses the per-tube `statsTube(): TubeStats` object with typed properties (`->currentJobsBuried`, `->currentJobsReady`) instead of 4.x's `stats()->getArrayCopy()['current-jobs-buried']`. Also fixes a 4.x latent bug where global `stats()` was accessed inside a per-tube `useTube()` loop. * `statsJob(): JobStats` replaces `statsJob()->getArrayCopy()` — property access (`->age`, `->timeToRelease`, `->tube->value`, `->reserves`) instead of string indices. * `publish()` returns `JobIdInterface` instead of `Job`: Pheanstalk 8.x `put()` returns a lightweight `JobId` rather than the full `Job` value object (breaking change in the library). * Narrow `JobNotFoundException` catches in `wait()`, `buryJob()`, `cleanTubes()`, `getMessagesFromTube()`, `request()`, `sendRequest()`. Peek-then-delete races are benign and no longer spam Sentry through `CriticalErrorsHandler` — they go to `LOG_DEBUG` only. This closes Sentry clusters #27116 / #27284 / #27283 (~86 events of noise). * New `close()` wrapper around Pheanstalk 8.x `disconnect()` so workers can release the beanstalkd socket explicitly on shutdown, mirroring the WorkerBase closeRedis() pattern from #1022. - src/Core/Workers/WorkerModelsEvents.php and src/PBXCoreREST/Controllers/Modules/ModulesControllerBase.php: replace removed `Pheanstalk\Contract\PheanstalkInterface` with `Pheanstalk\Contract\PheanstalkPublisherInterface` (the 8.x interface that actually holds `DEFAULT_PRIORITY` / `DEFAULT_DELAY`). Verified on prod stand serber@boffart.miko.ru (mikopbx 2026.1.233-dev): 1. 10/10 standalone smoke test (publish, getMessagesFromTube, close, reconnect, round-trip) passed against the 8.x vendor on-disk. 2. All four Beanstalk workers (Cdr, CallEvents, ModelsEvents, NotifyByEmail) respawned on the new code with no fatal. 3. SIGSTOP on beanstalkd for 25 s — workers logged `Pheanstalk\Exception\ConnectionException: Socket error 11: Resource temporarily unavailable` (EAGAIN from SO_RCVTIMEO=10 s) and kept running. No `Maximum execution time` in syslog. Before this patch the same scenario produced Sentry cluster #27085. 4. Sentry `lastSeen` for `server_name=mikopbx` on #27085 / #27116 / #27284 / #27283 is frozen at timestamps pre-deploy — host stopped generating new events across all four clusters. Release coordination note: Pheanstalk 8.x drops `Pheanstalk\Contract\PheanstalkInterface`, moves `DEFAULT_PRIORITY` from the main `Pheanstalk` class into `PheanstalkPublisherInterface`, and relocates `Pheanstalk\Job` to `Pheanstalk\Values\Job`. Extension modules that import these 4.x-only symbols must ship a matching patch before the next ISO release: - ModulePT1CCore: `Lib/RestAPI/Controllers/PostController.php` replaces `Pheanstalk::DEFAULT_PRIORITY` with `PheanstalkPublisherInterface::DEFAULT_PRIORITY`. - ModuleTranslator and ModuleQualityAssessment: one-line swap of `Pheanstalk\Contract\PheanstalkInterface` → `Pheanstalk\Contract\PheanstalkPublisherInterface` in their `bin/WorkerAmiTalkDetect.php`. All three above are disabled on the verification stand, so the upgrade is safe to deploy there today. Broader rollout must wait on the module updates being tracked as separate tasks.
1 parent bc4875c commit 936d845

4 files changed

Lines changed: 196 additions & 58 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"lbuchs/webauthn": "^2.0",
2828
"league/oauth2-client": "^2.7",
2929
"league/oauth2-google": "^4.0",
30-
"pda/pheanstalk": "^4.0",
30+
"pda/pheanstalk": "^8.0",
3131
"php-mime-mail-parser/php-mime-mail-parser": "^9.0",
3232
"php-school/cli-menu": "^4.4",
3333
"php-school/terminal": "^0.2.1",

0 commit comments

Comments
 (0)