mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 23:49:45 +00:00
[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:
parent
3cb3e9a49e
commit
f0310b5b7c
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue