Skip to content

fix(agent-isolation): stop the macOS touch overlay deadlocking on SIGTERM - #1376

Merged
potiuk merged 1 commit into
apache:mainfrom
potiuk:fix/macos-touch-overlay-signal-deadlock
Sep 24, 2026
Merged

potiuk merged 1 commit into
apache:mainfrom
potiuk:fix/macos-touch-overlay-signal-deadlock

Conversation

@potiuk

@potiuk potiuk commented Sep 24, 2026

Copy link
Copy Markdown
Member

The macOS touch overlay sometimes hangs on screen after the key is touched, and only a force-quit gets rid of it.

Cause

The watcher closes the Aqua window with SIGTERM. The window script installs flag-setting Python handlers for that, but it does so before tk.Tk(). Aqua Tk's initialisation then replaces SIGINT/SIGHUP/SIGTERM with its own TkMacOSXSignalHandler, which calls Tcl_Exit straight from signal context.

That is not async-signal-safe. The pulse redraws every 33 ms, so the signal often lands mid-draw, inside CA::Transaction::commit, which holds CoreAnimation's backing-store lock. Tcl_Exit destroys the windows, the destroy invalidates the view, and that blocks on the same lock. The main thread deadlocks on itself.

A macOS hang report shows exactly that chain:

CA::Transaction::commit → … → ripc_DrawRects
  → _sigtramp → TkMacOSXSignalHandler → Tcl_Exit
  → Tk_DestroyWindow → XDestroyWindow → -[NSView setNeedsDisplayInRect:]
  → CABackingStoreInvalidate → __psynch_mutexwait   (deadlocked, 14 s+)

Fix

  • New take_back_signals() installs the flag handlers for SIGTERM, SIGINT and SIGHUP right after the root exists, so Tk's handler is replaced. The existing watch_for_stop poll then closes the window, and nothing unsafe runs in signal context.
  • main() no longer installs handlers before tk.Tk().
  • New AST test test_the_aqua_window_takes_termination_signals_back_from_tk pins the ordering. It fails on the old code.

Testing

  • tools/agent-isolation/tests/test_gpg_touch_overlay.py: 93 passed, 7 skipped (the environment has no Tk 8.6 python, and ps is restricted).
  • The pre-commit hooks pass: workspace ruff, ruff format, mypy and pytest.
  • Not reproduced live: the deadlock is a timing race, and the hang report above is the evidence.

🤖 Generated with Claude Code

…TERM

The watcher closes the Aqua overlay with SIGTERM the moment the key is
touched. The window installed its flag-setting handlers before tk.Tk(),
but Aqua Tk's initialisation replaces SIGINT/SIGHUP/SIGTERM with its own
handler, which calls Tcl_Exit from inside the signal. Landing while the
pulse is mid-redraw, the teardown blocks on the CoreAnimation
backing-store lock the interrupted draw holds: the window deadlocks on
its own main thread and stays on screen until force-quit (the hang
report shows _sigtramp -> TkMacOSXSignalHandler -> Tcl_Exit ->
Tk_DestroyWindow -> CABackingStoreInvalidate -> __psynch_mutexwait
under CA::Transaction::commit).

Take the signals back right after the root exists, and add SIGHUP, so
the existing watch_for_stop poll is what closes the window and nothing
unsafe runs in signal context. A regression test pins the ordering.

Generated-by: Claude Opus 5
@potiuk
potiuk merged commit b27c871 into apache:main Sep 24, 2026
9 checks passed
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