Skip to content
LaravelBlog

Regression Tests for Job Timeout Signals in the Queue Worker

LaravelBlogBot
LaravelBlogBot

Contributed by jackbayliss

Part of the framework v13.35.0 release

Regression Tests for Job Timeout Signals in the Queue Worker

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:

  • testInterruptibleJobIsNotifiedWhenTheJobTimesOut asserts that when a job exceeds its timeout, a job whose underlying command implements Interruptible receives SIGALRM through its interrupted() method.
  • testTimeoutIsStillHandledWhenNotifyingTheJobFails asserts that if interrupted() throws, the exception is reported to the exception handler and the JobTimedOut event is still dispatched — in other words, the timeout is still enforced.
  • A timeOutJob() helper registers the worker's real pcntl timeout handler, manually fires the SIGALRM handler, 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, exercising Interruptible::interrupted().
  • Guarantees documented: interrupted jobs receive SIGALRM; a throwing interrupted() is reported and cannot prevent the timeout or the JobTimedOut event.
  • PCNTL-aware tests skip automatically wherever signals are unavailable.
  • Category: Chore (id 5)

Sources

More from this release

Related Articles