mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-05 15:41:23 +03:00
Follow-up to the previous commit, from an adversarial review of it. - The ack wake-up skipped the coalescing drain. When the socket arm of the select won, there was no message to seed the drain loop with, so the channel was never polled that iteration: a held frame could be sent while a strictly newer one already sat queued, and a queued cursor waited for the next producer message. Seed the loop from the channel when we woke on an ack instead. - The loop could wait while holding a frame it was allowed to send. Credit replenished by the top-of-loop drain was not consulted before entering the select, so the frame waited for the worker's next message; if capture then returned WouldBlock it sat there until the stall teardown. Take whatever is queued without blocking in that case and fall through to the send. - The capture worker no longer had any backpressure. Draining the channel every iteration (needed so cursors keep flowing) means a full channel no longer parks it, so a consumer converting at a fraction of the capture rate made the privileged service keep grabbing frames that were then discarded -- a packed copy per frame on the CPU path, a PRIME export on the dma-buf path. The worker now skips the grab while the task is holding an undeliverable frame, and keeps polling the cursor so the remote pointer stays live. The gate is deliberately conditioned on holding a frame, not merely on having no credit: with nothing held the task blocks in recv() and cannot observe an ack, so gating there would stop the worker feeding it at all. The comment claiming the bounded channel backpressures the worker is corrected.