Regression Tests for Job Timeout Signals in the Queue Worker
Contributed by jackbayliss
Part of the framework v13.35.0 release
Pull request #61787 lands on the 13.x branch as a test-only addition to the queue worker: it pins down the "notify the job of a timeout signal" behavior that a separate, earlier PR introduced. If you are upgrading from v13.34.0, nothing changes at runtime — the diff touches only tests/Queue/QueueWorkerTest.php. What you gain is regression protection for two subtle guarantees around job timeouts and the Interruptible contract, so that a future refactor of Illuminate\Queue\Worker cannot silently break them.
What changed
Two new tests were added to QueueWorkerTest, plus a small helper:
testInterruptibleJobIsNotifiedWhenTheJobTimesOutasserts that when a job exceeds its timeout, a job whose underlying command implementsInterruptiblereceivesSIGALRMthrough itsinterrupted()method.testTimeoutIsStillHandledWhenNotifyingTheJobFailsasserts that ifinterrupted()throws, the exception is reported to the exception handler and theJobTimedOutevent is still dispatched — in other words, the timeout is still enforced.- A
timeOutJob()helper registers the worker's realpcntltimeout handler, manually fires theSIGALRMhandler, and cleans up the signal state afterwards.
Both tests are annotated with #[RequiresPhpExtension('pcntl')], so they skip cleanly on environments without the extension. The PR also widens the visibility of registerTimeoutHandler() on the test worker subclass so the tests can invoke it directly.
The behavior it pins down
The lines under test live in the worker's timeout flow. When a job starts, the worker registers an alarm-based handler; when that alarm fires, the worker calls notifyJobOfSignal(SIGALRM) — giving the running command a chance to react — and then kills the process so the timeout is enforced. That notification code, added in the separate PR this one covers, previously had no coverage at all.
For users, this becomes relevant the moment a job implements the Interruptible contract:
use Illuminate\Contracts\Queue\Interruptible;
class GenerateAnnualReport implements ShouldQueue, Interruptible
{
public function interrupted(int $signal): void
{
Log::warning('Report job timed out; releasing locks and cleaning up.', [
'signal' => $signal,
]);
// Delete temp files, release locks, flush metrics...
}
public function handle(): void
{
// Long-running work.
}
}
The first test proves your interrupted() method really is invoked with SIGALRM when the job times out — not just with worker stop signals.
Why it matters
Before this PR, the timeout-notification path was untested, so any refactor of Worker could have silently dropped the notification or, worse, allowed a failing interrupted() to abort timeout handling. The second test encodes the resilience guarantee explicitly: exceptions thrown from your interrupted() implementation are reported through the exception handler — neither swallowed nor propagated into the signal path — and the JobTimedOut event still fires, so listeners and monitoring keep working.
That is a contract you can build on: notification is best-effort, enforcement is guaranteed.
How the tests work
The tests use an anonymous class that records the received signal, and a mocked CallQueuedHandler whose getRunningCommand() returns it — mirroring how the real worker resolves the command behind a queued job. The helper then stubs Worker::killUsing() so the simulated timeout throws a caught exception instead of actually exiting the test process, fires the registered handler manually via pcntl_signal_get_handler(SIGALRM)(SIGALRM), and restores default signal handling in a finally block. It is a tidy pattern for testing pcntl-dependent code paths deterministically.
Upgrade impact
None. There are no changes to src/, no new configuration, and no behavioral differences between v13.34.0 and a release containing this PR. Two notes if you maintain custom worker code: if you override notifyJobOfSignal(), these tests now document the behavior you should preserve; and remember that signal notification itself requires the pcntl extension, which is also why the tests self-skip without it.
Takeaways
- Test-only PR: no runtime changes; upgrading from v13.34.0 is a no-op for your application.
- Coverage added for
notifyJobOfSignal(SIGALRM)on the job-timeout path, exercisingInterruptible::interrupted(). - Guarantees documented: interrupted jobs receive
SIGALRM; a throwinginterrupted()is reported and cannot prevent the timeout or theJobTimedOutevent. - PCNTL-aware tests skip automatically wherever signals are unavailable.
- Category: Chore (id 5)