Fixes
QueueProcessesTable::add()now evicts stale(pid, server)rows whose heartbeat is older than the newQueue.staleHeartbeatThreshold(default 90s) before inserting, fixing the worker crash-loop on containerized deployments (Docker/FrankenPHP). When the killed worker's row survived and the new worker tried to register with the same recycled PID, the unique-index collision threw aQueryExceptionthat the existingPersistenceFailedExceptioncatch inProcessor::run()didn't handle (#493).- Idle-poll heartbeat output is now suppressed unless
--verboseis set, so a worker waiting for jobs no longer floods stdout/log with "Looking for Job …" / "nothing to do, sleeping." lines on every loop iteration (#491). Processor::captureOutput()no longer caches the schema check for the lifetime of the worker. A long-running worker started beforebin/cake migrations migrateadded theoutputcolumn toqueued_jobswould see the cachedfalsefor the rest of its runtime and silently drop captured stdout until restart. ExplicitQueue.captureOutputconfig is still memoised (that branch never changes mid-process); auto-detect now re-checks per call (#496).ExecuteTask::add()no longer mangles quoted command paths with embedded spaces.explode(' ', ...)split"/usr/local/bin/My Tool" arg1acrosscommandandparams[0]; switched tostr_getcsvwith space delimiter so quoted paths survive intact. The plaincmd arg1 arg2shape continues to work unchanged (#496).QueueController::index()caps the rendered pending/scheduled lists atQueue.adminDetailsLimit(default 200) to avoid OOM on realistic backlogs. With thousands of pending or failed-and-retried rows the dashboard previously crashed via DebugKit's Variables panel serialising every view variable. Aggregate counts on the tiles keep reflecting the true totals; truncated lists get a "Showing N most recent of M" notice with a pre-filtered link to the QueuedJobs admin (#497).
Improvements
-
New
--force(-f) option onbin/cake queue worker cleanwipes everyqueue_processesrow regardless of the heartbeat threshold (#492). Recovery path for the same container-restart scenario above when an operator wants a one-shot manual reset. -
Queue.adminBackUrl(with optionalQueue.adminBackLabel) makes the admin layout's "Back to App" header link configurable, so installations can point users back to a non-default app URL. Mirrors the same pattern used by cakephp-audit-stash, cakephp-bouncer and cakephp-databaselog. -
workerkeyis now the canonical worker identity inqueue_processes(#494). The unique(pid, server)index has been dropped (re-added as a non-unique index for query performance) because PID is not a stable identity — the OS recycles low PIDs across container restarts.update(),remove(), andendProcess()look up byworkerkey(already uniquely indexed); when multiple rows share a PID, the most recently heartbeated row is targeted. Migration required: runbin/cake migrations migrate -p Queue. -
createJob()gains an opt-inuniqueoption that dedups fan-out enqueues against the existingreference-keyed pending job (#498). First call inserts as usual; subsequent calls while the original is still pending return the existing entity without inserting. Once it completes, the next call inserts a fresh row, so scheduled re-runs continue to enqueue normally. Requiresreferenceto be set (throwsInvalidArgumentExceptionotherwise); default isfalseso existing callers are unchanged.$queuedJobsTable->createJob( 'VolunteerCheckOutReminder', ['account_uuid' => $accountUuid], [ 'reference' => 'volunteer_check_out:' . $accountUuid, 'unique' => true, ], );
-
Admin dashboard banner gains a red Queue Stalled state for "action required" signaling (#499). Triggers when the last heartbeat is older than
Queue.dashboardStalledAfter(default 120s) with a pending backlog and no in-flight job, or when no worker has reported at all and there's a pending backlog or a stuck fetched job. The existing green Queue Running and yellow Queue Idle states are unchanged. When red, the banner expands with a diagnostic grid (last activity, workers/servers, pending count) and a one-line cause hint. Two new config keys:Queue.dashboardIdleAfter(60s default),Queue.dashboardStalledAfter(120s default).
Full Changelog: 8.13.0...8.14.0