fix: guard processSendQueue against non-writable stream status#3416
fix: guard processSendQueue against non-writable stream status#3416lodekeeper wants to merge 7 commits intolibp2p:mainfrom
Conversation
9538027 to
c1c6584
Compare
Add a try-catch around sendData() in processSendQueue() to catch StreamStateError when the underlying transport closes between a drain event and the send attempt. This prevents the error from propagating as an uncaught exception. Also add a writeStatus guard in continueSendingOnDrain to skip processing when the stream has fully closed. Fixes libp2p#3415
c1c6584 to
a8c2cf2
Compare
|
@lodekeeper - can you review this commit 17d9da8 please |
|
@dozyio Reviewed commit 17d9da8 — looks good overall.
Tests: Both cases are solid:
One minor note: the second test sets Happy to address any remaining feedback before merge. |
|
@lodekeeper it seems test are failing after merging latest main branch - could you investigate? |
The drain handler was changed in libp2p#3435 to wrap processSendQueue() in queueMicrotask() to avoid a Maximum call stack size exceeded error. That merge of main into this branch causes the StreamStateError drain tests added in 17d9da8 to fail: dispatchEvent(new Event('drain')) now returns before sendData is invoked, so synchronous assertions on stub call counts and writeBufferLength see stale state. Await the microtask queue between dispatching drain and asserting so the tests observe the post-drain state the scenarios were designed to verify. 🤖 Generated with AI assistance
|
@dozyio Investigated — pushed Root cause: the merge of Fix: await a microtask flush ( |
What
Add a
writeStatusguard at the top ofprocessSendQueue()inabstract-message-stream.tsto bail out when the stream is no longer writable.Why
A race condition causes an uncaught
StreamStateErrorwhen adrainevent fires on a stream whose underlying transport is already closing:writeStatustransitions to'closing'drainevent fires on the underlying TCP streamsafeDispatchEvent('drain')on the encrypted streamcontinueSendingOnDrain→processSendQueue()→sendData()→send()on the now-closing streamsend()throwsStreamStateError— inside an event listener with no try/catch → uncaught exceptionObserved on Lodestar mainnet (v1.41.0,
@libp2p/utils@7.0.13): 6 uncaught exceptions in 7 days.Fix
Single guard at the top of
processSendQueue(), consistent with the existing bail-out pattern:Test
Added a regression test that:
capacity: 1to trigger backpressuresend()returnsfalsewriteStatusto'closing'draineventsendDatais NOT called (the guard catches it) and no error is thrownCloses #3415