[vm, mac] Disable TSAN during profiler sample collection.
The suspended thread might hold a TSAN-internal lock to a location that will be read by the sampling thread, which would cause a dead lock. TEST=tsan Bug: https://github.com/dart-lang/sdk/issues/62332 Change-Id: I0ac6a05c25067749b52ed83b14f80ab1850013a3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/471321 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
This commit is contained in:
committed by
Commit Queue
parent
61c3fbc220
commit
894a5fa7d5
@@ -5,6 +5,7 @@
|
||||
#ifndef RUNTIME_PLATFORM_THREAD_SANITIZER_H_
|
||||
#define RUNTIME_PLATFORM_THREAD_SANITIZER_H_
|
||||
|
||||
#include "platform/allocation.h"
|
||||
#include "platform/globals.h"
|
||||
|
||||
#if __SANITIZE_THREAD__
|
||||
@@ -54,6 +55,8 @@ extern "C" void __tsan_write8_pc(void* addr, void* pc);
|
||||
extern "C" void __tsan_write16_pc(void* addr, void* pc);
|
||||
extern "C" void __tsan_func_entry(void* pc);
|
||||
extern "C" void __tsan_func_exit();
|
||||
extern "C" void __tsan_ignore_thread_begin();
|
||||
extern "C" void __tsan_ignore_thread_end();
|
||||
constexpr uintptr_t kExternalPCBit = 1ULL << 60;
|
||||
#else
|
||||
#define NO_SANITIZE_THREAD
|
||||
@@ -72,4 +75,22 @@ constexpr uintptr_t kExternalPCBit = 1ULL << 60;
|
||||
#define DO_IF_NOT_TSAN(CODE) CODE
|
||||
#endif
|
||||
|
||||
namespace dart {
|
||||
|
||||
class TsanIgnoreScope : public ValueObject {
|
||||
public:
|
||||
TsanIgnoreScope() {
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
__tsan_ignore_thread_begin();
|
||||
#endif
|
||||
}
|
||||
~TsanIgnoreScope() {
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
__tsan_ignore_thread_end();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dart
|
||||
|
||||
#endif // RUNTIME_PLATFORM_THREAD_SANITIZER_H_
|
||||
|
||||
+14
-32
@@ -199,25 +199,10 @@ class ProfilerStackWalker : public ValueObject {
|
||||
// MSAN/ASAN are unaware of frames initialized by generated code.
|
||||
NO_SANITIZE_ADDRESS
|
||||
NO_SANITIZE_MEMORY
|
||||
#if defined(DART_HOST_OS_MACOS)
|
||||
// Mac profiling is cross-thread and TSAN doesn't know that thread_suspend
|
||||
// establishes synchronization.
|
||||
NO_SANITIZE_THREAD
|
||||
#endif
|
||||
static uword* LoadStackSlot(uword* ptr) {
|
||||
return reinterpret_cast<uword*>(*ptr);
|
||||
}
|
||||
|
||||
#if defined(DART_HOST_OS_MACOS)
|
||||
// Mac profiling is cross-thread and TSAN doesn't know that thread_suspend
|
||||
// establishes synchronization.
|
||||
#define IGNORE_RACE(x) x##_ignore_race
|
||||
#define IGNORE_RACE2(x) x##IgnoreRace
|
||||
#else
|
||||
#define IGNORE_RACE(x) x
|
||||
#define IGNORE_RACE2(x) x
|
||||
#endif
|
||||
|
||||
// The layout of C stack frames.
|
||||
#if defined(HOST_ARCH_IA32) || defined(HOST_ARCH_X64) || \
|
||||
defined(HOST_ARCH_ARM) || defined(HOST_ARCH_ARM64)
|
||||
@@ -388,8 +373,7 @@ static bool GetAndValidateThreadStackBounds(OSThread* os_thread,
|
||||
|
||||
#if defined(DART_INCLUDE_SIMULATOR)
|
||||
const bool use_simulator_stack_bounds =
|
||||
FLAG_use_simulator && thread != nullptr &&
|
||||
thread->IGNORE_RACE2(IsExecutingDartCode)();
|
||||
FLAG_use_simulator && thread != nullptr && thread->IsExecutingDartCode();
|
||||
if (use_simulator_stack_bounds) {
|
||||
Isolate* isolate = thread->isolate();
|
||||
ASSERT(isolate != nullptr);
|
||||
@@ -1073,13 +1057,12 @@ class ProfilerDartStackWalker : public ProfilerStackWalker {
|
||||
|
||||
void walk() {
|
||||
RELEASE_ASSERT(StubCode::HasBeenInitialized());
|
||||
if (thread_->IGNORE_RACE2(IsDeoptimizing)()) {
|
||||
if (thread_->IsDeoptimizing()) {
|
||||
sample_->set_ignore_sample(true);
|
||||
return;
|
||||
}
|
||||
|
||||
uword* exit_fp =
|
||||
reinterpret_cast<uword*>(thread_->IGNORE_RACE(top_exit_frame_info)());
|
||||
uword* exit_fp = reinterpret_cast<uword*>(thread_->top_exit_frame_info());
|
||||
bool has_exit_frame = exit_fp != nullptr;
|
||||
if (has_exit_frame) {
|
||||
// Exited from compiled code or interpreter.
|
||||
@@ -1090,14 +1073,13 @@ class ProfilerDartStackWalker : public ProfilerStackWalker {
|
||||
pc_ = CallerPC();
|
||||
fp_ = CallerFP();
|
||||
} else {
|
||||
if (thread_->IGNORE_RACE(vm_tag)() == VMTag::kDartTagId) {
|
||||
if (thread_->vm_tag() == VMTag::kDartTagId) {
|
||||
// Running compiled code.
|
||||
// Use the FP and PC from the thread interrupt or simulator; already set
|
||||
// in the constructor.
|
||||
|
||||
#if defined(DART_DYNAMIC_MODULES)
|
||||
} else if (thread_->IGNORE_RACE(vm_tag)() ==
|
||||
VMTag::kDartInterpretedTagId) {
|
||||
} else if (thread_->vm_tag() == VMTag::kDartInterpretedTagId) {
|
||||
// Running interpreter.
|
||||
pc_ = reinterpret_cast<uword*>(thread_->interpreter()->get_pc());
|
||||
fp_ = reinterpret_cast<uword*>(thread_->interpreter()->get_fp());
|
||||
@@ -1300,7 +1282,7 @@ static Sample* SetupSample(Thread* thread,
|
||||
bool allocation_sample,
|
||||
ThreadId tid) {
|
||||
ASSERT(thread != nullptr);
|
||||
Isolate* isolate = thread->IGNORE_RACE(isolate)();
|
||||
Isolate* isolate = thread->isolate();
|
||||
SampleBlockBuffer* buffer = Profiler::sample_block_buffer();
|
||||
Sample* sample = allocation_sample ? buffer->ReserveAllocationSample(isolate)
|
||||
: buffer->ReserveCPUSample(isolate);
|
||||
@@ -1308,7 +1290,7 @@ static Sample* SetupSample(Thread* thread,
|
||||
return nullptr;
|
||||
}
|
||||
sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(), tid);
|
||||
uword vm_tag = thread->IGNORE_RACE(vm_tag)();
|
||||
uword vm_tag = thread->vm_tag();
|
||||
#if defined(DART_INCLUDE_SIMULATOR)
|
||||
// When running in the simulator, the runtime entry function address
|
||||
// (stored as the vm tag) is the address of a redirect function.
|
||||
@@ -1321,7 +1303,7 @@ static Sample* SetupSample(Thread* thread,
|
||||
}
|
||||
#endif
|
||||
sample->set_vm_tag(vm_tag);
|
||||
sample->set_user_tag(thread->IGNORE_RACE(user_tag)());
|
||||
sample->set_user_tag(thread->user_tag());
|
||||
sample->set_thread_task(thread->task_kind());
|
||||
return sample;
|
||||
}
|
||||
@@ -1405,7 +1387,7 @@ void Profiler::SampleThreadSingleFrame(Thread* thread,
|
||||
ASSERT(Profiler::sample_block_buffer() != nullptr);
|
||||
|
||||
#if !defined(PRODUCT)
|
||||
Isolate* isolate = thread->IGNORE_RACE(isolate)();
|
||||
Isolate* isolate = thread->isolate();
|
||||
|
||||
// Increment counter for vm tag.
|
||||
VMTagCounters* counters = isolate->vm_tag_counters();
|
||||
@@ -1441,9 +1423,9 @@ void ReleaseToCurrentBlock(Isolate* isolate) {
|
||||
void Profiler::SampleThread(Thread* thread,
|
||||
const InterruptedThreadState& state) {
|
||||
ASSERT(thread != nullptr);
|
||||
OSThread* os_thread = thread->IGNORE_RACE(os_thread)();
|
||||
OSThread* os_thread = thread->os_thread();
|
||||
ASSERT(os_thread != nullptr);
|
||||
Isolate* isolate = thread->IGNORE_RACE(isolate)();
|
||||
Isolate* isolate = thread->isolate();
|
||||
|
||||
// Double check if interrupts are disabled
|
||||
// after the thread interrupter decided to send a signal.
|
||||
@@ -1465,7 +1447,7 @@ void Profiler::SampleThread(Thread* thread,
|
||||
return;
|
||||
}
|
||||
|
||||
const bool in_dart_code = thread->IGNORE_RACE2(IsExecutingDartCode)();
|
||||
const bool in_dart_code = thread->IsExecutingDartCode();
|
||||
|
||||
uintptr_t sp = 0;
|
||||
uintptr_t fp = state.fp;
|
||||
@@ -1513,7 +1495,7 @@ void Profiler::SampleThread(Thread* thread,
|
||||
}
|
||||
|
||||
if (thread->IsDartMutatorThread()) {
|
||||
if (thread->IGNORE_RACE2(IsDeoptimizing)()) {
|
||||
if (thread->IsDeoptimizing()) {
|
||||
counters_.single_frame_sample_deoptimizing.fetch_add(1);
|
||||
SampleThreadSingleFrame(thread, sample, pc);
|
||||
ReleaseToCurrentBlock(isolate);
|
||||
@@ -1547,7 +1529,7 @@ void Profiler::SampleThread(Thread* thread,
|
||||
Dart_Port port = (isolate != nullptr) ? isolate->main_port() : ILLEGAL_PORT;
|
||||
ProfilerNativeStackWalker native_stack_walker(
|
||||
&counters_, port, sample, isolate, stack_lower, stack_upper, pc, fp, sp);
|
||||
const bool exited_dart_code = thread->IGNORE_RACE2(HasExitedDartCode)();
|
||||
const bool exited_dart_code = thread->HasExitedDartCode();
|
||||
ProfilerDartStackWalker dart_stack_walker(thread, port, sample, isolate, pc,
|
||||
fp, sp, lr,
|
||||
/*allocation_sample=*/false);
|
||||
|
||||
@@ -2461,6 +2461,20 @@ ISOLATE_UNIT_TEST_CASE(Profiler_EnterExitIsolate) {
|
||||
}
|
||||
}
|
||||
|
||||
// Poke a lot at OSThread::thread_interrupt_disabled_, which will be read by the
|
||||
// sampling thread.
|
||||
// https://github.com/dart-lang/sdk/issues/62332
|
||||
ISOLATE_UNIT_TEST_CASE(Profiler_ThreadEnableDisableProfiler) {
|
||||
EnableProfiler();
|
||||
Profiler::UpdateFlagProfilePeriod(50); // Microseconds.
|
||||
Profiler::UpdateSamplePeriod();
|
||||
|
||||
for (intptr_t i = 0; i < 100000; i++) {
|
||||
Dart_ThreadDisableProfiling();
|
||||
Dart_ThreadEnableProfiling();
|
||||
}
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(Profiler_UpdateRunningState) {
|
||||
Isolate* isolate = Isolate::Current();
|
||||
SampleFilter filter(isolate->main_port(), Thread::kMutatorTask, -1, -1);
|
||||
|
||||
@@ -1056,20 +1056,10 @@ bool Thread::IsExecutingDartCode() const {
|
||||
return (top_exit_frame_info() == 0) && VMTag::IsDartTag(vm_tag());
|
||||
}
|
||||
|
||||
bool Thread::IsExecutingDartCodeIgnoreRace() const {
|
||||
return (top_exit_frame_info_ignore_race() == 0) &&
|
||||
VMTag::IsDartTag(vm_tag_ignore_race());
|
||||
}
|
||||
|
||||
bool Thread::HasExitedDartCode() const {
|
||||
return (top_exit_frame_info() != 0) && !VMTag::IsDartTag(vm_tag());
|
||||
}
|
||||
|
||||
bool Thread::HasExitedDartCodeIgnoreRace() const {
|
||||
return (top_exit_frame_info_ignore_race() != 0) &&
|
||||
!VMTag::IsDartTag(vm_tag_ignore_race());
|
||||
}
|
||||
|
||||
template <class C>
|
||||
C* Thread::AllocateReusableHandle() {
|
||||
C* handle = reinterpret_cast<C*>(reusable_handles_.AllocateScopedHandle());
|
||||
|
||||
@@ -580,8 +580,6 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
|
||||
|
||||
// The isolate that this thread is operating on, or nullptr if none.
|
||||
Isolate* isolate() const { return isolate_; }
|
||||
NO_SANITIZE_THREAD
|
||||
Isolate* isolate_ignore_race() const { return isolate_; }
|
||||
static intptr_t isolate_offset() { return OFFSET_OF(Thread, isolate_); }
|
||||
static intptr_t isolate_group_offset() {
|
||||
return OFFSET_OF(Thread, isolate_group_);
|
||||
@@ -637,11 +635,9 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
|
||||
|
||||
// Is |this| executing Dart code?
|
||||
bool IsExecutingDartCode() const;
|
||||
bool IsExecutingDartCodeIgnoreRace() const;
|
||||
|
||||
// Has |this| exited Dart code?
|
||||
bool HasExitedDartCode() const;
|
||||
bool HasExitedDartCodeIgnoreRace() const;
|
||||
|
||||
bool HasCompilerState() const { return compiler_state_ != nullptr; }
|
||||
|
||||
@@ -749,8 +745,6 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
|
||||
}
|
||||
|
||||
uword top_exit_frame_info() const { return top_exit_frame_info_; }
|
||||
NO_SANITIZE_THREAD
|
||||
uword top_exit_frame_info_ignore_race() const { return top_exit_frame_info_; }
|
||||
void set_top_exit_frame_info(uword top_exit_frame_info) {
|
||||
top_exit_frame_info_ = top_exit_frame_info;
|
||||
}
|
||||
@@ -881,8 +875,6 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
|
||||
#endif
|
||||
|
||||
uword vm_tag() const { return vm_tag_; }
|
||||
NO_SANITIZE_THREAD
|
||||
uword vm_tag_ignore_race() const { return vm_tag_; }
|
||||
void set_vm_tag(uword tag) { vm_tag_ = tag; }
|
||||
static intptr_t vm_tag_offset() { return OFFSET_OF(Thread, vm_tag_); }
|
||||
|
||||
@@ -1352,8 +1344,6 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
|
||||
}
|
||||
|
||||
bool IsDeoptimizing() const { return deopt_context_ != nullptr; }
|
||||
NO_SANITIZE_THREAD
|
||||
bool IsDeoptimizingIgnoreRace() const { return deopt_context_ != nullptr; }
|
||||
DeoptContext* deopt_context() const { return deopt_context_; }
|
||||
void set_deopt_context(DeoptContext* value) {
|
||||
ASSERT(value == nullptr || deopt_context_ == nullptr);
|
||||
@@ -1372,8 +1362,6 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
|
||||
}
|
||||
|
||||
uword user_tag() const { return user_tag_; }
|
||||
NO_SANITIZE_THREAD
|
||||
uword user_tag_ignore_race() const { return user_tag_; }
|
||||
static intptr_t user_tag_offset() { return OFFSET_OF(Thread, user_tag_); }
|
||||
static intptr_t current_tag_offset() {
|
||||
return OFFSET_OF(Thread, current_tag_);
|
||||
|
||||
@@ -76,6 +76,11 @@ class ThreadInterrupterMacOS {
|
||||
return;
|
||||
}
|
||||
ThreadInterruptScope signal_handler_scope;
|
||||
// Mac profiling is cross-thread and TSAN doesn't know that thread_suspend
|
||||
// establishes synchronization. Also, the suspended thread might hold a
|
||||
// TSAN-internal lock for a location the sampling thread might read, which
|
||||
// would cause a deadlock.
|
||||
TsanIgnoreScope tsan_ignore_scope;
|
||||
Profiler::SampleThread(thread, ProcessState(state));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@ class ThreadState : public BaseThread {
|
||||
|
||||
// OSThread corresponding to this thread.
|
||||
OSThread* os_thread() const { return os_thread_; }
|
||||
NO_SANITIZE_THREAD
|
||||
OSThread* os_thread_ignore_race() const { return os_thread_; }
|
||||
void set_os_thread(OSThread* os_thread) { os_thread_ = os_thread; }
|
||||
|
||||
// The topmost zone used for allocation in this thread.
|
||||
|
||||
Reference in New Issue
Block a user