Repository navigation
feat: throw when a failed job can't be stored instead of only logging it - #643
Merged
Merged
Conversation
…ored FailedJobHandler now throws a typed FailedJobNotStoredException (a QueueException naming the job class, id, queue and the job's own error, with the storage error as previous) when the failed-job repository throws. failed() and JobFailedEvent still don't run, and callers don't delete the job, so it is failed again once its reservation expires. The worker stops on it: it logs the exception at critical level and rethrows it out of work() (queue:work exits 1), including when a driver throws it from pop(), instead of logging a pop failure and polling again. On the timeout path it is logged at critical level, since it runs inside the alarm handler, and the worker exits as after any timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KaHtaJjfe2D38CdEdnmTb
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When the failed-job repository can't store a job that failed for good,
FailedJobHandlernow throws a typedFailedJobNotStoredException(extendsQueueException). The message names the job class and id. The context names the queue, the job's own exception and the storage error, and the storage error is attached asprevious. #535's behaviour is unchanged:failed()isn't called andJobFailedEventisn't dispatched when storing fails, and no caller deletes the job, so it stays in its queue (still reserved) and is failed again once its reservation expires.How each caller handles it:
Worker(last attempt, invalid backoff, retry limits, per-job timeout guard)critical([marko/queue] Worker stopping: <message> <context> <suggestion>), then rethrows it out ofwork().queue:workprints it and exits 1.Workerpop, whenDatabaseQueue/RedisQueuefail a job at pop timeWorker::handleTimeout()(inside the pcntl alarm handler)critical, not thrown, because an exception thrown from the signal handler would surface inside the interrupted job'shandle(). The worker then exits withEXIT_TIMED_OUT, as it does after any timeout.SyncQueue::push()FailedJobNotStoredExceptioninstead ofJobFailedException. This can only happen with a custom repository, becauseNullFailedJobRepositorynever throws.Decisions
queue.retry_after, once the store works.FailedJobNotStoredException, with the named constructorfailedJobNotStored(), rather than another factory onQueueException. With its own type, the worker can catch it separately from ordinary pop failures and other queue errors without matching on message text.store()call is wrapped.SerializationExceptionfrom signing the payload still propagates as before.getPrevious().Issue vs code
The issue says the handler "only logs" a storage failure. In fact
FailedJobHandlernever caught it, so the raw exception already propagated. The log-only handling was in the worker: its pop-failure path, which swallowed pop-time storage failures from the database/redis drivers and kept polling, andhandleTimeout(). This PR fixes both places and types the exception.Docs
queue.md: new "When a Failed Job Cannot Be Stored" subsection under "When a Job Fails for Good", plus updates to the pop-failure note, the Worker Logs table (newcriticalrow), Stop Reasons, theFailedJobHandlerInterfacereference and the Exceptions table. One-line notes were added toqueue-database.md,queue-redis.mdandqueue-sync.md.Tests
FailedJobHandlerTest: a store failure throws with the job class/id, queue, job error and storage error asprevious.failed()isn't called, no event is dispatched, nothing is logged.WorkerFailedJobNotStoredTest: the worker stops and pulls no further jobs; the job is neither deleted nor released; acriticallog line is written; aFailedJobNotStoredExceptionfrompop()is rethrown with no sleep; other pop failures still poll; the timeout path logscriticaland exits withEXIT_TIMED_OUT.DatabaseQueueAttemptsTest: withfailed_jobsrenamed away,pop()throws, the row stays reserved, andfailed()and the event don't run. After the table is restored and the reservation expires, the job is failed and stored, andfailed()runs exactly once.SyncQueueTest:push()throwsFailedJobNotStoredExceptionwith the storage error asprevious, andfailed()and the event don't run.composer cigreen locally (tests, phpcs, php-cs-fixer, phpstan, docs:lint).Fixes #641
🤖 Generated with Claude Code
https://claude.ai/code/session_017KaHtaJjfe2D38CdEdnmTb