Skip to content

fix: serialize NSPasteboard access to stop the ⌥C / clipboard-poll AppHangs#34

Merged
soyasis merged 2 commits into
masterfrom
fix/serialize-pasteboard-access
Jun 21, 2026
Merged

fix: serialize NSPasteboard access to stop the ⌥C / clipboard-poll AppHangs#34
soyasis merged 2 commits into
masterfrom
fix/serialize-pasteboard-access

Conversation

@soyasis

@soyasis soyasis commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

What

Routes every NSPasteboard access through a single serial queue so clipboard reads no longer block the main thread — fixing the 1.5.0 AppHang timeouts (Sentry) while keeping the no-concurrent-readers guarantee.

Why

On 1.5.0 a synchronous pasteboard read on the main thread could block for seconds on pboardd — fetching a Universal Clipboard / Handoff item over the network, or a large or promise-backed item — freezing the ⌥C hot path and the clipboard-history poll. The previous "everything on main" approach avoided a separate crash (two concurrent readers crashing in __NSFastEnumerationMutationHandler) but is exactly what left the main thread exposed to those hangs.

How

  • New PasteboardQueue: a process-wide serial DispatchQueue that funnels every pasteboard content read/write. Reads are async (off-main, so a slow daemon never hangs the runloop), writes are fire-and-forget but still serialized, and flush() drains pending writes on quit so a copy made right before ⌘Q isn't lost.
  • The ⌥C / history-poll detection takes ONE atomic snapshot per call (file URLs + text + image + changeCount in a single hop) and resolves off the lane — OCR and disk loads run after the read returns. A write landing mid-detection can no longer make the window act on a mix of two clipboard states, and Vision OCR doesn't occupy the shared lane.
  • Cheap changeCount probes stay on the caller's thread (the property is cached and doesn't iterate the items array).

Testing

  • Build + unit tests pass.
  • Manual matrix on a real device: plain text, URL, image file (with and without OCR-able text), screenshot image data, empty clipboard, clipboard-history re-copy, copy-then-⌘Q, a ~23 MB clipboard, and a rapid ⌥C + paste-back stress pass (no crash, no hang).
  • Universal Clipboard / Handoff — the original freeze trigger — ⌥C no longer hangs.

Notes

  • Paste-back holds the serial lane for ~0.4s after each paste (its restore window). It's bounded and only delays a ⌥C fired immediately after a paste-back — kept deliberately, because freeing the lane would let a back-to-back paste-back restore the wrong clipboard.
  • Re-copying a clipboard-history entry floats it to most-recent (standard clipboard-manager behavior).

@soyasis soyasis merged commit 61e94a5 into master Jun 21, 2026
1 check passed
@soyasis soyasis deleted the fix/serialize-pasteboard-access branch June 21, 2026 18:59
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