// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "platform/assert.h" #include "vm/heap/safepoint.h" #include "vm/isolate.h" #include "vm/lockers.h" #include "vm/profiler.h" #include "vm/stack_frame.h" #include "vm/symbols.h" #include "vm/thread_pool.h" #include "vm/unit_test.h" namespace dart { VM_UNIT_TEST_CASE(Mutex) { // This unit test case needs a running isolate. TestCase::CreateTestIsolate(); Mutex* mutex = new Mutex(); mutex->Lock(); EXPECT_EQ(false, mutex->TryLock()); mutex->Unlock(); EXPECT_EQ(true, mutex->TryLock()); mutex->Unlock(); { MutexLocker ml(mutex); EXPECT_EQ(false, mutex->TryLock()); } // The isolate shutdown and the destruction of the mutex are out-of-order on // purpose. Dart_ShutdownIsolate(); delete mutex; } VM_UNIT_TEST_CASE(Monitor) { // This unit test case needs a running isolate. TestCase::CreateTestIsolate(); OSThread* thread = OSThread::Current(); // Thread interrupter interferes with this test, disable interrupts. thread->DisableThreadInterrupts(); Monitor* monitor = new Monitor(); monitor->Enter(); monitor->Exit(); EXPECT_EQ(true, monitor->TryEnter()); monitor->Exit(); const int kNumAttempts = 5; int attempts = 0; while (attempts < kNumAttempts) { MonitorLocker ml(monitor); int64_t start = OS::GetCurrentMonotonicMicros(); int64_t wait_time = 2017; Monitor::WaitResult wait_result = ml.Wait(wait_time); int64_t stop = OS::GetCurrentMonotonicMicros(); // We expect to be timing out here. EXPECT_EQ(Monitor::kTimedOut, wait_result); // Check whether this attempt falls within the expected time limits. int64_t wakeup_time = (stop - start) / kMicrosecondsPerMillisecond; OS::PrintErr("wakeup_time: %" Pd64 "\n", wakeup_time); const int kAcceptableTimeJitter = 20; // Measured in milliseconds. const int kAcceptableWakeupDelay = 150; // Measured in milliseconds. if (((wait_time - kAcceptableTimeJitter) <= wakeup_time) && (wakeup_time <= (wait_time + kAcceptableWakeupDelay))) { break; } // Record the attempt. attempts++; } EXPECT_LT(attempts, kNumAttempts); // The isolate shutdown and the destruction of the mutex are out-of-order on // purpose. Dart_ShutdownIsolate(); delete monitor; } class ObjectCounter : public ObjectPointerVisitor { public: explicit ObjectCounter(IsolateGroup* isolate_group, const Object* obj) : ObjectPointerVisitor(isolate_group), obj_(obj), count_(0) {} void VisitPointers(ObjectPtr* first, ObjectPtr* last) { for (ObjectPtr* current = first; current <= last; ++current) { if (*current == obj_->ptr()) { ++count_; } } } void VisitCompressedPointers(uword heap_base, CompressedObjectPtr* first, CompressedObjectPtr* last) { for (CompressedObjectPtr* current = first; current <= last; ++current) { if (current->Decompress(heap_base) == obj_->ptr()) { ++count_; } } } intptr_t count() const { return count_; } private: const Object* obj_; intptr_t count_; }; class TaskWithZoneAllocation : public ThreadPool::Task { public: TaskWithZoneAllocation(Isolate* isolate, Monitor* monitor, bool* done, intptr_t id) : isolate_(isolate), monitor_(monitor), done_(done), id_(id) {} virtual void Run() { Thread::EnterIsolateAsHelper(isolate_, Thread::kUnknownTask); { Thread* thread = Thread::Current(); // Create a zone (which is also a stack resource) and exercise it a bit. StackZone stack_zone(thread); Zone* zone = thread->zone(); EXPECT_EQ(zone, stack_zone.GetZone()); ZoneGrowableArray* a0 = new (zone) ZoneGrowableArray(zone, 1); GrowableArray a1(zone, 1); for (intptr_t i = 0; i < 100000; ++i) { a0->Add(true); a1.Add(true); } // Check that we can create handles and allocate in old space. String& str = String::Handle(zone, String::New("old", Heap::kOld)); EXPECT(str.Equals("old")); const intptr_t unique_smi = id_ + 928327281; Smi& smi = Smi::Handle(zone, Smi::New(unique_smi)); EXPECT(smi.Value() == unique_smi); { HeapIterationScope iteration(thread); ObjectCounter counter(isolate_->group(), &smi); // Ensure that our particular zone is visited. iteration.IterateStackPointers(&counter, ValidationPolicy::kValidateFrames); EXPECT_EQ(1, counter.count()); } char* unique_chars = zone->PrintToString("unique_str_%" Pd, id_); String& unique_str = String::Handle(zone); { // String::New may create additional handles in the topmost scope that // we don't want to count, so wrap this in its own scope. HANDLESCOPE(thread); unique_str = String::New(unique_chars, Heap::kOld); } EXPECT(unique_str.Equals(unique_chars)); { HeapIterationScope iteration(thread); ObjectCounter str_counter(isolate_->group(), &unique_str); // Ensure that our particular zone is visited. iteration.IterateStackPointers(&str_counter, ValidationPolicy::kValidateFrames); // We should visit the string object exactly once. EXPECT_EQ(1, str_counter.count()); } } Thread::ExitIsolateAsHelper(); { MonitorLocker ml(monitor_); *done_ = true; ml.Notify(); } } private: Isolate* isolate_; Monitor* monitor_; bool* done_; intptr_t id_; }; ISOLATE_UNIT_TEST_CASE(ManyTasksWithZones) { const int kTaskCount = 100; Monitor sync[kTaskCount]; bool done[kTaskCount]; Isolate* isolate = thread->isolate(); for (int i = 0; i < kTaskCount; i++) { done[i] = false; Dart::thread_pool()->Run(isolate, &sync[i], &done[i], i); } bool in_isolate = true; for (int i = 0; i < kTaskCount; i++) { // Check that main mutator thread can still freely use its own zone. String& bar = String::Handle(String::New("bar")); if (i % 10 == 0) { // Mutator thread is free to independently move in/out/between isolates. Thread::ExitIsolate(); in_isolate = false; } MonitorLocker ml(&sync[i]); while (!done[i]) { if (in_isolate) { ml.WaitWithSafepointCheck(thread); } else { ml.Wait(); } } EXPECT(done[i]); if (i % 10 == 0) { Thread::EnterIsolate(isolate); in_isolate = true; } EXPECT(bar.Equals("bar")); } } #ifndef PRODUCT class SimpleTaskWithZoneAllocation : public ThreadPool::Task { public: SimpleTaskWithZoneAllocation(intptr_t id, Isolate* isolate, Thread** thread_ptr, Monitor* sync, Monitor* monitor, intptr_t* done_count, bool* wait) : id_(id), isolate_(isolate), thread_ptr_(thread_ptr), sync_(sync), monitor_(monitor), done_count_(done_count), wait_(wait) {} virtual void Run() { Thread::EnterIsolateAsHelper(isolate_, Thread::kUnknownTask); { Thread* thread = Thread::Current(); *thread_ptr_ = thread; CreateStackZones(id_); } Thread::ExitIsolateAsHelper(); // Notify the main thread that this thread has exited. { MonitorLocker ml(monitor_); *done_count_ += 1; ml.Notify(); } } private: void CreateStackZones(intptr_t num) { Thread* thread = Thread::Current(); *thread_ptr_ = thread; StackZone stack_zone(thread); Zone* zone = thread->zone(); EXPECT_EQ(zone, stack_zone.GetZone()); // Create a zone (which is also a stack resource) and exercise it a bit. ZoneGrowableArray* a0 = new (zone) ZoneGrowableArray(zone, 1); GrowableArray a1(zone, 1); for (intptr_t i = 0; i < 1000 * num + id_; ++i) { a0->Add(true); a1.Add(true); } num -= 1; if (num != 0) { CreateStackZones(num); return; } { // Let the main thread know we're done with memory ops on this thread. MonitorLocker ml(monitor_); *done_count_ += 1; ml.Notify(); } // Wait for the go-ahead from the main thread to exit. { MonitorLocker sync_ml(sync_); while (*wait_) { sync_ml.Wait(); } } } intptr_t id_; Isolate* isolate_; Thread** thread_ptr_; Monitor* sync_; Monitor* monitor_; intptr_t* done_count_; bool* wait_; }; ISOLATE_UNIT_TEST_CASE(ManySimpleTasksWithZones) { const int kTaskCount = 10; Monitor monitor; Monitor sync; Thread* threads[kTaskCount]; Isolate* isolate = Thread::Current()->isolate(); intptr_t done_count = 0; bool wait = true; EXPECT(!thread->force_growth()); ForceGrowthScope no_heap_growth_scope(thread); for (intptr_t i = 0; i < kTaskCount; i++) { Dart::thread_pool()->Run( (i + 1), isolate, &threads[i], &sync, &monitor, &done_count, &wait); } // Wait until all spawned tasks finish their memory operations. { MonitorLocker ml(&monitor); while (done_count < kTaskCount) { ml.Wait(); } // Reset the done counter for use later. done_count = 0; } // Unblock the tasks so they can finish. { MonitorLocker sync_ml(&sync); wait = false; sync_ml.NotifyAll(); } // Now wait for them all to exit before destroying the isolate. { MonitorLocker ml(&monitor); while (done_count < kTaskCount) { ml.Wait(); } } } #endif TEST_CASE(ThreadRegistry) { Isolate* orig = Thread::Current()->isolate(); Zone* orig_zone = Thread::Current()->zone(); char* orig_str = orig_zone->PrintToString("foo"); Dart_ExitIsolate(); // Create and enter a new isolate. TestCase::CreateTestIsolate(); Zone* zone0 = Thread::Current()->zone(); EXPECT(zone0 != orig_zone); Dart_ShutdownIsolate(); // Create and enter yet another isolate. TestCase::CreateTestIsolate(); { // Create a stack resource this time, and exercise it. TransitionNativeToVM transition(Thread::Current()); StackZone stack_zone(Thread::Current()); Zone* zone1 = Thread::Current()->zone(); EXPECT(zone1 != zone0); EXPECT(zone1 != orig_zone); } Dart_ShutdownIsolate(); Dart_EnterIsolate(reinterpret_cast(orig)); // Original zone should be preserved. EXPECT_EQ(orig_zone, Thread::Current()->zone()); EXPECT_STREQ("foo", orig_str); } // A helper thread that repeatedly reads ICData class ICDataTestTask : public ThreadPool::Task { public: static const intptr_t kTaskCount; ICDataTestTask(Isolate* isolate, const Array& ic_datas, Monitor* monitor, intptr_t* exited, std::atomic* done) : isolate_(isolate), ic_datas_(ic_datas), len_(ic_datas.Length()), monitor_(monitor), exited_(exited), done_(done) {} virtual void Run() { Thread::EnterIsolateAsHelper(isolate_, Thread::kUnknownTask); Thread* thread = Thread::Current(); { StackZone stack_zone(thread); ICData& ic_data = ICData::Handle(); Array& arr = Array::Handle(); while (true) { for (intptr_t cnt = 0; cnt < 0x1000; cnt++) { for (intptr_t i = 0; i < len_; i++) { ic_data ^= ic_datas_.AtAcquire(i); arr = ic_data.entries(); intptr_t num_checks = arr.Length() / 3; if (num_checks < 0 || num_checks > 5) { OS::PrintErr("Failure: %" Pd " checks!\n", num_checks); abort(); } } } if (done_->load(std::memory_order_acquire)) { break; } TransitionVMToBlocked blocked(thread); } } Thread::ExitIsolateAsHelper(); { MonitorLocker ml(monitor_); ++*exited_; ml.Notify(); } } private: Isolate* isolate_; const Array& ic_datas_; const intptr_t len_; Monitor* monitor_; intptr_t* exited_; // # tasks that are no longer running. std::atomic* done_; // Signal that helper threads can stop working. }; static Function* CreateFunction(const char* name) { const String& class_name = String::Handle(Symbols::New(Thread::Current(), "ownerClass")); const Script& script = Script::Handle(); const Library& lib = Library::Handle(Library::New(class_name)); const Class& owner_class = Class::Handle( Class::New(lib, class_name, script, TokenPosition::kNoSource)); const String& function_name = String::ZoneHandle(Symbols::New(Thread::Current(), name)); const FunctionType& signature = FunctionType::ZoneHandle(FunctionType::New()); Function& function = Function::ZoneHandle(Function::New( signature, function_name, UntaggedFunction::kRegularFunction, true, false, false, false, false, owner_class, TokenPosition::kNoSource)); return &function; } const intptr_t ICDataTestTask::kTaskCount = 1; // Test that checks that other threads only see a fully initialized ICData // whenever ICData is updated. ISOLATE_UNIT_TEST_CASE(ICDataTest) { Isolate* isolate = thread->isolate(); USE(isolate); Monitor monitor; intptr_t exited = 0; std::atomic done = {false}; const intptr_t kNumICData = 0x10; const Array& ic_datas = Array::Handle(Array::New(kNumICData)); ICData& ic_data = ICData::Handle(); Function& owner = *CreateFunction("DummyFunction"); String& name = String::Handle(Symbols::New(thread, "foo")); const Array& args_desc = Array::Handle(ArgumentsDescriptor::NewBoxed(0, 0, Object::empty_array())); for (intptr_t i = 0; i < kNumICData; i++) { ic_data = ICData::New(owner, name, args_desc, /*deopt_id=*/0, /*num_args_tested=*/1, ICData::kInstance, Object::null_abstract_type()); ic_datas.SetAtRelease(i, ic_data); } for (int i = 0; i < ICDataTestTask::kTaskCount; i++) { Dart::thread_pool()->Run(isolate, ic_datas, &monitor, &exited, &done); } for (int i = 0; i < 0x10000; i++) { for (intptr_t i = 0; i < kNumICData; i++) { ic_data ^= ic_datas.At(i); if (ic_data.NumberOfChecks() < 4) { ic_data.AddReceiverCheck(kInstanceCid + ic_data.NumberOfChecks(), owner, 1); } else { ic_data = ICData::New(owner, name, args_desc, /*deopt_id=*/0, /*num_args_tested=*/1, ICData::kInstance, Object::null_abstract_type()); ic_datas.SetAtRelease(i, ic_data); } } } // Ensure we looped long enough to allow all helpers to succeed and exit. { done.store(true, std::memory_order_release); MonitorLocker ml(&monitor); while (exited != ICDataTestTask::kTaskCount) { ml.Wait(); } EXPECT_EQ(ICDataTestTask::kTaskCount, exited); } } // A helper thread that alternatingly cooperates and organizes // safepoint rendezvous. At rendezvous, it explicitly visits the // stacks looking for a specific marker (Smi) to verify that the expected // number threads are actually visited. The task is "done" when it has // successfully made all other tasks and the main thread rendezvous (may // not happen in the first rendezvous, since tasks are still starting up). class SafepointTestTask : public ThreadPool::Task { public: static const intptr_t kTaskCount; SafepointTestTask(Isolate* isolate, Monitor* monitor, intptr_t* expected_count, intptr_t* total_done, intptr_t* exited) : isolate_(isolate), monitor_(monitor), expected_count_(expected_count), total_done_(total_done), exited_(exited), local_done_(false) {} virtual void Run() { Thread::EnterIsolateAsHelper(isolate_, Thread::kUnknownTask); { MonitorLocker ml(monitor_); ++*expected_count_; } Thread* thread = Thread::Current(); for (int i = reinterpret_cast(thread);; ++i) { StackZone stack_zone(thread); Zone* zone = thread->zone(); const intptr_t kUniqueSmi = 928327281; Smi& smi = Smi::Handle(zone, Smi::New(kUniqueSmi)); if ((i % 100) != 0) { // Usually, we just cooperate. TransitionVMToBlocked transition(thread); } else { // But occasionally, organize a rendezvous. HeapIterationScope iteration(thread); // Establishes a safepoint. ASSERT(thread->IsAtSafepoint()); ObjectCounter counter(isolate_->group(), &smi); iteration.IterateStackPointers(&counter, ValidationPolicy::kValidateFrames); { MonitorLocker ml(monitor_); EXPECT_EQ(*expected_count_, counter.count()); } UserTag& tag = UserTag::Handle(zone, isolate_->current_tag()); if (tag.ptr() != isolate_->default_tag()) { String& label = String::Handle(zone, tag.label()); EXPECT(label.Equals("foo")); MonitorLocker ml(monitor_); if (*expected_count_ == kTaskCount && !local_done_) { // Success for the first time! Remember that we are done, and // update the total count. local_done_ = true; ++*total_done_; } } } // Check whether everyone is done. { MonitorLocker ml(monitor_); if (*total_done_ == kTaskCount) { // Another task might be at SafepointThreads when resuming. Ensure its // expectation reflects reality, since we pop our handles here. --*expected_count_; break; } } } Thread::ExitIsolateAsHelper(); { MonitorLocker ml(monitor_); ++*exited_; ml.Notify(); } } private: Isolate* isolate_; Monitor* monitor_; intptr_t* expected_count_; // # copies of kUniqueSmi we expect to visit. intptr_t* total_done_; // # tasks that successfully safepointed once. intptr_t* exited_; // # tasks that are no longer running. bool local_done_; // this task has successfully safepointed >= once. }; const intptr_t SafepointTestTask::kTaskCount = 5; // Test rendezvous of: // - helpers in VM code, // - main thread in pure Dart, // organized by // - helpers. TEST_CASE(SafepointTestDart) { Isolate* isolate = Thread::Current()->isolate(); Monitor monitor; intptr_t expected_count = 0; intptr_t total_done = 0; intptr_t exited = 0; for (int i = 0; i < SafepointTestTask::kTaskCount; i++) { Dart::thread_pool()->Run( isolate, &monitor, &expected_count, &total_done, &exited); } // Run Dart code on the main thread long enough to allow all helpers // to get their verification done and exit. Use a specific UserTag // to enable the helpers to verify that the main thread is // successfully interrupted in the pure Dart loop. #if defined(USING_SIMULATOR) const intptr_t kLoopCount = 12345678; #else const intptr_t kLoopCount = 1234567890; #endif // defined(USING_SIMULATOR) char buffer[1024]; Utils::SNPrint(buffer, sizeof(buffer), "import 'dart:developer';\n" "int dummy = 0;\n" "main() {\n" " new UserTag('foo').makeCurrent();\n" " for (dummy = 0; dummy < %" Pd "; ++dummy) {\n" " dummy += (dummy & 1);\n" " }\n" "}\n", kLoopCount); Dart_Handle lib = TestCase::LoadTestScript(buffer, NULL); EXPECT_VALID(lib); Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); EXPECT_VALID(result); // Ensure we looped long enough to allow all helpers to succeed and exit. { MonitorLocker ml(&monitor); while (exited != SafepointTestTask::kTaskCount) { ml.Wait(); } EXPECT_EQ(SafepointTestTask::kTaskCount, total_done); EXPECT_EQ(SafepointTestTask::kTaskCount, exited); } } // Test rendezvous of: // - helpers in VM code, and // - main thread in VM code, // organized by // - helpers. ISOLATE_UNIT_TEST_CASE(SafepointTestVM) { Isolate* isolate = thread->isolate(); Monitor monitor; intptr_t expected_count = 0; intptr_t total_done = 0; intptr_t exited = 0; for (int i = 0; i < SafepointTestTask::kTaskCount; i++) { Dart::thread_pool()->Run( isolate, &monitor, &expected_count, &total_done, &exited); } String& label = String::Handle(String::New("foo")); UserTag& tag = UserTag::Handle(UserTag::New(label)); isolate->set_current_tag(tag); MonitorLocker ml(&monitor); while (exited != SafepointTestTask::kTaskCount) { ml.WaitWithSafepointCheck(thread); } } // Test case for recursive safepoint operations. ISOLATE_UNIT_TEST_CASE(RecursiveSafepointTest1) { intptr_t count = 0; { GcSafepointOperationScope safepoint_scope(thread); count += 1; { GcSafepointOperationScope safepoint_scope(thread); count += 1; { GcSafepointOperationScope safepoint_scope(thread); count += 1; } } } EXPECT(count == 3); } ISOLATE_UNIT_TEST_CASE(ThreadIterator_Count) { intptr_t thread_count_0 = 0; intptr_t thread_count_1 = 0; { OSThreadIterator ti; while (ti.HasNext()) { OSThread* thread = ti.Next(); EXPECT(thread != NULL); thread_count_0++; } } { OSThreadIterator ti; while (ti.HasNext()) { OSThread* thread = ti.Next(); EXPECT(thread != NULL); thread_count_1++; } } EXPECT(thread_count_0 > 0); EXPECT(thread_count_1 > 0); EXPECT(thread_count_0 >= thread_count_1); } ISOLATE_UNIT_TEST_CASE(ThreadIterator_FindSelf) { OSThread* current = OSThread::Current(); EXPECT(OSThread::IsThreadInList(current->id())); } struct ThreadIteratorTestParams { ThreadId spawned_thread_id; ThreadJoinId spawned_thread_join_id; Monitor* monitor; }; void ThreadIteratorTestMain(uword parameter) { ThreadIteratorTestParams* params = reinterpret_cast(parameter); OSThread* thread = OSThread::Current(); EXPECT(thread != NULL); MonitorLocker ml(params->monitor); params->spawned_thread_id = thread->id(); params->spawned_thread_join_id = OSThread::GetCurrentThreadJoinId(thread); EXPECT(params->spawned_thread_id != OSThread::kInvalidThreadId); EXPECT(OSThread::IsThreadInList(thread->id())); ml.Notify(); } // NOTE: This test case also verifies that known TLS destructors are called // on Windows. See |OnDartThreadExit| in os_thread_win.cc for more details. TEST_CASE(ThreadIterator_AddFindRemove) { ThreadIteratorTestParams params; params.spawned_thread_id = OSThread::kInvalidThreadId; params.monitor = new Monitor(); { MonitorLocker ml(params.monitor); EXPECT(params.spawned_thread_id == OSThread::kInvalidThreadId); // Spawn thread and wait to receive the thread id. OSThread::Start("ThreadIteratorTest", ThreadIteratorTestMain, reinterpret_cast(¶ms)); while (params.spawned_thread_id == OSThread::kInvalidThreadId) { ml.Wait(); } EXPECT(params.spawned_thread_id != OSThread::kInvalidThreadId); EXPECT(params.spawned_thread_join_id != OSThread::kInvalidThreadJoinId); OSThread::Join(params.spawned_thread_join_id); } EXPECT(!OSThread::IsThreadInList(params.spawned_thread_id)) delete params.monitor; } // Test rendezvous of: // - helpers in VM code, and // - main thread in VM code, // organized by // - main thread, and // - helpers. ISOLATE_UNIT_TEST_CASE(SafepointTestVM2) { Isolate* isolate = thread->isolate(); Monitor monitor; intptr_t expected_count = 0; intptr_t total_done = 0; intptr_t exited = 0; for (int i = 0; i < SafepointTestTask::kTaskCount; i++) { Dart::thread_pool()->Run( isolate, &monitor, &expected_count, &total_done, &exited); } bool all_helpers = false; do { GcSafepointOperationScope safepoint_scope(thread); { MonitorLocker ml(&monitor); if (expected_count == SafepointTestTask::kTaskCount) { all_helpers = true; } } } while (!all_helpers); String& label = String::Handle(String::New("foo")); UserTag& tag = UserTag::Handle(UserTag::New(label)); isolate->set_current_tag(tag); MonitorLocker ml(&monitor); while (exited != SafepointTestTask::kTaskCount) { ml.WaitWithSafepointCheck(thread); } } // Test recursive safepoint operation scopes with other threads trying // to also start a safepoint operation scope. ISOLATE_UNIT_TEST_CASE(RecursiveSafepointTest2) { Isolate* isolate = thread->isolate(); Monitor monitor; intptr_t expected_count = 0; intptr_t total_done = 0; intptr_t exited = 0; for (int i = 0; i < SafepointTestTask::kTaskCount; i++) { Dart::thread_pool()->Run( isolate, &monitor, &expected_count, &total_done, &exited); } bool all_helpers = false; do { GcSafepointOperationScope safepoint_scope(thread); { GcSafepointOperationScope safepoint_scope(thread); MonitorLocker ml(&monitor); if (expected_count == SafepointTestTask::kTaskCount) { all_helpers = true; } } } while (!all_helpers); String& label = String::Handle(String::New("foo")); UserTag& tag = UserTag::Handle(UserTag::New(label)); isolate->set_current_tag(tag); bool all_exited = false; do { GcSafepointOperationScope safepoint_scope(thread); { GcSafepointOperationScope safepoint_scope(thread); MonitorLocker ml(&monitor); if (exited == SafepointTestTask::kTaskCount) { all_exited = true; } } } while (!all_exited); } class AllocAndGCTask : public ThreadPool::Task { public: AllocAndGCTask(Isolate* isolate, Monitor* done_monitor, bool* done) : isolate_(isolate), done_monitor_(done_monitor), done_(done) {} virtual void Run() { Thread::EnterIsolateAsHelper(isolate_, Thread::kUnknownTask); { Thread* thread = Thread::Current(); StackZone stack_zone(thread); Zone* zone = stack_zone.GetZone(); String& old_str = String::Handle(zone, String::New("old", Heap::kOld)); isolate_->group()->heap()->CollectAllGarbage(); EXPECT(old_str.Equals("old")); } Thread::ExitIsolateAsHelper(); // Tell main thread that we are ready. { MonitorLocker ml(done_monitor_); ASSERT(!*done_); *done_ = true; ml.Notify(); } } private: Isolate* isolate_; Monitor* done_monitor_; bool* done_; }; ISOLATE_UNIT_TEST_CASE(HelperAllocAndGC) { Monitor done_monitor; bool done = false; Isolate* isolate = thread->isolate(); Dart::thread_pool()->Run(isolate, &done_monitor, &done); { while (true) { TransitionVMToBlocked transition(thread); MonitorLocker ml(&done_monitor); if (done) { break; } } } } class AllocateGlobsOfMemoryTask : public ThreadPool::Task { public: AllocateGlobsOfMemoryTask(Isolate* isolate, Monitor* done_monitor, bool* done) : isolate_(isolate), done_monitor_(done_monitor), done_(done) {} virtual void Run() { Thread::EnterIsolateAsHelper(isolate_, Thread::kUnknownTask); { Thread* thread = Thread::Current(); StackZone stack_zone(thread); Zone* zone = stack_zone.GetZone(); int count = 100 * 1000; while (count-- > 0) { String::Handle(zone, String::New("abc")); } } Thread::ExitIsolateAsHelper(); // Tell main thread that we are ready. { MonitorLocker ml(done_monitor_); ASSERT(!*done_); *done_ = true; ml.Notify(); } } private: Isolate* isolate_; Monitor* done_monitor_; bool* done_; }; ISOLATE_UNIT_TEST_CASE(ExerciseTLABs) { const int NUMBER_TEST_THREADS = 10; Monitor done_monitor[NUMBER_TEST_THREADS]; bool done[NUMBER_TEST_THREADS]; Isolate* isolate = thread->isolate(); for (int i = 0; i < NUMBER_TEST_THREADS; i++) { done[i] = false; Dart::thread_pool()->Run( isolate, &done_monitor[i], &done[i]); } for (int i = 0; i < NUMBER_TEST_THREADS; i++) { MonitorLocker ml(&done_monitor[i]); while (!done[i]) { ml.WaitWithSafepointCheck(thread); } } } ISOLATE_UNIT_TEST_CASE(SafepointRwLockWithReadLock) { SafepointRwLock lock; SafepointReadRwLocker locker(Thread::Current(), &lock); DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(!lock.IsCurrentThreadWriter()); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockWithWriteLock) { SafepointRwLock lock; SafepointWriteRwLocker locker(Thread::Current(), &lock); DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(lock.IsCurrentThreadWriter()); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockWithoutAnyLocks) { SafepointRwLock lock; DEBUG_ONLY(EXPECT(!lock.IsCurrentThreadReader())); EXPECT(!lock.IsCurrentThreadWriter()); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockReentrantReadLock) { SafepointRwLock lock; { SafepointReadRwLocker locker(Thread::Current(), &lock); { SafepointReadRwLocker locker1(Thread::Current(), &lock); DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(!lock.IsCurrentThreadWriter()); } DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(!lock.IsCurrentThreadWriter()); } DEBUG_ONLY(EXPECT(!lock.IsCurrentThreadReader())); EXPECT(!lock.IsCurrentThreadWriter()); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockReentrantWriteLock) { SafepointRwLock lock; { SafepointWriteRwLocker locker(Thread::Current(), &lock); { SafepointWriteRwLocker locker1(Thread::Current(), &lock); DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(lock.IsCurrentThreadWriter()); } DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(lock.IsCurrentThreadWriter()); } DEBUG_ONLY(EXPECT(!lock.IsCurrentThreadReader())); EXPECT(!lock.IsCurrentThreadWriter()); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockWriteToReadLock) { SafepointRwLock lock; { SafepointWriteRwLocker locker(Thread::Current(), &lock); { SafepointReadRwLocker locker1(Thread::Current(), &lock); DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(lock.IsCurrentThreadWriter()); } DEBUG_ONLY(EXPECT(lock.IsCurrentThreadReader())); EXPECT(lock.IsCurrentThreadWriter()); } DEBUG_ONLY(EXPECT(!lock.IsCurrentThreadReader())); EXPECT(!lock.IsCurrentThreadWriter()); } template static void RunLockerWithLongJumpTest() { const intptr_t kNumIterations = 5; volatile intptr_t execution_count = 0; volatile intptr_t thrown_count = 0; LockType lock; for (intptr_t i = 0; i < kNumIterations; ++i) { LongJumpScope jump; if (setjmp(*jump.Set()) == 0) { LockerType locker(Thread::Current(), &lock); execution_count++; Thread::Current()->long_jump_base()->Jump( 1, Object::background_compilation_error()); } else { thrown_count++; } } EXPECT_EQ(kNumIterations, execution_count); EXPECT_EQ(kNumIterations, thrown_count); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockWriteWithLongJmp) { RunLockerWithLongJumpTest(); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockReadWithLongJmp) { RunLockerWithLongJumpTest(); } ISOLATE_UNIT_TEST_CASE(SafepointMutexLockerWithLongJmp) { RunLockerWithLongJumpTest(); } struct ReaderThreadState { ThreadJoinId reader_id = OSThread::kInvalidThreadJoinId; SafepointRwLock* rw_lock = nullptr; IsolateGroup* isolate_group = nullptr; intptr_t elapsed_us = 0; }; void Helper(uword arg) { auto state = reinterpret_cast(arg); state->reader_id = OSThread::GetCurrentThreadJoinId(OSThread::Current()); const bool kBypassSafepoint = false; Thread::EnterIsolateGroupAsHelper(state->isolate_group, Thread::kUnknownTask, kBypassSafepoint); { auto thread = Thread::Current(); const auto before_us = OS::GetCurrentMonotonicMicros(); intptr_t after_us = before_us; { SafepointReadRwLocker reader(thread, state->rw_lock); after_us = OS::GetCurrentMonotonicMicros(); } state->elapsed_us = (after_us - before_us); } Thread::ExitIsolateGroupAsHelper(kBypassSafepoint); } ISOLATE_UNIT_TEST_CASE(SafepointRwLockExclusiveNestedWriter_Regress44000) { auto isolate_group = IsolateGroup::Current(); SafepointRwLock lock; ReaderThreadState state; state.rw_lock = &lock; state.isolate_group = isolate_group; { // Hold one writer lock. SafepointWriteRwLocker locker(Thread::Current(), &lock); { // Hold another, nested, writer lock. SafepointWriteRwLocker locker2(Thread::Current(), &lock); // Start a thread, it will try to acquire read lock but it will have to // wait until we have exited both writer scopes. if (OSThread::Start("DartWorker", &Helper, reinterpret_cast(&state)) != 0) { FATAL("Could not start worker thread"); } // Give thread a little time to actually start running. OS::Sleep(20); OS::Sleep(500); } OS::Sleep(500); } // Join the other thread. OSThread::Join(state.reader_id); // Ensure the reader thread had to wait for around 1 second. EXPECT(state.elapsed_us > 2 * 500 * 1000); } ISOLATE_UNIT_TEST_CASE(SafepointMonitorUnlockScope) { // This test uses ASSERT instead of EXPECT because IsOwnedByCurrentThread is // only available in debug mode. Since our vm/cc tests run in DEBUG mode that // is sufficent for this test. Monitor monitor; { SafepointMonitorLocker ml(&monitor); ASSERT(monitor.IsOwnedByCurrentThread()); { SafepointMonitorUnlockScope ml_unlocker(&ml); ASSERT(!monitor.IsOwnedByCurrentThread()); { SafepointMonitorLocker inner_ml(&monitor); ASSERT(monitor.IsOwnedByCurrentThread()); } } } } } // namespace dart