From 2f4238df5f54dcf9645d98a9a5056cd3823934d9 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Thu, 6 Aug 2026 12:07:00 +0200 Subject: [PATCH 1/2] stream: fix flaky stream destroy, reachable from HTTP/2 teardown Signed-off-by: Tim Perry --- src/stream_pipe.cc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/stream_pipe.cc b/src/stream_pipe.cc index e626e103daf8..d070888b54e6 100644 --- a/src/stream_pipe.cc +++ b/src/stream_pipe.cc @@ -58,7 +58,8 @@ void StreamPipe::Unpipe(bool is_in_deletion) { is_closed_ = true; is_reading_ = false; - source()->RemoveStreamListener(&readable_listener_); + // Source may already be gone here during destroy + if (source() != nullptr) source()->RemoveStreamListener(&readable_listener_); if (pending_writes_ == 0 || sink_destroyed_) sink()->RemoveStreamListener(&writable_listener_); @@ -209,8 +210,14 @@ void StreamPipe::WritableListener::OnStreamAfterShutdown(ShutdownWrap* w, void StreamPipe::ReadableListener::OnStreamDestroy() { StreamPipe* pipe = ContainerOf(&StreamPipe::readable_listener_, this); pipe->source_destroyed_ = true; - if (!pipe->is_eof_) { - OnStreamRead(UV_EPIPE, uv_buf_init(nullptr, 0)); + if (pipe->is_eof_) return; + + // Mirror ReadableListener::OnStreamRead() teardown, but without + // other stream interactions: + pipe->is_eof_ = true; + if (pipe->pending_writes_ == 0) { + pipe->sink()->Shutdown(); + pipe->Unpipe(); } } @@ -227,8 +234,7 @@ void StreamPipe::WritableListener::OnStreamDestroy() { void StreamPipe::WritableListener::OnStreamWantsWrite(size_t suggested_size) { StreamPipe* pipe = ContainerOf(&StreamPipe::writable_listener_, this); pipe->wanted_data_ = suggested_size; - if (pipe->is_reading_ || pipe->is_closed_) - return; + if (pipe->is_reading_ || pipe->is_closed_ || pipe->source_destroyed_) return; HandleScope handle_scope(pipe->env()->isolate()); InternalCallbackScope callback_scope(pipe, InternalCallbackScope::kSkipTaskQueues); From da4b68425253ea10d3f055b64e05a7f317c6aa68 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Sun, 9 Aug 2026 22:38:43 +0200 Subject: [PATCH 2/2] test: avoid deadlock issue in pipeline http2 tests to fix flakiness This does not solve the remaining underlying deadlock issue, but does bound the test behaviour in a way that seems to avoid failures in practice. Deadlock fix to come separately later. Co-authored-by: Filip Skokan Signed-off-by: Tim Perry --- test/parallel/test-stream-pipeline-http2.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-stream-pipeline-http2.js b/test/parallel/test-stream-pipeline-http2.js index c35cd696bd1e..8ffee7786838 100644 --- a/test/parallel/test-stream-pipeline-http2.js +++ b/test/parallel/test-stream-pipeline-http2.js @@ -27,10 +27,12 @@ const http2 = require('http2'); client.close(); })); - let cnt = 10; + let received = 0; req.on('data', (data) => { - cnt--; - if (cnt === 0) rs.destroy(); + received += data.length; + // Bound the data that flows before teardown - bytes per data event vary + // by platform, and letting this run longer hangs on macOS. + if (received >= 32 * 1024) rs.destroy(); }); })); }