[ROMEO-1265] Exit cleanly on SIGTERM received during boot - #94
Merged
Conversation
Booting the application can take long enough that a termination signal arrives before Runner installs its graceful signal handling, so the process dies non-gracefully and the orchestrator reports a crash. Install a minimal SIGTERM trap before boot_system; Runner replaces it with the full graceful shutdown once boot completes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
air-forkbomb
approved these changes
Jul 27, 2026
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.
Issue
Workers that are scaled to zero get resurrected by a deploy (the chart applies
replicas: 1), then the autoscaler's 30s probe sees an empty queue and scales them back down. The SIGTERM therefore lands at pod age ~10–20s — while the app is still booting.CLI#runboots the application before any signal handling exists:A SIGTERM arriving in that window hits no handler at all, so the process dies from the default disposition.
bin/eventbosswraps everything inrescue => e ... exit 1, so the orchestrator sees a non-zero exit and reports a crash — for us that means an SMS page for what is actually an ordinary, harmless scale-down.This is not specific to the autoscaler: any mid-boot SIGTERM does it (Spot reclaim, node drain, rebalancing,
kubectl rollout restart).Why not copy this from Sidekiq
It was suggested that since eventboss borrowed its signal handling from Sidekiq, we could transplant Sidekiq's solution. Sidekiq has the same gap —
lib/sidekiq/cli.rb#L42-L68:boot_applicationis line 43, theIO.pipe+Signal.traploop only starts at line 50. Sidekiq just doesn't get bitten in practice, because its workers aren't usually scaled to zero seconds after starting. The self-pipe pattern eventboss already copied intoRunner#setup_signalsis the Sidekiq code — same code, same place relative to boot.Solution
Install a minimal SIGTERM trap before booting:
This only covers the boot window. Once boot completes,
Runner#setup_signalsoverwrites the trap with the normal graceful shutdown (drain the launcher, thenexit 0), so runtime behaviour is unchanged — no in-flight message can be lost by this. There is nothing to drain during boot anyway: no listeners are running, so no SQS message has been received yet. Eventboss deletes messages only after successful processing, so anything mid-flight simply returns to the queue.The trap is deliberately minimal rather than a second self-pipe: it exists purely so the exit code is 0 instead of a signal death.
Testing
New spec forks a process, stubs
boot_systemto block, sends SIGTERM mid-boot and asserts the exit status:exitstatusisnil(killed by signal, no clean exit)exitstatusis0Full suite green (109 examples, 0 failures) on Ruby 3.4.5.
Notes
Runner#setup_signalsreplaces traps unconditionally. Not needed for this bug, but it would let a host app keep its own TERM handling.🤖 Generated with Claude Code