| // 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; |
| } |
| |
| #if !defined(PRODUCT) |
| 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; |
| } |
| #endif |
| |
| 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) override { |
| for (ObjectPtr* current = first; current <= last; ++current) { |
| if (*current == obj_->ptr()) { |
| ++count_; |
| } |
| } |
| } |
| |
| #if defined(DART_COMPRESSED_POINTERS) |
| void VisitCompressedPointers(uword heap_base, |
| CompressedObjectPtr* first, |
| CompressedObjectPtr* last) override { |
| for (CompressedObjectPtr* current = first; current <= last; ++current) { |
| if (current->Decompress(heap_base) == obj_->ptr()) { |
| ++count_; |
| } |
| } |
| } |
| #endif |
| |
| intptr_t count() const { return count_; } |
| |
| private: |
| const Object* obj_; |
| intptr_t count_; |
| }; |
| |
| class TaskWithZoneAllocation : public ThreadPool::Task { |
| public: |
| TaskWithZoneAllocation(IsolateGroup* isolate_group, |
| Monitor* monitor, |
| bool* done, |
| intptr_t id) |
| : isolate_group_(isolate_group), |
| monitor_(monitor), |
| done_(done), |
| id_(id) {} |
| virtual void Run() { |
| const bool kBypassSafepoint = false; |
| Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kUnknownTask, |
| kBypassSafepoint); |
| { |
| 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<bool>* a0 = new (zone) ZoneGrowableArray<bool>(zone, 1); |
| GrowableArray<bool> 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::ExitIsolateGroupAsHelper(kBypassSafepoint); |
| { |
| MonitorLocker ml(monitor_); |
| *done_ = true; |
| ml.Notify(); |
| } |
| } |
| |
| private: |
| IsolateGroup* isolate_group_; |
| Monitor* monitor_; |
| bool* done_; |
| intptr_t id_; |
| }; |
| |
| ISOLATE_UNIT_TEST_CASE(ManyTasksWithZones) { |
| const int kTaskCount = 100; |
| Monitor sync[kTaskCount]; |
| bool done[kTaskCount]; |
| auto isolate = thread->isolate(); |
| auto isolate_group = thread->isolate_group(); |
| for (int i = 0; i < kTaskCount; i++) { |
| done[i] = false; |
| Dart::thread_pool()->Run<TaskWithZoneAllocation>(isolate_group, &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, |
| IsolateGroup* isolate_group, |
| Thread** thread_ptr, |
| Monitor* sync, |
| Monitor* monitor, |
| intptr_t* done_count, |
| bool* wait) |
| : id_(id), |
| isolate_group_(isolate_group), |
| thread_ptr_(thread_ptr), |
| sync_(sync), |
| monitor_(monitor), |
| done_count_(done_count), |
| wait_(wait) {} |
| |
| virtual void Run() { |
| const bool kBypassSafepoint = false; |
| Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kUnknownTask, |
| kBypassSafepoint); |
| { |
| Thread* thread = Thread::Current(); |
| *thread_ptr_ = thread; |
| CreateStackZones(id_); |
| } |
| Thread::ExitIsolateGroupAsHelper(kBypassSafepoint); |
| // 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<bool>* a0 = new (zone) ZoneGrowableArray<bool>(zone, 1); |
| GrowableArray<bool> 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_; |
| IsolateGroup* isolate_group_; |
| 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]; |
| auto isolate_group = thread->isolate_group(); |
| 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<SimpleTaskWithZoneAllocation>( |
| (i + 1), isolate_group, &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<Dart_Isolate>(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 constexpr intptr_t kTaskCount = 1; |
| |
| ICDataTestTask(IsolateGroup* isolate_group, |
| const Array& ic_datas, |
| Monitor* monitor, |
| intptr_t* exited, |
| std::atomic<bool>* done) |
| : isolate_group_(isolate_group), |
| ic_datas_(ic_datas), |
| len_(ic_datas.Length()), |
| monitor_(monitor), |
| exited_(exited), |
| done_(done) {} |
| |
| virtual void Run() { |
| const bool kBypassSafepoint = false; |
| Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kUnknownTask, |
| kBypassSafepoint); |
| |
| 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::ExitIsolateGroupAsHelper(kBypassSafepoint); |
| { |
| MonitorLocker ml(monitor_); |
| ++*exited_; |
| ml.Notify(); |
| } |
| } |
| |
| private: |
| IsolateGroup* isolate_group_; |
| const Array& ic_datas_; |
| const intptr_t len_; |
| Monitor* monitor_; |
| intptr_t* exited_; // # tasks that are no longer running. |
| std::atomic<bool>* 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; |
| } |
| |
| // Test that checks that other threads only see a fully initialized ICData |
| // whenever ICData is updated. |
| ISOLATE_UNIT_TEST_CASE(ICDataTest) { |
| auto isolate_group = thread->isolate_group(); |
| USE(isolate_group); |
| Monitor monitor; |
| intptr_t exited = 0; |
| std::atomic<bool> 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<ICDataTestTask>(isolate_group, 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 constexpr intptr_t kTaskCount = 5; |
| |
| 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() { |
| const bool kBypassSafepoint = false; |
| Thread::EnterIsolateGroupAsHelper(isolate_->group(), Thread::kUnknownTask, |
| kBypassSafepoint); |
| { |
| MonitorLocker ml(monitor_); |
| ++*expected_count_; |
| } |
| Thread* thread = Thread::Current(); |
| for (int i = reinterpret_cast<intptr_t>(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->OwnsSafepoint()); |
| 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::ExitIsolateGroupAsHelper(kBypassSafepoint); |
| { |
| 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. |
| }; |
| |
| // 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<SafepointTestTask>( |
| 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, nullptr); |
| EXPECT_VALID(lib); |
| Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr); |
| 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<SafepointTestTask>( |
| 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 != nullptr); |
| thread_count_0++; |
| } |
| } |
| |
| { |
| OSThreadIterator ti; |
| while (ti.HasNext()) { |
| OSThread* thread = ti.Next(); |
| EXPECT(thread != nullptr); |
| 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<ThreadIteratorTestParams*>(parameter); |
| OSThread* thread = OSThread::Current(); |
| EXPECT(thread != nullptr); |
| |
| 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<uword>(¶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<SafepointTestTask>( |
| 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<SafepointTestTask>( |
| 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(IsolateGroup* isolate_group, Monitor* done_monitor, bool* done) |
| : isolate_group_(isolate_group), |
| done_monitor_(done_monitor), |
| done_(done) {} |
| |
| virtual void Run() { |
| const bool kBypassSafepoint = false; |
| Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kUnknownTask, |
| kBypassSafepoint); |
| { |
| 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::ExitIsolateGroupAsHelper(kBypassSafepoint); |
| // Tell main thread that we are ready. |
| { |
| MonitorLocker ml(done_monitor_); |
| ASSERT(!*done_); |
| *done_ = true; |
| ml.Notify(); |
| } |
| } |
| |
| private: |
| IsolateGroup* isolate_group_; |
| Monitor* done_monitor_; |
| bool* done_; |
| }; |
| |
| ISOLATE_UNIT_TEST_CASE(HelperAllocAndGC) { |
| Monitor done_monitor; |
| bool done = false; |
| auto isolate_group = thread->isolate_group(); |
| Dart::thread_pool()->Run<AllocAndGCTask>(isolate_group, &done_monitor, &done); |
| { |
| while (true) { |
| TransitionVMToBlocked transition(thread); |
| MonitorLocker ml(&done_monitor); |
| if (done) { |
| break; |
| } |
| } |
| } |
| } |
| |
| class AllocateGlobsOfMemoryTask : public ThreadPool::Task { |
| public: |
| AllocateGlobsOfMemoryTask(IsolateGroup* isolate_group, |
| Monitor* done_monitor, |
| bool* done) |
| : isolate_group_(isolate_group), |
| done_monitor_(done_monitor), |
| done_(done) {} |
| |
| virtual void Run() { |
| const bool kBypassSafepoint = false; |
| Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kUnknownTask, |
| kBypassSafepoint); |
| { |
| 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::ExitIsolateGroupAsHelper(kBypassSafepoint); |
| // Tell main thread that we are ready. |
| { |
| MonitorLocker ml(done_monitor_); |
| ASSERT(!*done_); |
| *done_ = true; |
| ml.Notify(); |
| } |
| } |
| |
| private: |
| IsolateGroup* isolate_group_; |
| 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]; |
| auto isolate_group = thread->isolate_group(); |
| for (int i = 0; i < NUMBER_TEST_THREADS; i++) { |
| done[i] = false; |
| Dart::thread_pool()->Run<AllocateGlobsOfMemoryTask>( |
| isolate_group, &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 <typename LockType, typename LockerType> |
| 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 { |
| ASSERT(Thread::Current()->StealStickyError() == |
| Object::background_compilation_error().ptr()); |
| thrown_count++; |
| } |
| } |
| EXPECT_EQ(kNumIterations, execution_count); |
| EXPECT_EQ(kNumIterations, thrown_count); |
| } |
| ISOLATE_UNIT_TEST_CASE(SafepointRwLockWriteWithLongJmp) { |
| RunLockerWithLongJumpTest<SafepointRwLock, SafepointWriteRwLocker>(); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(SafepointRwLockReadWithLongJmp) { |
| RunLockerWithLongJumpTest<SafepointRwLock, SafepointReadRwLocker>(); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(SafepointMutexLockerWithLongJmp) { |
| RunLockerWithLongJumpTest<Mutex, SafepointMutexLocker>(); |
| } |
| |
| struct ReaderThreadState { |
| ThreadJoinId reader_id = OSThread::kInvalidThreadJoinId; |
| SafepointRwLock* rw_lock = nullptr; |
| IsolateGroup* isolate_group = nullptr; |
| Monitor* monitor = nullptr; |
| bool child_started = false; |
| intptr_t value = -1; |
| intptr_t observed_value = -1; |
| }; |
| |
| void Helper(uword arg) { |
| auto state = reinterpret_cast<ReaderThreadState*>(arg); |
| state->reader_id = OSThread::GetCurrentThreadJoinId(OSThread::Current()); |
| // Notify other thread. |
| { |
| MonitorLocker ml(state->monitor); |
| state->child_started = true; |
| ml.Notify(); |
| } |
| const bool kBypassSafepoint = false; |
| Thread::EnterIsolateGroupAsHelper(state->isolate_group, Thread::kUnknownTask, |
| kBypassSafepoint); |
| { |
| auto thread = Thread::Current(); |
| intptr_t observed_value = -1; |
| { |
| SafepointReadRwLocker reader(thread, state->rw_lock); |
| observed_value = state->value; |
| } |
| state->observed_value = observed_value; |
| } |
| 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; |
| state.value = 0; |
| state.child_started = false; |
| state.monitor = new Monitor(); |
| { |
| // 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<uword>(&state)) != 0) { |
| FATAL("Could not start worker thread"); |
| } |
| // Wait for the thread to start. |
| { |
| MonitorLocker ml(state.monitor); |
| while (!state.child_started) { |
| ml.Wait(); |
| } |
| } |
| state.value = 1; |
| } |
| state.value = 2; |
| } |
| // Join the other thread. |
| OSThread::Join(state.reader_id); |
| |
| // Ensure the reader thread had to wait before it entered the |
| // SafepointWriteRwLocker scope. |
| EXPECT(state.observed_value == 2); |
| |
| delete state.monitor; |
| } |
| |
| 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 sufficient 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 |