Skip to content

jetlink-server: a SIGTERM during start-up stops it cleanly on Linux - #20

Closed
ernestchu wants to merge 1 commit into
zoompilot:mainfrom
ernestchu:sigterm-startup
Closed

ernestchu wants to merge 1 commit into
zoompilot:mainfrom
ernestchu:sigterm-startup

Conversation

@ernestchu

Copy link
Copy Markdown

On Linux, a SIGTERM that reaches jetlink-server while it is starting up ends the process outright, with no stopping on SIGTERM, no ordered shutdown and a non-zero exit. ProcessTests.sigtermStopsItCleanly fails about one run in ten on Linux because of it.

Cause

holdStopSignals() blocks SIGINT and SIGTERM before any thread starts, so an early signal stays pending until stopOnSignals takes it. But on Linux, dispatchMain() turns the main thread into libdispatch's signal thread, which waits in sigsuspend with an empty mask. Meanwhile the DispatchSource signal sources register asynchronously on the manager thread, which is where libdispatch installs its handler. A signal pending before that registration finishes is delivered to the main thread with its default action, so the process ends.

Reproduced on Linux by sending SIGTERM as soon as each log line appears (80 runs each):

SIGTERM sent at before after
jetlink-server is serving 80 killed 80 clean
web page on port (where the existing test sends it) 5 killed 80 clean

Fix

In stopOnSignals:

  • The signal sources run on a queue of their own.
  • Each source's registration handler leaves a DispatchGroup.
  • The main thread calls dispatchMain() only after the group has emptied.
  • The stop still runs on the main queue, as before.

macOS takes the same path, although the race is Linux-only: there a source registers through kqueue and the signal is ignored.

Tests

  • New, Linux only: sigtermDuringStartUpStillStopsCleanly sends SIGTERM at the serving line. It failed 5 of 5 without the fix.
  • With the fix, in swift:6.3.3-jammy: ProcessTests passed 20 of 20 runs, and the full suite (555 tests) 5 of 5.
  • swift format lint --strict is clean.
  • Not yet run on a Mac.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HkbKCyD7z4w52noeFzme3w

The stop signals are held from the start so a SIGTERM that comes early waits
for the handler, but on Linux dispatchMain leaves the main thread in
sigsuspend with nothing blocked, while the sources register asynchronously.
A held signal that came before they had taken over ended the process with
no shutdown: every time at the serving line, and about one time in sixteen at
the web page line, where sigtermStopsItCleanly sends it. The sources now
register on their own queue, and the main thread goes to dispatch only once
they have; the handler still runs on the main queue.

A new Linux test sends SIGTERM at the serving line. It failed 5 of 5 before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HkbKCyD7z4w52noeFzme3w
@ernestchu ernestchu closed this Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant