Skip to content

Commit f55e86d

Browse files
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 owner has released it, 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: #64565 🏠 Remote-Dev: homespace
1 parent 9f04fcd commit f55e86d

2 files changed

Lines changed: 37 additions & 15 deletions

File tree

‎src/tracing/agent_perfetto.cc‎

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ void PerfettoSessionReader::Deleter::operator()(
155155
ptr->tracing_session_->FlushBlocking();
156156
ptr->Read();
157157
ptr->tracing_session_->Stop();
158+
// The reader deletes itself on the tracing loop thread once the session has
159+
// stopped and the final read has drained; see MaybeStartTeardown().
160+
Mutex::ScopedLock lock(ptr->chunks_mutex_);
161+
ptr->owner_released_ = true;
162+
uv_async_send(&ptr->read_async_);
158163
}
159164

160165
PerfettoSessionReader::PerfettoSessionReader(
@@ -201,12 +206,12 @@ void PerfettoSessionReader::Read() {
201206

202207
void PerfettoSessionReader::ReadTraceCallback(
203208
perfetto::TracingSession::ReadTraceCallbackArgs args) {
204-
// On Perfetto internal thread.
205-
{
206-
Mutex::ScopedLock lock(chunks_mutex_);
207-
if (args.size > 0)
208-
pending_chunks_.emplace_back(args.data, args.data + args.size);
209-
}
209+
// On Perfetto internal thread. Hold chunks_mutex_ across the flag update and
210+
// the wakeup so that the loop thread cannot observe read_in_progress_ ==
211+
// false and tear the reader down while this callback is still touching it.
212+
Mutex::ScopedLock lock(chunks_mutex_);
213+
if (args.size > 0)
214+
pending_chunks_.emplace_back(args.data, args.data + args.size);
210215
// A single ReadTrace() cycle can yield multiple callbacks; the last one has
211216
// has_more == false, which clears read_in_progress_ so the next timer tick
212217
// can start a new read.
@@ -215,6 +220,8 @@ void PerfettoSessionReader::ReadTraceCallback(
215220
}
216221

217222
void PerfettoSessionReader::SessionStopCallback() {
223+
// On Perfetto internal thread; same locking rationale as ReadTraceCallback.
224+
Mutex::ScopedLock lock(chunks_mutex_);
218225
stop_requested_ = true;
219226
uv_async_send(&read_async_);
220227
}
@@ -235,16 +242,27 @@ void PerfettoSessionReader::OnReadAsync(uv_async_t* async) {
235242
chunks_to_write.pop_front();
236243
}
237244

238-
if (reader->stop_requested_ && reader->handles_pending_close_ == 0) {
239-
reader->writer_->Flush(true);
245+
reader->MaybeStartTeardown();
246+
}
240247

241-
reader->handles_pending_close_ = 2;
242-
uv_timer_stop(&reader->read_timer_);
243-
uv_close(reinterpret_cast<uv_handle_t*>(&reader->read_async_),
244-
OnHandleClose);
245-
uv_close(reinterpret_cast<uv_handle_t*>(&reader->read_timer_),
246-
OnHandleClose);
248+
void PerfettoSessionReader::MaybeStartTeardown() {
249+
if (handles_pending_close_ != 0) return;
250+
{
251+
// Only tear down once the owner has let go, the session has stopped and
252+
// no ReadTrace() cycle is still delivering callbacks bound to |this|.
253+
// Perfetto always terminates a read with has_more == false, and both
254+
// callbacks signal under chunks_mutex_, so once this condition is observed
255+
// under the lock nothing on the Perfetto thread will touch |this| again.
256+
Mutex::ScopedLock lock(chunks_mutex_);
257+
if (!owner_released_ || !stop_requested_ || read_in_progress_) return;
247258
}
259+
260+
writer_->Flush(true);
261+
262+
handles_pending_close_ = 2;
263+
uv_timer_stop(&read_timer_);
264+
uv_close(reinterpret_cast<uv_handle_t*>(&read_async_), OnHandleClose);
265+
uv_close(reinterpret_cast<uv_handle_t*>(&read_timer_), OnHandleClose);
248266
}
249267

250268
// static

‎src/tracing/agent_perfetto.h‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ class TraceWriter {
3838
// Threading: two threads are involved.
3939
// - Perfetto's internal thread invokes ReadTraceCallback. It only copies the
4040
// borrowed chunk into pending_chunks_ (under chunks_mutex_) and signals
41-
// read_async_; it never touches the writer or the file.
41+
// read_async_; it never touches the writer or the file. The stop callback
42+
// runs there too. Both signal while holding chunks_mutex_ so the loop
43+
// thread can tell when no further callback will dereference the reader.
4244
// - The agent's dedicated tracing loop thread runs everything else. A
4345
// repeating timer (read_timer_) starts a read every kReadPeriodMs, and
4446
// OnReadAsync drains pending_chunks_ into the writer there. This is why the
@@ -67,6 +69,7 @@ class PerfettoSessionReader final {
6769
void ReadTraceCallback(perfetto::TracingSession::ReadTraceCallbackArgs args);
6870
void SessionStopCallback();
6971
void Read();
72+
void MaybeStartTeardown();
7073

7174
static void OnReadAsync(uv_async_t* async);
7275
static void OnReadTimer(uv_timer_t* timer);
@@ -78,6 +81,7 @@ class PerfettoSessionReader final {
7881
int handles_pending_close_ = 0;
7982
std::atomic<bool> stop_requested_ = false;
8083
std::atomic<bool> read_in_progress_ = false;
84+
bool owner_released_ = false; // Guarded by chunks_mutex_.
8185

8286
Mutex chunks_mutex_;
8387
std::list<std::vector<char>> pending_chunks_;

0 commit comments

Comments
 (0)