fix(a11y/macos): avoid main-queue deadlock in headless clipboard reads#3877
fix(a11y/macos): avoid main-queue deadlock in headless clipboard reads#3877guillaumedeshayes wants to merge 1 commit into
Conversation
`get_clipboard()` does dispatch_sync onto the libdispatch main queue, but the headless CLI (`screenpipe record`) runs #[tokio::main] — nothing ever drains the main queue, so the first Cmd+C parks the clipboard worker forever in _dispatch_thread_main_event_wait_slow. The leftover inflight marker then trips the dead-man switch on next launch, permanently disabling clipboard capture. Probe the main queue once (async no-op, 500ms): if alive (GUI app), keep the main-thread read; if dead (headless), read on the worker thread — re-exposing the off-main race (arboard#218), backstopped by the existing crash marker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Closing while I investigate test failures — will reopen with a complete fix. |
louis030195
left a comment
There was a problem hiding this comment.
nice fix. @guillaumedeshayes does this completely resolve the headless cli hang on mac?
generated by the screenpipe pr-review pipe (https://screenpi.pe), not written by a human — reply and tag @louis030195 if it got something wrong.
|
Thank you. Yes, it does @louis030195 , I tested on mac. |
|
@guillaumedeshayes thanks for the detailed writeup. I pulled the branch and verified the fix end to end. It does resolve the headless deadlock. I ran the
So the three that deadlocked on One thing to address before merge: Heads-up on something that surprised me while testing: these macOS clipboard tests do not run in CI at all. The workspace test job runs on Ubuntu where Minor and optional: the probe result is latched for the whole process via Net: the approach is right and the deadlock is gone. Fix up |
Anshgrover23
left a comment
There was a problem hiding this comment.
@guillaumedeshayes Could you add the demonstration video/Screenshots?
description
In headless CLI mode (
screenpipe record), the first Cmd+C permanently breaks clipboard capture.get_clipboard()hops onto the libdispatch main queue viadispatch_sync(introduced in c3598dc to fix the off-main NSPasteboard SIGSEGV). That's correct in the desktop app, where the main thread runs the runloop — but the CLI's main thread is#[tokio::main]'sblock_on, so the main queue is never drained and the sync hop blocks forever. The clipboard worker hangs, no clipboard row is written, andclipboard-read-inflightis left behind — so on the next startup the dead-man switch promotes the stale marker toclipboard-disabled-after-crashand clipboard capture is silently disabled until the user manually deletes the file.Fix: probe the main queue once per process (async no-op, 500ms timeout). Alive (desktop app) → keep the main-thread
sync_onceread, unchanged. Dead (headless CLI) → read the pasteboard on the worker thread. This re-exposes the off-main race from 1Password/arboard#218, but it's the only option without a main runloop, the window is microseconds per read, and the existing crash-marker dead-man switch backstops a crash loop. If the queue is dead, the leaked probe block is harmless (it could only run at process exit).related issue: none filed — happy to open one if preferred.
before
Sampled stack of the hung worker (100% of 1589 samples over 2s):
Main thread at the same time:
pthread_cond_waitinside tokio'sblock_on— never touches the dispatch main queue. On the next launch, the stale inflight marker is promoted toclipboard-disabled-after-crash→ clipboard permanently off.after
Desktop app path unaffected: the probe succeeds there, so reads stay on the main thread exactly as today.
how to test
cargo build --release --bin screenpipe, then./target/release/screenpipe record --disable-audio --disable-vision --data-dir /tmp/sp-test --port 3042sqlite3 /tmp/sp-test/db.sqlite "SELECT text_content FROM ui_events WHERE event_type='clipboard'"→ shows the copied text (before this patch: zero rows, and/tmp/sp-test/clipboard-read-inflightis left behind)test status
cargo test -p screenpipe-a11y --lib: 166 passed, 3 failed — the 3 failures (test_get_clipboard_{set_and_read,unicode,large_content}) are pre-existing and fail identically on unpatchedmain(verified: 3 passed / 3 failed, sameNoneassertion, baseline at f62ba66). They round-trip the real pasteboard from the test process, which doesn't work in a headless test environment on recent macOS — unrelated to this change.🤖 Generated with Claude Code