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); } /** 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 */