Skip to content

Fix head-of-line blocking and resource leak in BidirectionalRelay#713

Open
mlsad3 wants to merge 5 commits intoapple:mainfrom
mlsad3:fix/bidirectional-relay-blocking
Open

Fix head-of-line blocking and resource leak in BidirectionalRelay#713
mlsad3 wants to merge 5 commits intoapple:mainfrom
mlsad3:fix/bidirectional-relay-blocking

Conversation

@mlsad3
Copy link
Copy Markdown

@mlsad3 mlsad3 commented Apr 29, 2026

Replace synchronous write() calls in BidirectionalRelay with non-blocking I/O and DispatchSourceWrite backpressure handling. Under concurrent vsock proxy load, a single blocked write on the shared serial dispatch queue would freeze all relay connections permanently, including unrelated new connections.

Closes #712

Changes

  • Set relay file descriptors to O_NONBLOCK and handle EAGAIN by suspending reads and installing a write source to drain pending data
  • Give each BidirectionalRelay its own serial queue instead of sharing one from UnixSocketRelayManager, eliminating cross-connection blocking
  • Resume suspended read sources before cancelling in stop() — GCD does not deliver cancel handlers on suspended dispatch sources, which caused file descriptor and memory leaks on teardown under backpressure
  • Guard-unwrap buf.baseAddress in drainPendingWrite

Test plan

Added BidirectionalRelayTests with 5 tests:

  • Basic bidirectional relay
  • Cross-connection head-of-line blocking isolation
  • Backpressure recovery (EAGAIN → write source drain → resume)
  • EOF handling
  • Stop while backpressured (verifies cancel handlers fire and waitForCompletion() resolves)

Tested under sustained concurrent load that previously caused permanent hangs; verified normal operations work correctly after sustained load.

@mlsad3 mlsad3 force-pushed the fix/bidirectional-relay-blocking branch from 946f453 to e3eeb21 Compare April 29, 2026 16:31
Comment thread Tests/ContainerizationOSTests/BidirectionalRelayTests.swift
@mlsad3 mlsad3 force-pushed the fix/bidirectional-relay-blocking branch 4 times, most recently from af584de to 245e9ad Compare May 1, 2026 04:06
mlsad3 added 5 commits April 30, 2026 21:11
Replace synchronous write() calls with non-blocking I/O and
DispatchSourceWrite backpressure in BidirectionalRelay. When a
destination fd's buffer fills (e.g. during high container launch
concurrency), the relay now suspends reading and installs a write
source to drain pending data, instead of blocking the dispatch queue.

Also remove the shared serial DispatchQueue from UnixSocketRelayManager
so each BidirectionalRelay uses its own queue. Previously, a single
blocked write on any connection would freeze ALL relay connections,
including unrelated commands like `docker version`.
Resume suspended read sources before cancelling in stop(). GCD will not
deliver cancel handlers on suspended dispatch sources, which caused file
descriptor and memory leaks when connections were torn down while under
backpressure. Also guard-unwrap buf.baseAddress in drainPendingWrite.

Adds test for stop() while backpressured to verify cancel handlers fire.
@mlsad3 mlsad3 force-pushed the fix/bidirectional-relay-blocking branch from 245e9ad to 4105f64 Compare May 1, 2026 04:11
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.

[Bug]: Head-of-line blocking in BidirectionalRelay causes permanent hang under concurrent vsock proxy load

2 participants