Commit 936d845
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
- src
- Core
- System
- Workers
- PBXCoreREST/Controllers/Modules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
| 30 | + | |
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| |||
0 commit comments