[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>
This commit is contained in:
Derek Xu 2023-07-10 20:33:33 +00:00 committed by Commit Queue
parent 3cb3e9a49e
commit f0310b5b7c
5 changed files with 209 additions and 98 deletions

View file

@ -96,6 +96,11 @@ OSThread::~OSThread() {
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

View file

@ -60,6 +60,7 @@ class Mutex {
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 @@ class OSThread : public BaseThread {
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 @@ class OSThread : public BaseThread {
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.

View file

@ -94,12 +94,11 @@ DEFINE_FLAG(charp,
// 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 @@ DEFINE_FLAG(charp,
// 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 @@ DEFINE_FLAG(charp,
//
// 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 @@ void Timeline::ReclaimCachedBlocksFromThreads() {
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 @@ void Timeline::Clear() {
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 @@ bool TimelineEventBlock::ContainsEventsThatCanBeSerializedLocked() const {
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 @@ TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() {
// 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 @@ TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() {
}
#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 @@ void TimelineEventRecorder::WriteTo(const char* directory) {
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 @@ void TimelineEventRecorder::WriteTo(const char* directory) {
#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(
}
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 @@ intptr_t TimelineEventFixedBufferRecorder::Size() {
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 @@ void TimelineEventFixedBufferRecorder::PrintTraceEvent(
#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 @@ void TimelineEventFixedBufferRecorder::CompleteEvent(TimelineEvent* event) {
}
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 @@ void TimelineEventPerfettoFileRecorder::DrainImpl(const TimelineEvent& event) {
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 @@ void TimelineEventEndlessRecorder::PrintTraceEvent(
#endif // !defined(PRODUCT)
TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() {
ASSERT(lock_.IsOwnedByCurrentThread());
return head_;
}
@ -2288,6 +2307,7 @@ void TimelineEventEndlessRecorder::CompleteEvent(TimelineEvent* event) {
}
TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() {
ASSERT(lock_.IsOwnedByCurrentThread());
TimelineEventBlock* block = new TimelineEventBlock(block_index_++);
block->Open();
if (head_ == nullptr) {
@ -2302,8 +2322,8 @@ TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() {
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 @@ TimelineEventBlock::TimelineEventBlock(intptr_t block_index)
: next_(nullptr),
length_(0),
block_index_(block_index),
thread_id_(OSThread::kInvalidThreadId),
current_owner_(nullptr),
in_use_(false) {}
TimelineEventBlock::~TimelineEventBlock() {
@ -2339,11 +2359,13 @@ void TimelineEventBlock::PrintJSON(JSONStream* js) const {
}
#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 @@ void TimelineEventBlock::Reset() {
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 @@ void TimelineEventBlock::Finish() {
if (FLAG_trace_timeline) {
OS::PrintErr("Finish block %p\n", this);
}
current_owner_ = nullptr;
in_use_ = false;
#ifndef PRODUCT
if (Service::timeline_stream.enabled()) {

View file

@ -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 @@ class TimelineEventBlock : public MallocAllocated {
// 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 @@ class TimelineEventBlock : public MallocAllocated {
friend class JSONStream;
private:
void Finish();
DISALLOW_COPY_AND_ASSIGN(TimelineEventBlock);
};
@ -907,7 +908,8 @@ class TimelineEventRecorder : public MallocAllocated {
#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 @@ class TimelineEventRecorder : public MallocAllocated {
// 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 @@ class TimelineEventRecorder : public MallocAllocated {
friend class TimelineStream;
friend class TimelineTestHelper;
friend class Timeline;
friend class OSThread;
private:
static constexpr intptr_t kTrackUuidToTrackMetadataInitialCapacity = 1 << 4;
@ -1010,7 +1014,7 @@ class TimelineEventFixedBufferRecorder : public TimelineEventRecorder {
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 @@ class TimelineEventCallbackRecorder : public TimelineEventRecorder {
}
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 @@ class TimelineEventEndlessRecorder : public TimelineEventRecorder {
void CompleteEvent(TimelineEvent* event);
TimelineEventBlock* GetNewBlockLocked();
TimelineEventBlock* GetHeadBlockLocked();
void Clear();
void ClearLocked();
#ifndef PRODUCT
void PrintJSONEvents(const JSONArray& array,
@ -1190,9 +1194,9 @@ class TimelineEventPlatformRecorder : public TimelineEventRecorder {
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);
};

View file

@ -47,11 +47,17 @@ class TimelineTestHelper : public AllStatic {
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 @@ class TimelineTestHelper : public AllStatic {
}
}
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 @@ class TimelineTestHelper : public AllStatic {
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 @@ class TimelineTestHelper : public AllStatic {
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 @@ class TimelineTestHelper : public AllStatic {
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 @@ TEST_CASE(TimelineRingRecorderJSONOrder) {
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 @@ TEST_CASE(TimelineRingRecorderJSONOrder) {
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.