Skip to content

fix(clipboard): bound channels and coalesce ui notifications#73

Merged
StudentWeis merged 2 commits into
mainfrom
fix/72-clipboard-channels-bounded-and-coalesce
May 6, 2026
Merged

fix(clipboard): bound channels and coalesce ui notifications#73
StudentWeis merged 2 commits into
mainfrom
fix/72-clipboard-channels-bounded-and-coalesce

Conversation

@StudentWeis
Copy link
Copy Markdown
Owner

Summary

Replace async_channel::unbounded with bounded(256) for the clipboard event and UI notification channels in src/app.rs, and coalesce UI refresh notifications so a burst of clipboard saves results in a single repository read + GPUI refresh. Also switch the OS clipboard callback path from send_blocking to try_send so a saturated channel can never stall system clipboard notifications.

Linked Issue

Closes #72

Changes

  • src/app.rs:
    • Add CLIPBOARD_EVENT_CHANNEL_CAPACITY and UI_NOTIFY_CHANNEL_CAPACITY (both 256).
    • Switch both channels to async_channel::bounded.
    • Producer side uses try_send; a Full result is debug-logged as a coalesced refresh, Closed is warned.
    • Consumer side calls drain_pending_notifications after each recv().await to coalesce all pending () into a single repository read + UI refresh.
  • src/clipboard/listener.rs:
    • Switch send_blockingtry_send in ClipboardHandler::on_clipboard_change so the OS clipboard callback thread never blocks on a full channel; dropped events are warn-logged.
    • Image processing background task now uses async send().await for natural backpressure (still off the OS callback thread).
  • New unit tests cover drain_pending_notifications (drain + empty cases) and the bounded capacity of both channels.

Testing

  • scripts/precheck.sh passes locally (415 tests pass, clippy clean, i18n/icons/themes OK)
  • New unit tests cover the coalescing drain helper and bounded-channel saturation behavior

Replace async_channel::unbounded with bounded(256) for both the
clipboard event channel (listener -> persistence) and the UI refresh
notification channel (persistence -> foreground).

Apps that copy several times per second could grow either channel
without limit if the consumer momentarily fell behind, and every
saved record triggered a full get_display_records + GPUI refresh.

Changes:
- src/app.rs: introduce CLIPBOARD_EVENT_CHANNEL_CAPACITY and
  UI_NOTIFY_CHANNEL_CAPACITY (=256). Coalesce notifications by
  draining all pending () after each recv, so a burst of saves
  results in a single repository read + UI refresh. Use try_send
  on the producer side: a full channel means a refresh is already
  pending and the notification can be dropped (debug-logged).
- src/clipboard/listener.rs: switch send_blocking -> try_send in
  the OS clipboard callback path so a saturated channel never
  stalls system clipboard notifications. The image-processing
  background task uses async send().await for natural backpressure.
- Add unit tests for drain_pending_notifications and the bounded
  capacity of both channels.

Refs #72
… queue

Address review feedback on PR #73:

1. LastCopyState was being advanced even when try_send dropped the
   event due to a full channel. Once the channel drained, the dedup
   gate (should_forward_*) would silently filter out the next legitimate
   retry of the same content, escalating 'drop one event' into
   'permanently miss this content'. Fix: advance LastCopyState only
   after a successful enqueue, in lockstep with the channel.

2. The image processing channel was still async_channel::unbounded
   despite carrying the heaviest payload (DynamicImage). High-frequency
   image copies could grow memory without limit, defeating the bounded-
   channels goal of this PR. Fix: switch to bounded(1) with newest-wins
   overwrite: when the slot is full, the OS callback drains the oldest
   queued image and inserts the new one — the most recent image is the
   only meaningful payload.

Implementation:
- src/clipboard/listener.rs:
  - Extract on_clipboard_change body into ClipboardMonitor::dispatch_payload,
    returning Option<LastCopyState>; the caller advances state only on Some.
    The mutex guard is now held only across dedup-check + dispatch + state
    advance (also resolves clippy::significant_drop_tightening).
  - Add IMAGE_PROCESSING_CHANNEL_CAPACITY = 1 and try_send_image_overwrite
    helper that uses a producer-side Receiver clone (image_drain) to evict
    the oldest queued image on TrySendError::Full.
  - start_clipboard_monitor builds the image channel via bounded(1) and
    hands a Receiver clone to ClipboardMonitor as the drain handle.
- New unit tests cover:
  - try_send_image_overwrite: empty / full-overwrite / closed paths.
  - Text + Files branches: state stays unchanged on send failure, so the
    same content can still be forwarded after the channel drains.
  - Text branch happy path: state advances on success.

Refs #72
@StudentWeis StudentWeis merged commit 4628971 into main May 6, 2026
8 checks passed
@StudentWeis StudentWeis deleted the fix/72-clipboard-channels-bounded-and-coalesce branch May 6, 2026 07:50
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.

fix: bound clipboard channels and coalesce ui notifications

1 participant