[VM/Timeline] Change locking scheme used by TimelineEventRingRecorder to prevent races
TEST=Loaded the timeline in DevTools on a debug build with the recorder
set to ring, endless, and startup. Recorded traces on a debug build with
the recorder set to systrace, file, and perfettofile. Ran the
vm/cc/TimelineRingRecorderRace test on ASAN and TSAN.
Issue: https://github.com/flutter/flutter/issues/129258
Change-Id: I39920ac3e4a70e5714915fcd554ae171ed82504a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/312721
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Derek Xu <derekx@google.com>
diff --git a/runtime/vm/os_thread.cc b/runtime/vm/os_thread.cc
index 6ee3aee..3536a12 100644
--- a/runtime/vm/os_thread.cc
+++ b/runtime/vm/os_thread.cc
@@ -96,6 +96,11 @@
log_ = nullptr;
#if defined(SUPPORT_TIMELINE)
if (Timeline::recorder() != nullptr) {
+ // Acquire the recorder's lock so that |timeline_block_| cannot be given to
+ // another thread until the call to |TimelineEventRecorder::FinishBlock| is
+ // complete.
+ MutexLocker recorder_lock_locker(&Timeline::recorder()->lock_);
+ MutexLocker timeline_block_lock_locker(timeline_block_lock());
Timeline::recorder()->FinishBlock(timeline_block_);
}
#endif
diff --git a/runtime/vm/os_thread.h b/runtime/vm/os_thread.h
index 2eea84b..07297ae 100644
--- a/runtime/vm/os_thread.h
+++ b/runtime/vm/os_thread.h
@@ -60,6 +60,7 @@
friend class SafepointMutexLocker;
friend class OSThreadIterator;
friend class TimelineEventRecorder;
+ friend class TimelineEventRingRecorder;
friend class PageSpace;
friend void Dart_TestMutex();
DISALLOW_COPY_AND_ASSIGN(Mutex);
@@ -111,10 +112,14 @@
Mutex* timeline_block_lock() const { return &timeline_block_lock_; }
// Only safe to access when holding |timeline_block_lock_|.
- TimelineEventBlock* timeline_block() const { return timeline_block_; }
+ TimelineEventBlock* TimelineBlockLocked() const {
+ ASSERT(timeline_block_lock()->IsOwnedByCurrentThread());
+ return timeline_block_;
+ }
// Only safe to access when holding |timeline_block_lock_|.
- void set_timeline_block(TimelineEventBlock* block) {
+ void SetTimelineBlockLocked(TimelineEventBlock* block) {
+ ASSERT(timeline_block_lock()->IsOwnedByCurrentThread());
timeline_block_ = block;
}
@@ -284,6 +289,8 @@
char* name_; // A name for this thread.
mutable Mutex timeline_block_lock_;
+ // The block that the timeline recorder has permitted this thread to write
+ // events to.
TimelineEventBlock* timeline_block_;
// All |Thread|s are registered in the thread list.
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index a9c212a..aeefc65 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -94,12 +94,11 @@
// busy if blocks are being reclaimed by the reporting system.
//
// Reporting:
-// When requested, the timeline is serialized in the trace-event format
-// (https://goo.gl/hDZw5M). The request can be for a VM-wide timeline or an
-// isolate specific timeline. In both cases it may be that a thread has
-// a |TimelineEventBlock| cached in TLS partially filled with events. In order
-// to report a complete timeline the cached |TimelineEventBlock|s need to be
-// reclaimed.
+// When requested, the timeline is serialized in either Chrome's JSON trace
+// format (https://goo.gl/hDZw5M) or Perfetto's proto trace format. In both
+// cases, it may be that a thread has a |TimelineEventBlock| cached in TLS
+// partially filled with events. In order to report a complete timeline, the
+// cached |TimelineEventBlock|s need to be reclaimed.
//
// Reclaiming open |TimelineEventBlock|s from threads:
//
@@ -108,6 +107,9 @@
// To reclaim blocks, we iterate over all threads and remove the cached
// |TimelineEventBlock| from each thread. This is safe because we hold the
// |Thread|'s |timeline_block_lock_| meaning the block can't be being modified.
+// When clearing the reclaimed blocks, or serializing the events in them, we
+// hold |TimelineEventRecorder::lock_| before reclaiming the blocks, to prevent
+// reclaimed blocks from being handed out again until we release it.
//
// Locking notes:
// The following locks are used by the timeline system:
@@ -120,8 +122,8 @@
//
// Locks must always be taken in the following order:
// |Thread::thread_list_lock_|
-// |Thread::timeline_block_lock_|
-// |TimelineEventRecorder::lock_|
+// |TimelineEventRecorder::lock_|
+// |Thread::timeline_block_lock_|
//
std::atomic<RecorderSynchronizationLock::RecorderState>
@@ -338,11 +340,8 @@
OSThread* thread = it.Next();
MutexLocker ml(thread->timeline_block_lock());
// Grab block and clear it.
- TimelineEventBlock* block = thread->timeline_block();
- thread->set_timeline_block(nullptr);
- // TODO(johnmccutchan): Consider dropping the timeline_block_lock here
- // if we can do it everywhere. This would simplify the lock ordering
- // requirements.
+ TimelineEventBlock* block = thread->TimelineBlockLocked();
+ thread->SetTimelineBlockLocked(nullptr);
recorder->FinishBlock(block);
}
}
@@ -392,8 +391,11 @@
return;
}
ASSERT(recorder != nullptr);
+ // Acquire the recorder's lock to prevent the reclaimed blocks from being
+ // handed out again until they have been cleared.
+ MutexLocker ml(&recorder->lock_);
ReclaimCachedBlocksFromThreads();
- recorder->Clear();
+ recorder->ClearLocked();
}
void TimelineEventArguments::SetNumArguments(intptr_t length) {
@@ -1248,11 +1250,6 @@
return !InUseLocked() && !IsEmpty();
}
-ThreadId TimelineEventBlock::ThreadIdLocked() const {
- ASSERT(Timeline::recorder()->lock_.IsOwnedByCurrentThread());
- return thread_id_;
-}
-
TimelineEventFilter::TimelineEventFilter(int64_t time_origin_micros,
int64_t time_extent_micros)
: time_origin_micros_(time_origin_micros),
@@ -1360,6 +1357,11 @@
// Grab the current thread.
OSThread* thread = OSThread::Current();
ASSERT(thread != nullptr);
+ // Acquire the recorder lock in case we need to call |GetNewBlockLocked|. We
+ // acquire the lock here and not directly before calls to |GetNewBlockLocked|
+ // due to locking order restrictions.
+ Mutex& recorder_lock = lock_;
+ recorder_lock.Lock();
Mutex* thread_block_lock = thread->timeline_block_lock();
ASSERT(thread_block_lock != nullptr);
// We are accessing the thread's timeline block- so take the lock here.
@@ -1372,26 +1374,34 @@
}
#endif // defined(DEBUG)
- TimelineEventBlock* thread_block = thread->timeline_block();
+ TimelineEventBlock* thread_block = thread->TimelineBlockLocked();
if ((thread_block != nullptr) && thread_block->IsFull()) {
- MutexLocker ml(&lock_);
// Thread has a block and it is full:
// 1) Mark it as finished.
- thread_block->Finish();
+ thread->SetTimelineBlockLocked(nullptr);
+ FinishBlock(thread_block);
// 2) Allocate a new block.
+ // We release |thread_block_lock| before calling |GetNewBlockLocked| to
+ // avoid TSAN warnings about lock order inversion.
+ thread_block_lock->Unlock();
thread_block = GetNewBlockLocked();
- thread->set_timeline_block(thread_block);
+ thread_block_lock->Lock();
+ thread->SetTimelineBlockLocked(thread_block);
} else if (thread_block == nullptr) {
- MutexLocker ml(&lock_);
// Thread has no block. Attempt to allocate one.
+ // We release |thread_block_lock| before calling |GetNewBlockLocked| to
+ // avoid TSAN warnings about lock order inversion.
+ thread_block_lock->Unlock();
thread_block = GetNewBlockLocked();
- thread->set_timeline_block(thread_block);
+ thread_block_lock->Lock();
+ thread->SetTimelineBlockLocked(thread_block);
}
+ recorder_lock.Unlock();
if (thread_block != nullptr) {
// NOTE: We are exiting this function with the thread's block lock held.
ASSERT(!thread_block->IsFull());
- TimelineEvent* event = thread_block->StartEvent();
+ TimelineEvent* event = thread_block->StartEventLocked();
return event;
}
// Drop lock here as no event is being handed out.
@@ -1471,6 +1481,9 @@
return;
}
+ // Acquire the recorder's lock to prevent the reclaimed blocks from being
+ // handed out again until the trace has been serialized.
+ MutexLocker ml(&lock_);
Timeline::ReclaimCachedBlocksFromThreads();
intptr_t pid = OS::ProcessId();
@@ -1501,16 +1514,9 @@
#endif
void TimelineEventRecorder::FinishBlock(TimelineEventBlock* block) {
- if (block == nullptr) {
- return;
+ if (block != nullptr) {
+ block->Finish();
}
- MutexLocker ml(&lock_);
- block->Finish();
-}
-
-TimelineEventBlock* TimelineEventRecorder::GetNewBlock() {
- MutexLocker ml(&lock_);
- return GetNewBlockLocked();
}
void TimelineEventRecorder::AddTrackMetadataBasedOnThread(
@@ -1591,11 +1597,9 @@
}
TimelineEventFixedBufferRecorder::~TimelineEventFixedBufferRecorder() {
- MutexLocker ml(&lock_);
- // Delete all blocks.
- for (intptr_t i = 0; i < num_blocks_; i++) {
- blocks_[i].Reset();
- }
+ // We do not need to acquire any locks, because at this point we must have
+ // reclaimed all the blocks, and |RecorderSynchronizationLock| must have been
+ // put in a state that prevents blocks from being given out.
delete memory_;
}
@@ -1607,8 +1611,10 @@
void TimelineEventFixedBufferRecorder::PrintEventsCommon(
const TimelineEventFilter& filter,
std::function<void(const TimelineEvent&)>&& print_impl) {
- Timeline::ReclaimCachedBlocksFromThreads();
+ // Acquire the recorder's lock to prevent the reclaimed blocks from being
+ // handed out again until the trace has been serialized.
MutexLocker ml(&lock_);
+ Timeline::ReclaimCachedBlocksFromThreads();
ResetTimeTracking();
intptr_t block_offset = FindOldestBlockIndexLocked();
if (block_offset == -1) {
@@ -1759,11 +1765,12 @@
#endif // !defined(PRODUCT)
TimelineEventBlock* TimelineEventFixedBufferRecorder::GetHeadBlockLocked() {
+ ASSERT(lock_.IsOwnedByCurrentThread());
return &blocks_[0];
}
-void TimelineEventFixedBufferRecorder::Clear() {
- MutexLocker ml(&lock_);
+void TimelineEventFixedBufferRecorder::ClearLocked() {
+ ASSERT(lock_.IsOwnedByCurrentThread());
for (intptr_t i = 0; i < num_blocks_; i++) {
TimelineEventBlock* block = &blocks_[i];
block->Reset();
@@ -1800,18 +1807,25 @@
}
TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked() {
- // TODO(johnmccutchan): This function should only hand out blocks
- // which have been marked as finished.
+ ASSERT(lock_.IsOwnedByCurrentThread());
if (block_cursor_ == num_blocks_) {
block_cursor_ = 0;
}
TimelineEventBlock* block = &blocks_[block_cursor_++];
- block->Reset();
- block->Open();
+ if (block->current_owner_ != nullptr) {
+ MutexLocker ml(block->current_owner_->timeline_block_lock());
+ block->current_owner_->SetTimelineBlockLocked(nullptr);
+ block->Reset();
+ block->Open();
+ } else {
+ block->Reset();
+ block->Open();
+ }
return block;
}
TimelineEventBlock* TimelineEventStartupRecorder::GetNewBlockLocked() {
+ ASSERT(lock_.IsOwnedByCurrentThread());
if (block_cursor_ == num_blocks_) {
return nullptr;
}
@@ -2188,14 +2202,18 @@
TimelineEventEndlessRecorder::TimelineEventEndlessRecorder()
: head_(nullptr), tail_(nullptr), block_index_(0) {}
-TimelineEventEndlessRecorder::~TimelineEventEndlessRecorder() {}
+TimelineEventEndlessRecorder::~TimelineEventEndlessRecorder() {
+ ASSERT(head_ == nullptr);
+}
#ifndef PRODUCT
void TimelineEventEndlessRecorder::PrintEventsCommon(
const TimelineEventFilter& filter,
std::function<void(const TimelineEvent&)>&& print_impl) {
- Timeline::ReclaimCachedBlocksFromThreads();
+ // Acquire the recorder's lock to prevent the reclaimed blocks from being
+ // handed out again until the trace has been serialized.
MutexLocker ml(&lock_);
+ Timeline::ReclaimCachedBlocksFromThreads();
ResetTimeTracking();
for (TimelineEventBlock* current = head_; current != nullptr;
current = current->next()) {
@@ -2273,6 +2291,7 @@
#endif // !defined(PRODUCT)
TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() {
+ ASSERT(lock_.IsOwnedByCurrentThread());
return head_;
}
@@ -2288,6 +2307,7 @@
}
TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() {
+ ASSERT(lock_.IsOwnedByCurrentThread());
TimelineEventBlock* block = new TimelineEventBlock(block_index_++);
block->Open();
if (head_ == nullptr) {
@@ -2302,8 +2322,8 @@
return block;
}
-void TimelineEventEndlessRecorder::Clear() {
- MutexLocker ml(&lock_);
+void TimelineEventEndlessRecorder::ClearLocked() {
+ ASSERT(lock_.IsOwnedByCurrentThread());
TimelineEventBlock* current = head_;
while (current != nullptr) {
TimelineEventBlock* next = current->next();
@@ -2319,7 +2339,7 @@
: next_(nullptr),
length_(0),
block_index_(block_index),
- thread_id_(OSThread::kInvalidThreadId),
+ current_owner_(nullptr),
in_use_(false) {}
TimelineEventBlock::~TimelineEventBlock() {
@@ -2339,11 +2359,13 @@
}
#endif
-TimelineEvent* TimelineEventBlock::StartEvent() {
+TimelineEvent* TimelineEventBlock::StartEventLocked() {
+ OSThread* os_thread = OSThread::Current();
+ ASSERT(os_thread != nullptr);
+ ASSERT(os_thread == current_owner_);
+ ASSERT(os_thread->timeline_block_lock()->IsOwnedByCurrentThread());
ASSERT(!IsFull());
if (FLAG_trace_timeline) {
- OSThread* os_thread = OSThread::Current();
- ASSERT(os_thread != nullptr);
intptr_t tid = OSThread::ThreadIdToIntPtr(os_thread->id());
OS::PrintErr("StartEvent in block %p for thread %" Pd "\n", this, tid);
}
@@ -2364,14 +2386,14 @@
events_[i].Reset();
}
length_ = 0;
- thread_id_ = OSThread::kInvalidThreadId;
+ current_owner_ = nullptr;
in_use_ = false;
}
void TimelineEventBlock::Open() {
OSThread* os_thread = OSThread::Current();
ASSERT(os_thread != nullptr);
- thread_id_ = os_thread->trace_id();
+ current_owner_ = os_thread;
in_use_ = true;
}
@@ -2379,6 +2401,7 @@
if (FLAG_trace_timeline) {
OS::PrintErr("Finish block %p\n", this);
}
+ current_owner_ = nullptr;
in_use_ = false;
#ifndef PRODUCT
if (Service::timeline_stream.enabled()) {
diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h
index 6c8711f..778d021 100644
--- a/runtime/vm/timeline.h
+++ b/runtime/vm/timeline.h
@@ -10,6 +10,7 @@
#include "include/dart_tools_api.h"
+#include "platform/assert.h"
#include "platform/atomic.h"
#include "platform/hashmap.h"
#include "vm/allocation.h"
@@ -815,27 +816,25 @@
// Only safe to access under the recorder's lock.
inline bool ContainsEventsThatCanBeSerializedLocked() const;
- // Only safe to access under the recorder's lock.
- inline ThreadId ThreadIdLocked() const;
-
protected:
#ifndef PRODUCT
void PrintJSON(JSONStream* stream) const;
#endif
- TimelineEvent* StartEvent();
+ // Only safe to call from the thread that owns this block, while the thread is
+ // holding its block lock.
+ TimelineEvent* StartEventLocked();
TimelineEvent events_[kBlockSize];
TimelineEventBlock* next_;
intptr_t length_;
intptr_t block_index_;
- // Only accessed under the recorder's lock.
- ThreadId thread_id_;
+ // Only safe to access under the recorder's lock.
+ OSThread* current_owner_;
bool in_use_;
void Open();
- void Finish();
friend class Thread;
friend class TimelineEventRecorder;
@@ -847,6 +846,8 @@
friend class JSONStream;
private:
+ void Finish();
+
DISALLOW_COPY_AND_ASSIGN(TimelineEventBlock);
};
@@ -907,7 +908,8 @@
#endif // !defined(PRODUCT)
virtual const char* name() const = 0;
virtual intptr_t Size() = 0;
- TimelineEventBlock* GetNewBlock();
+ // Only safe to call when holding |lock_|.
+ virtual TimelineEventBlock* GetNewBlockLocked() = 0;
void FinishBlock(TimelineEventBlock* block);
// This function must be called at least once for each thread that corresponds
// to a track in the trace.
@@ -936,9 +938,10 @@
// Interface method(s) which must be implemented.
virtual TimelineEvent* StartEvent() = 0;
virtual void CompleteEvent(TimelineEvent* event) = 0;
+ // Only safe to call when holding |lock_|.
virtual TimelineEventBlock* GetHeadBlockLocked() = 0;
- virtual TimelineEventBlock* GetNewBlockLocked() = 0;
- virtual void Clear() = 0;
+ // Only safe to call when holding |lock_|.
+ virtual void ClearLocked() = 0;
// Utility method(s).
#ifndef PRODUCT
@@ -968,6 +971,7 @@
friend class TimelineStream;
friend class TimelineTestHelper;
friend class Timeline;
+ friend class OSThread;
private:
static constexpr intptr_t kTrackUuidToTrackMetadataInitialCapacity = 1 << 4;
@@ -1010,7 +1014,7 @@
TimelineEventBlock* GetHeadBlockLocked();
// Only safe to call when holding |lock_|.
intptr_t FindOldestBlockIndexLocked() const;
- void Clear();
+ void ClearLocked();
#ifndef PRODUCT
void PrintJSONEvents(const JSONArray& array,
@@ -1089,9 +1093,9 @@
}
protected:
- TimelineEventBlock* GetNewBlockLocked() { return nullptr; }
- TimelineEventBlock* GetHeadBlockLocked() { return nullptr; }
- void Clear() {}
+ TimelineEventBlock* GetNewBlockLocked() { UNREACHABLE(); }
+ TimelineEventBlock* GetHeadBlockLocked() { UNREACHABLE(); }
+ void ClearLocked() { ASSERT(lock_.IsOwnedByCurrentThread()); }
TimelineEvent* StartEvent();
void CompleteEvent(TimelineEvent* event);
};
@@ -1141,7 +1145,7 @@
void CompleteEvent(TimelineEvent* event);
TimelineEventBlock* GetNewBlockLocked();
TimelineEventBlock* GetHeadBlockLocked();
- void Clear();
+ void ClearLocked();
#ifndef PRODUCT
void PrintJSONEvents(const JSONArray& array,
@@ -1190,9 +1194,9 @@
virtual const char* name() const = 0;
protected:
- TimelineEventBlock* GetNewBlockLocked() { return nullptr; }
- TimelineEventBlock* GetHeadBlockLocked() { return nullptr; }
- void Clear() {}
+ TimelineEventBlock* GetNewBlockLocked() { UNREACHABLE(); }
+ TimelineEventBlock* GetHeadBlockLocked() { UNREACHABLE(); }
+ void ClearLocked() { ASSERT(lock_.IsOwnedByCurrentThread()); }
TimelineEvent* StartEvent();
void CompleteEvent(TimelineEvent* event);
};
diff --git a/runtime/vm/timeline_test.cc b/runtime/vm/timeline_test.cc
index 8d768ff..cce7fbb 100644
--- a/runtime/vm/timeline_test.cc
+++ b/runtime/vm/timeline_test.cc
@@ -47,11 +47,17 @@
event->StreamInit(stream);
}
+ static Mutex& GetRecorderLock(TimelineEventRecorder& recorder) {
+ return recorder.lock_;
+ }
+
static void FakeThreadEvent(TimelineEventBlock* block,
intptr_t ftid,
const char* label = "fake",
TimelineStream* stream = nullptr) {
- TimelineEvent* event = block->StartEvent();
+ OSThread& current_thread = *OSThread::Current();
+ MutexLocker ml(current_thread.timeline_block_lock());
+ TimelineEvent* event = block->StartEventLocked();
ASSERT(event != nullptr);
event->DurationBegin(label);
event->thread_ = OSThread::ThreadIdFromIntPtr(ftid);
@@ -60,10 +66,6 @@
}
}
- static void SetBlockThread(TimelineEventBlock* block, intptr_t ftid) {
- block->thread_id_ = OSThread::ThreadIdFromIntPtr(ftid);
- }
-
static void FakeDuration(TimelineEventRecorder* recorder,
const char* label,
int64_t start,
@@ -74,7 +76,7 @@
TimelineEvent* event = recorder->StartEvent();
ASSERT(event != nullptr);
event->Duration(label, start, end);
- event->Complete();
+ recorder->CompleteEvent(event);
}
static void FakeBegin(TimelineEventRecorder* recorder,
@@ -86,7 +88,7 @@
TimelineEvent* event = recorder->StartEvent();
ASSERT(event != nullptr);
event->Begin(label, /*id=*/-1, start);
- event->Complete();
+ recorder->CompleteEvent(event);
}
static void FakeEnd(TimelineEventRecorder* recorder,
@@ -98,7 +100,7 @@
TimelineEvent* event = recorder->StartEvent();
ASSERT(event != nullptr);
event->End(label, end);
- event->Complete();
+ recorder->CompleteEvent(event);
}
static void FinishBlock(TimelineEventBlock* block) { block->Finish(); }
@@ -346,21 +348,25 @@
new TimelineEventRingRecorder(TimelineEventBlock::kBlockSize * 2);
TimelineRecorderOverride<TimelineEventRingRecorder> override(recorder);
- TimelineEventBlock* block_0 = Timeline::recorder()->GetNewBlock();
- EXPECT(block_0 != nullptr);
- TimelineEventBlock* block_1 = Timeline::recorder()->GetNewBlock();
- EXPECT(block_1 != nullptr);
- // Test that we wrapped.
- EXPECT(block_0 == Timeline::recorder()->GetNewBlock());
+ {
+ Mutex& recorder_lock = TimelineTestHelper::GetRecorderLock(*recorder);
+ MutexLocker ml(&recorder_lock);
+ TimelineEventBlock* block_0 = Timeline::recorder()->GetNewBlockLocked();
+ EXPECT(block_0 != nullptr);
+ TimelineEventBlock* block_1 = Timeline::recorder()->GetNewBlockLocked();
+ EXPECT(block_1 != nullptr);
+ // Test that we wrapped.
+ EXPECT(block_0 == Timeline::recorder()->GetNewBlockLocked());
- // Emit the earlier event into block_1.
- TimelineTestHelper::FakeThreadEvent(block_1, 2, "Alpha", &stream);
- OS::Sleep(32);
- // Emit the later event into block_0.
- TimelineTestHelper::FakeThreadEvent(block_0, 2, "Beta", &stream);
+ // Emit the earlier event into block_1.
+ TimelineTestHelper::FakeThreadEvent(block_1, 2, "Alpha", &stream);
+ OS::Sleep(32);
+ // Emit the later event into block_0.
+ TimelineTestHelper::FakeThreadEvent(block_0, 2, "Beta", &stream);
- TimelineTestHelper::FinishBlock(block_0);
- TimelineTestHelper::FinishBlock(block_1);
+ TimelineTestHelper::FinishBlock(block_0);
+ TimelineTestHelper::FinishBlock(block_1);
+ }
JSONStream js;
TimelineEventFilter filter;
@@ -374,6 +380,72 @@
EXPECT(alpha < beta);
}
+TEST_CASE(TimelineRingRecorderRace) {
+ struct ReportEventsArguments {
+ Monitor& synchronization_monitor;
+ TimelineEventRecorder& recorder;
+ ThreadJoinId join_id = OSThread::kInvalidThreadJoinId;
+ };
+
+ // Note that |recorder| will be freed by |TimelineRecorderOverride|'s
+ // destructor.
+ TimelineEventRingRecorder& recorder =
+ *(new TimelineEventRingRecorder(2 * TimelineEventBlock::kBlockSize));
+ TimelineRecorderOverride<TimelineEventRingRecorder> override(&recorder);
+ Monitor synchronization_monitor;
+ JSONStream js;
+ TimelineEventFilter filter;
+ ReportEventsArguments report_events_1_arguments{synchronization_monitor,
+ recorder};
+ ReportEventsArguments report_events_2_arguments{synchronization_monitor,
+ recorder};
+
+ // Try concurrently writing events, serializing them, and clearing the
+ // timeline. It is not possible to assert anything about the outcome, because
+ // of scheduling uncertainty. This test is just used to ensure that TSAN
+ // checks the ring recorder code.
+ OSThread::Start(
+ "ReportEvents1",
+ [](uword arguments_ptr) {
+ ReportEventsArguments& arguments =
+ *reinterpret_cast<ReportEventsArguments*>(arguments_ptr);
+ for (intptr_t i = 0; i < 2 * TimelineEventBlock::kBlockSize; ++i) {
+ TimelineTestHelper::FakeDuration(&arguments.recorder, "testEvent",
+ /*start=*/0, /*end=*/1);
+ }
+ MonitorLocker ml(&arguments.synchronization_monitor);
+ arguments.join_id =
+ OSThread::GetCurrentThreadJoinId(OSThread::Current());
+ ml.Notify();
+ },
+ reinterpret_cast<uword>(&report_events_1_arguments));
+ OSThread::Start(
+ "ReportEvents2",
+ [](uword arguments_ptr) {
+ ReportEventsArguments& arguments =
+ *reinterpret_cast<ReportEventsArguments*>(arguments_ptr);
+ for (intptr_t i = 0; i < 2 * TimelineEventBlock::kBlockSize; ++i) {
+ TimelineTestHelper::FakeDuration(&arguments.recorder, "testEvent",
+ /*start=*/0, /*end=*/1);
+ }
+ MonitorLocker ml(&arguments.synchronization_monitor);
+ arguments.join_id =
+ OSThread::GetCurrentThreadJoinId(OSThread::Current());
+ ml.Notify();
+ },
+ reinterpret_cast<uword>(&report_events_2_arguments));
+ Timeline::Clear();
+ recorder.PrintJSON(&js, &filter);
+
+ MonitorLocker ml(&synchronization_monitor);
+ while (report_events_1_arguments.join_id == OSThread::kInvalidThreadJoinId ||
+ report_events_2_arguments.join_id == OSThread::kInvalidThreadJoinId) {
+ ml.Wait();
+ }
+ OSThread::Join(report_events_1_arguments.join_id);
+ OSThread::Join(report_events_2_arguments.join_id);
+}
+
// |OSThread::Start()| takes in a function pointer, and only lambdas that don't
// capture can be converted to function pointers. So, we use these macros to
// avoid needing to capture.