From daf369ec3e0942d3934c42fc9e341771a1a47991 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 6 Jun 2026 15:50:11 +0200 Subject: [PATCH 1/2] Truncate failure_message so recording a large failure cannot crash the worker markJobFailed() stored the failure message and captured output verbatim into the failure_message / output TEXT columns. A failure larger than the column (e.g. a database error that echoes a huge multi-row query) made the save throw, so the worker crashed while recording the failure: the job got stuck and the queue stalled. Byte-truncate both fields to the column size before saving. --- src/Model/Table/QueuedJobsTable.php | 17 +++++++++++++++++ .../Model/Table/QueuedJobsTableTest.php | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/Model/Table/QueuedJobsTable.php b/src/Model/Table/QueuedJobsTable.php index e31df74a..226ca43f 100644 --- a/src/Model/Table/QueuedJobsTable.php +++ b/src/Model/Table/QueuedJobsTable.php @@ -76,6 +76,16 @@ class QueuedJobsTable extends Table { */ public const DAY = 86400; + /** + * Maximum byte length persisted to the `failure_message` / `output` TEXT + * columns. A larger value is byte-truncated before save so recording a big + * failure (e.g. a database error that echoes a huge query) can never overflow + * the column and crash the worker while it is recording the failure. + * + * @var int + */ + public const FAILURE_MESSAGE_MAX_LENGTH = 65535; + /** * Terminal `status` value stamped on a job that has exhausted all of its * retries. Distinguishes a permanently-failed job (which will never run @@ -768,6 +778,13 @@ public function markJobDone(QueuedJob $job, ?string $output = null): bool { * @return bool Success */ public function markJobFailed(QueuedJob $job, ?string $failureMessage = null, ?string $output = null): bool { + if ($failureMessage !== null) { + $failureMessage = mb_strcut($failureMessage, 0, static::FAILURE_MESSAGE_MAX_LENGTH); + } + if ($output !== null) { + $output = mb_strcut($output, 0, static::FAILURE_MESSAGE_MAX_LENGTH); + } + $fields = [ 'failure_message' => $failureMessage, 'memory' => Memory::usage(), diff --git a/tests/TestCase/Model/Table/QueuedJobsTableTest.php b/tests/TestCase/Model/Table/QueuedJobsTableTest.php index fe78a588..e5e99ea5 100644 --- a/tests/TestCase/Model/Table/QueuedJobsTableTest.php +++ b/tests/TestCase/Model/Table/QueuedJobsTableTest.php @@ -133,6 +133,23 @@ public function testMarkJobFailed() { $this->assertTrue($this->QueuedJobs->markJobFailed($job)); } + /** + * A failure message larger than the TEXT column must be truncated instead of + * overflowing the column and throwing while recording the failure. + * + * @return void + */ + public function testMarkJobFailedTruncatesOversizedMessage() { + $job = $this->QueuedJobs->createJob('Foo', ['test' => 'data']); + $oversized = str_repeat('x', QueuedJobsTable::FAILURE_MESSAGE_MAX_LENGTH + 5000); + + $this->assertTrue($this->QueuedJobs->markJobFailed($job, $oversized)); + + $stored = $this->QueuedJobs->get($job->id); + $this->assertNotNull($stored->failure_message); + $this->assertLessThanOrEqual(QueuedJobsTable::FAILURE_MESSAGE_MAX_LENGTH, strlen((string)$stored->failure_message)); + } + /** * @return void */ From 60338a4910b3c753fce726b0c54c2f7ae3a0b116 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sat, 6 Jun 2026 18:07:23 +0200 Subject: [PATCH 2/2] Fix phpstan: cast dynamic worker action call instead of a stale ignore A newer phpstan no longer reports an error on the dynamic $this->$action() call, so the @phpstan-ignore-next-line became an unmatched (failing) ignore. Cast the result to int to satisfy the declared return type without an ignore. --- src/Command/WorkerCommand.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index f1ee3426..52241f3e 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -122,8 +122,7 @@ public function execute(Arguments $args, ConsoleIo $io) { return $this->clean($io, (bool)$args->getOption('force')); } - /** @phpstan-ignore-next-line */ - return $this->$action($io, $pid); + return (int)$this->$action($io, $pid); } /**