From 05ad85d208e64037bd61995f4ba62eac405d4c8c Mon Sep 17 00:00:00 2001 From: Sam Attard Date: Fri, 28 Aug 2026 07:41:30 +0000 Subject: [PATCH] src: fix perfetto session reader teardown race PerfettoSessionReader::Deleter issues a final ReadTrace() and then Stop()s the session. Perfetto delivers the read data and the stop notification as independent tasks on its own thread, so the stop could win, close the uv handles and delete the reader while a ReadTraceCallback bound to the raw pointer was still queued. That callback then locked a destroyed mutex and signalled a closed uv_async_t. Only tear the reader down once the session has stopped and no read is in flight, and have both Perfetto-thread callbacks update their flag and signal under chunks_mutex_ so the loop thread cannot free the reader in between. Refs: https://github.com/nodejs/node/pull/64565 --- src/tracing/agent_perfetto.cc | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/tracing/agent_perfetto.cc b/src/tracing/agent_perfetto.cc index 289b73e8c0ff..cf63584de0f6 100644 --- a/src/tracing/agent_perfetto.cc +++ b/src/tracing/agent_perfetto.cc @@ -201,12 +201,11 @@ void PerfettoSessionReader::Read() { void PerfettoSessionReader::ReadTraceCallback( perfetto::TracingSession::ReadTraceCallbackArgs args) { - // On Perfetto internal thread. - { - Mutex::ScopedLock lock(chunks_mutex_); - if (args.size > 0) - pending_chunks_.emplace_back(args.data, args.data + args.size); - } + // On Perfetto internal thread. Signal under the lock so OnReadAsync() cannot + // free |this| while a callback is still running. + Mutex::ScopedLock lock(chunks_mutex_); + if (args.size > 0) + pending_chunks_.emplace_back(args.data, args.data + args.size); // A single ReadTrace() cycle can yield multiple callbacks; the last one has // has_more == false, which clears read_in_progress_ so the next timer tick // can start a new read. @@ -215,6 +214,8 @@ void PerfettoSessionReader::ReadTraceCallback( } void PerfettoSessionReader::SessionStopCallback() { + // On Perfetto internal thread. + Mutex::ScopedLock lock(chunks_mutex_); stop_requested_ = true; uv_async_send(&read_async_); } @@ -224,9 +225,12 @@ void PerfettoSessionReader::OnReadAsync(uv_async_t* async) { PerfettoSessionReader* reader = static_cast(async->data); std::list> chunks_to_write; + // Shutdown requested and no read outstanding. + bool should_tear_down = false; { Mutex::ScopedLock lock(reader->chunks_mutex_); std::swap(chunks_to_write, reader->pending_chunks_); + should_tear_down = reader->stop_requested_ && !reader->read_in_progress_; } while (!chunks_to_write.empty()) { @@ -235,16 +239,14 @@ void PerfettoSessionReader::OnReadAsync(uv_async_t* async) { chunks_to_write.pop_front(); } - if (reader->stop_requested_ && reader->handles_pending_close_ == 0) { - reader->writer_->Flush(true); + if (!should_tear_down || reader->handles_pending_close_ != 0) return; - reader->handles_pending_close_ = 2; - uv_timer_stop(&reader->read_timer_); - uv_close(reinterpret_cast(&reader->read_async_), - OnHandleClose); - uv_close(reinterpret_cast(&reader->read_timer_), - OnHandleClose); - } + reader->writer_->Flush(true); + + reader->handles_pending_close_ = 2; + uv_timer_stop(&reader->read_timer_); + uv_close(reinterpret_cast(&reader->read_async_), OnHandleClose); + uv_close(reinterpret_cast(&reader->read_timer_), OnHandleClose); } // static