Repository navigation
fix(core): bound the Laravel scheduler preset's stop grace - #1265
Merged
Hydralerne merged 1 commit intoOct 15, 2026
Merged
Conversation
The Laravel scheduler preset used the same 90s stop grace as the workers. The inspected Laravel 12.x versions lack coordinated `schedule:work` shutdown. With PHP as container PID 1 and no termination handler, SIGTERM does not stop scheduling, and work can continue until Docker force-kills the container. A 90s grace made a scheduler handover wait that long. The scheduler preset now sets stopSignal SIGTERM and a 10s grace. The grace favors shorter shutdown and handover latency over task completion. A foreground scheduled command with more than 10 seconds left can be killed on either version. Laravel 13.18.0 with signal handling available stops new runs on INT, TERM or QUIT and waits for existing ones, but detached background tasks may not complete. Applications that need completion should use a verified draining version and size the grace for the whole remaining run. This changes registered preset metadata. Deployed roles currently come from explicit configuration, so the openship.json reference example gets the same signal and grace, with a note on sizing. The queue and horizon presets keep 90s, which exceeds the default 60s queue and Horizon supervisor timeouts. The parser round-trip test now covers the scheduler's signal and grace. Refs oblien#935
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
The Laravel
schedulerpreset now stops withSIGTERMand a 10s grace period instead of 90s. Thequeueandhorizonpresets keep 90s. The copiedrolesexample in theopenship.jsonreference gets the same scheduler settings.Motivation
The inspected Laravel 12.x versions lack coordinated
schedule:workshutdown. With PHP running as container PID 1 and no termination handler, SIGTERM does not stop scheduling or drain children; work can continue until Docker force-kills the container.In a local test on Laravel 12.70.0:
stopGracePeriod: "90s",SIGKILLarrived 90.02 s afterSIGTERMLaravel 13.18.0, with pcntl available, handles INT/TERM/QUIT by stopping new
schedule:runlaunches and waiting for existing runs. This does not guarantee completion of detached background tasks.The 10s grace deliberately favors shorter shutdown/handover latency over task completion. A foreground scheduled command with more than 10 seconds remaining can be killed on either version, leaving partial effects. Neither
withoutOverlapping()norrunInBackground()makes that bound safe. Applications requiring completion must use a verified draining version and size grace for the entire remaining run, with headroom.This changes registered preset metadata. Automatic preset application is not wired into deployment detection in this revision; deployed roles currently come from explicit configuration. Existing containers retain their saved timeout, and explicit role arrays override presets. Singleton replacement already stops the old scheduler before starting the new one: the benefit is shorter handover delay, not reduced old/new scheduler overlap.
Related issue
Refs #935.
Changes
packages/core
src/stacks.ts: the Laravelschedulerpreset setsstopSignal: "SIGTERM"andstopGracePeriod: "10s". The command andsingleton: trueare unchanged. Thequeueandhorizonpresets keep"90s". The preset comment now says:schedule:workshutdownsrc/stack-roles.test.ts: the test "every preset sets a 90s stop grace period…" becomes "preserves worker and scheduler shutdown settings through the openship.json parser". It expects["90s", "90s", "10s"]before and after the parser round-trip. The round-trip now includesstopSignal, and the scheduler'sstopSignalmust be"SIGTERM"both times.apps/web
content/docs/reference/openship-json.mdx("Warm a worker or scheduler"): the example's scheduler gets"stopSignal": "SIGTERM"and"stopGracePeriod": "10s". The queue keeps"90s". A new sentence follows the example: "These are example shutdown budgets. Longer scheduled runs may need a larger grace period on a Laravel version verified to drain."Verification
Before the preset change, the updated test fails with
expected [ '90s', '90s', '90s' ] to deeply equal [ '90s', '90s', '10s' ].Mutation checks. Each mutant fails the updated test:
"90s"withoutstopSignal.stopSignal:expected undefined to be 'SIGTERM'."90s".The edited docs example is valid JSON. Passed through
parseOpenshipConfigJson, it returns no errors and no warnings, with the scheduler atSIGTERMand10s.bun run --cwd apps/web lintcould not check the page in this checkout. It stops atCannot find module '@/.source/server', a module that the docs build generates. I did not runnext build.Prettier comparison:
stacks.ts,stack-roles.test.tsandopenship-json.mdxwere not Prettier-clean before this change. I did not reformat them. Prettier reports the same number of differences before and after this change. Instacks.tsandstack-roles.test.ts, none is on a changed line. Inopenship-json.mdx, Prettier already wanted to reflow the example's one-line JSON entries before this change, and that difference remains.Checklist