30 July 2026¶
1 change:
- CommandBase: one model for long-running workers
CommandBase: one model for long-running workers¶
Two new Pramnos\Console primitives — SignalStop (cooperative graceful stop) and
SystemdNotifier (sd_notify) — join WorkerLock/WorkerReloader, and CommandBase
now composes all four into a single long-running-worker model. ProcessQueue and
broadcast:serve drop their hand-rolled copies onto it.
See the Console Guide.
Why¶
The daemon concerns were reimplemented per worker, three different ways:
- Graceful stop:
ProcessQueuetrapped only SIGINT (viaconfigureInterruptHandling) and drove a private$shouldContinue;broadcast:servehand-rolled its own SIGTERM+SIGINT pair; the app'sworker.phphand-rolled a third. SIGTERM (systemctl stop / deploy) was not even trapped byProcessQueue. - sd_notify: existed only as a copy-pasted function in an application worker; nothing in the framework spoke the protocol.
- Heartbeat:
CommandBase::heartbeat()took no arguments, so it could not record the rich state (jobs_processed,current_job, …) thatWorkerLock::heartbeat()already supports, nor surface the lease-lost signal.
Added (all additive)¶
SignalStop— async SIGTERM/SIGINT handlers that raise a flag (or run an$onStopcallback); the loop finishes the current job, then exits. No-op withoutpcntl.SystemdNotifier—ready()/watchdog()/stopping()/status()overNOTIFY_SOCKET; every call a no-op offType=notify. Skips unreachable abstract-namespace sockets.CommandBasegainsinstallStopSignals(?callable),shouldStop()(signals ∪.stopsentinel),signalStop()/systemd()accessors, a richerheartbeat(array $extra = []): bool(records state, pings the watchdog, returns lease-still-ours), andendJob()now sends systemdSTOPPING.
Changed¶
ProcessQueuereplaces its$shouldContinue/handleSignalwithinstallStopSignals()+while (!$this->shouldStop())— now trapping SIGTERM too — with identical stop/lock-missing/max-runtime behaviour.broadcast:servereplaces its inline pcntl pair withinstallStopSignals(fn () => $server->stop()).
The two documented stop models are now distinct: beginJob()'s SIGINT handler (immediate
cleanup+exit) for one-shot lock-guarded commands, and installStopSignals()/shouldStop()
(cooperative) for long-running workers.
BC¶
CommandBase::heartbeat() changed from (): void to (array $extra = []): bool — additive
for callers (existing no-arg calls and ignored returns keep working); a subclass that
overrides heartbeat() must widen its signature to match. No <namespace>\Application
consumer relied on the removed private ProcessQueue members. Full framework suite green,
and validated end-to-end against a downstream consumer application.
Tests¶
SignalStopTest, SystemdNotifierTest (real unix datagram socket), new CommandBaseTest
cases (heartbeat $extra + shouldStop signal/sentinel), and the existing
ProcessQueueCommandTest/BroadcastServeTest suites (migrated, green).