Skip to content

fix: move IN-loop clipboard ops off watchdog thread pool (Windows)#12

Merged
offbyonebit merged 1 commit into
mainfrom
claude/windows-slowness-pool-error-TyJCD
May 21, 2026
Merged

fix: move IN-loop clipboard ops off watchdog thread pool (Windows)#12
offbyonebit merged 1 commit into
mainfrom
claude/windows-slowness-pool-error-TyJCD

Conversation

@offbyonebit
Copy link
Copy Markdown
Owner

Root cause

On Windows, Observer uses WindowsApiObserver, which in watchdog 4.x dispatches filesystem events through an internal thread pool. The previous code called _on_file_changed() synchronously inside the watchdog event handler, meaning every incoming file-change event blocked a pool worker thread while doing:

  1. File read from disk
  2. pyperclip.paste() — Windows clipboard access, a GUI resource that serializes between processes and can stall
  3. pyperclip.copy() — same

Under normal Syncthing activity (many temp-file events), all pool workers could be blocked simultaneously → BrokenExecutor / pool-exhaustion error + the app becoming sluggish or unresponsive. Linux uses InotifyObserver which doesn't have a thread pool limit in the same way, so the bug didn't surface there.

There was also a secondary performance issue: _matches() called Path.resolve() three times on every filesystem event (including all the Syncthing temp files), which is expensive on Windows.

Changes (clipsync/clipboard.py)

  • _in_queue + clipsync-in thread: watchdog handler now just does queue.put(path) and returns immediately, keeping the pool free. A dedicated thread we own drains the queue and calls _on_file_changed().
  • Cached resolved paths in _ClipboardFileHandler.__init__: resolved once at startup, reused on every event.
  • Fast name pre-filter in _matches(): checks Path(path).name against a set {"clipboard.txt", "clipboard.png"} before the more expensive Path.resolve() call, so Syncthing temp-file events are rejected in O(1).
  • Proper _in_loop shutdown: stop() posts a sentinel ("") to unblock the queue get, joins the thread with a 3 s timeout.

Test plan

  • On Windows: copy text between two peers — should be fast with no pool errors in the log
  • Confirm clipsync-in thread appears in thread listing at startup
  • Confirm clean shutdown (no hang during stop())
  • Existing test suite passes (ruff, mypy, pytest)

https://claude.ai/code/session_0128MyVVEu1Dt3jTeaZ1edyL


Generated by Claude Code

On Windows, watchdog's Observer uses WindowsApiObserver which dispatches
events through a thread pool. The old code ran file I/O and pyperclip
clipboard access synchronously inside the watchdog event handler, blocking
pool workers under load and causing pool-exhaustion errors and UI
slowness that didn't reproduce on Linux.

- Add _in_queue (SimpleQueue) and a dedicated clipsync-in thread that
  drains the queue and calls _on_file_changed. The watchdog handler now
  just puts a path on the queue and returns immediately, keeping the
  thread pool free.
- Cache resolved clipboard file paths in _ClipboardFileHandler so
  _matches() doesn't call Path.resolve() three times per event.
- Add fast name-based pre-filter before the (more expensive) resolve()
  call, since Syncthing generates many temp-file events for other files.

https://claude.ai/code/session_0128MyVVEu1Dt3jTeaZ1edyL
@offbyonebit offbyonebit marked this pull request as ready for review May 21, 2026 16:41
@offbyonebit offbyonebit merged commit f55ad35 into main May 21, 2026
1 check 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.

2 participants