Skip to content

fix: make signal handlers async-signal-safe#25

Open
rmorgans wants to merge 6 commits intomobydeck:mainfrom
rmorgans:fix/signal-safety
Open

fix: make signal handlers async-signal-safe#25
rmorgans wants to merge 6 commits intomobydeck:mainfrom
rmorgans:fix/signal-safety

Conversation

@rmorgans
Copy link

Summary

  • Replace printf/exit in signal handlers with volatile sig_atomic_t flags (async-signal-safe)
  • Use sigaction() instead of signal() with explicit SA_RESTART control: enabled for SIGWINCH, disabled for fatal signals so select() returns EINTR promptly
  • Handle EINTR in read paths
  • Fix hang when fatal signal arrives during a blocked write: write_all() stops retrying EINTR when die_signal is set, and the exit path uses TCSANOW + _exit() when stdout is wedged
  • Add forkpty()-based signal safety integration tests with exact PID targeting
  • Add *.dSYM/ to .gitignore

Dependency

This branch is stacked on #22 (fix/write-all). The first 3 commits belong to that PR. Merge #22 first, then this PR will be a clean 3-commit diff.

Test plan

  • sh tests/test_signal.sh ./atch — 12 passed, 0 failed
  • sh tests/test.sh ./atch — signal cases pass; one pre-existing unrelated failure (dash in binary name)
  • Manual repro: attach to yes X session, don't drain PTY, send SIGTERM — exits within 1s (previously hung forever)

🤖 Generated with Claude Code

rmorgans and others added 6 commits March 14, 2026 22:51
Extract write_all() and use it everywhere a socket write must be
complete-or-fail. Fixes spurious disconnects under memory pressure
or signal interruption that returns a short count.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
send_kill() still used a bare write() for the kill packet.
Apply the same write_all() retry loop as push and attach paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- write_all: return -1 with errno=EIO when write() returns 0 (no
  progress), preventing an infinite retry loop
- Include fault injection tests (preload_short_write.c) that force
  short socket writes to deterministically verify the retry logic
  in push and kill paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace printf/exit in signal handlers with volatile sig_atomic_t flags.
Use sigaction() instead of signal() to control SA_RESTART explicitly:
SA_RESTART for SIGWINCH (benign), no SA_RESTART for fatal signals so
select() returns EINTR promptly.

Handle EINTR in read paths. write_all() stops retrying EINTR when
die_signal is set. When stdout itself is wedged (blocked write to PTY),
the deferred-signal exit path uses TCSANOW + _exit() to avoid hanging
in printf or atexit handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
C harness using forkpty() for exact PID targeting — no pkill races or
heuristic process identification. Tests SIGWINCH survival, SIGTERM exit,
SIGHUP exit, SIGTERM during blocked stdout write, and detach character.

Wire harness into test.sh with TAP folding that preserves diagnostic
lines for debuggability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
rmorgans added a commit to rmorgans/atch that referenced this pull request Mar 15, 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