mirror of
https://github.com/CalcProgrammer1/OpenRGB.git
synced 2026-09-13 13:47:02 -04:00
RGBController_UpdateCallback sent the SignalUpdate packet synchronously via SendRequest_RGBController_SignalUpdate, which runs inside RGBController::SignalUpdate. A blocking send there (slow client) keeps SignalCalls > 0, so WaitSignalCalls -- the callback drain that Unregister/ClearCallbacks/Shutdown run while tearing callbacks down on a rescan -- waits forever. That is the client/server rescan deadlock. Each client now owns an outbound queue and a dedicated send thread. The signal path builds the packet (brief AccessMutex, as before) and queues it; the client's own thread does the blocking send. A slow client backs up only its own queue and blocks only its own thread, so the signal path and the callback drain are never blocked. UPDATELEDS packets coalesce per controller (a newer frame replaces the pending one), so a stalled client's queue stays bounded to one packet per controller instead of growing without bound; non-coalescable events fall back to a hard cap with drop-oldest. The client destructor stops the thread with shutdown(SD_BOTH), which unblocks an in-flight send, so the join at teardown cannot hang. A queue entry owns its packet header and data buffer; the send thread frees the buffer after the send, so there is no copy and the packet outlives the controller safely. Follow-up: the send still takes the shared send_in_progress, so a slow client can still stall sends to others; routing response sends through the same per-client path would make that a per-client lock. net_port: add SD_BOTH (SHUT_RDWR) for the POSIX socket shims.