| // Copyright 2007-2011 the V8 project authors. All rights reserved. |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following |
| // disclaimer in the documentation and/or other materials provided |
| // with the distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived |
| // from this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <limits.h> |
| |
| #include <memory> |
| |
| #include "src/init/v8.h" |
| |
| #include "src/base/platform/platform.h" |
| #include "src/codegen/compilation-cache.h" |
| #include "src/execution/execution.h" |
| #include "src/execution/isolate.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/strings/unicode-inl.h" |
| #include "src/utils/utils.h" |
| #include "test/cctest/cctest.h" |
| |
| namespace { |
| |
| class DeoptimizeCodeThread : public v8::base::Thread { |
| public: |
| DeoptimizeCodeThread(v8::Isolate* isolate, v8::Local<v8::Context> context, |
| const char* trigger) |
| : Thread(Options("DeoptimizeCodeThread")), |
| isolate_(isolate), |
| context_(isolate, context), |
| source_(trigger) {} |
| |
| void Run() override { |
| v8::Locker locker(isolate_); |
| isolate_->Enter(); |
| v8::HandleScope handle_scope(isolate_); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(isolate_, context_); |
| v8::Context::Scope context_scope(context); |
| // This code triggers deoptimization of some function that will be |
| // used in a different thread. |
| CompileRun(source_); |
| isolate_->Exit(); |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| v8::Persistent<v8::Context> context_; |
| // The code that triggers the deoptimization. |
| const char* source_; |
| }; |
| |
| void UnlockForDeoptimization(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::Isolate* isolate = args.GetIsolate(); |
| // Gets the pointer to the thread that will trigger the deoptimization of the |
| // code. |
| DeoptimizeCodeThread* deoptimizer = |
| reinterpret_cast<DeoptimizeCodeThread*>(isolate->GetData(0)); |
| { |
| // Exits and unlocks the isolate. |
| isolate->Exit(); |
| v8::Unlocker unlocker(isolate); |
| // Starts the deoptimizing thread. |
| deoptimizer->Start(); |
| // Waits for deoptimization to finish. |
| deoptimizer->Join(); |
| } |
| // The deoptimizing thread has finished its work, and the isolate |
| // will now be used by the current thread. |
| isolate->Enter(); |
| } |
| |
| void UnlockForDeoptimizationIfReady( |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::Isolate* isolate = args.GetIsolate(); |
| bool* ready_to_deoptimize = reinterpret_cast<bool*>(isolate->GetData(1)); |
| if (*ready_to_deoptimize) { |
| // The test should enter here only once, so put the flag back to false. |
| *ready_to_deoptimize = false; |
| // Gets the pointer to the thread that will trigger the deoptimization of |
| // the code. |
| DeoptimizeCodeThread* deoptimizer = |
| reinterpret_cast<DeoptimizeCodeThread*>(isolate->GetData(0)); |
| { |
| // Exits and unlocks the thread. |
| isolate->Exit(); |
| v8::Unlocker unlocker(isolate); |
| // Starts the thread that deoptimizes the function. |
| deoptimizer->Start(); |
| // Waits for the deoptimizing thread to finish. |
| deoptimizer->Join(); |
| } |
| // The deoptimizing thread has finished its work, and the isolate |
| // will now be used by the current thread. |
| isolate->Enter(); |
| } |
| } |
| } // namespace |
| |
| namespace v8 { |
| namespace internal { |
| namespace test_lockers { |
| |
| TEST(LazyDeoptimizationMultithread) { |
| i::FLAG_allow_natives_syntax = true; |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| { |
| v8::Locker locker(isolate); |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| const char* trigger_deopt = "obj = { y: 0, x: 1 };"; |
| |
| // We use the isolate to pass arguments to the UnlockForDeoptimization |
| // function. Namely, we pass a pointer to the deoptimizing thread. |
| DeoptimizeCodeThread deoptimize_thread(isolate, context, trigger_deopt); |
| isolate->SetData(0, &deoptimize_thread); |
| v8::Context::Scope context_scope(context); |
| |
| // Create the function templace for C++ code that is invoked from |
| // JavaScript code. |
| Local<v8::FunctionTemplate> fun_templ = |
| v8::FunctionTemplate::New(isolate, UnlockForDeoptimization); |
| Local<Function> fun = fun_templ->GetFunction(context).ToLocalChecked(); |
| CHECK(context->Global() |
| ->Set(context, v8_str("unlock_for_deoptimization"), fun) |
| .FromJust()); |
| |
| // Optimizes a function f, which will be deoptimized in another |
| // thread. |
| CompileRun( |
| "var b = false; var obj = { x: 1 };" |
| "function f() { g(); return obj.x; }" |
| "function g() { if (b) { unlock_for_deoptimization(); } }" |
| "%NeverOptimizeFunction(g);" |
| "%PrepareFunctionForOptimization(f);" |
| "f(); f(); %OptimizeFunctionOnNextCall(f);" |
| "f();"); |
| |
| // Trigger the unlocking. |
| Local<Value> v = CompileRun("b = true; f();"); |
| |
| // Once the isolate has been unlocked, the thread will wait for the |
| // other thread to finish its task. Once this happens, this thread |
| // continues with its execution, that is, with the execution of the |
| // function g, which then returns to f. The function f should have |
| // also been deoptimized. If the replacement did not happen on this |
| // thread's stack, then the test will fail here. |
| CHECK(v->IsNumber()); |
| CHECK_EQ(1, static_cast<int>(v->NumberValue(context).FromJust())); |
| } |
| isolate->Dispose(); |
| } |
| |
| TEST(LazyDeoptimizationMultithreadWithNatives) { |
| i::FLAG_allow_natives_syntax = true; |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| { |
| v8::Locker locker(isolate); |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| const char* trigger_deopt = "%DeoptimizeFunction(f);"; |
| |
| // We use the isolate to pass arguments to the UnlockForDeoptimization |
| // function. Namely, we pass a pointer to the deoptimizing thread. |
| DeoptimizeCodeThread deoptimize_thread(isolate, context, trigger_deopt); |
| isolate->SetData(0, &deoptimize_thread); |
| bool ready_to_deopt = false; |
| isolate->SetData(1, &ready_to_deopt); |
| v8::Context::Scope context_scope(context); |
| |
| // Create the function templace for C++ code that is invoked from |
| // JavaScript code. |
| Local<v8::FunctionTemplate> fun_templ = |
| v8::FunctionTemplate::New(isolate, UnlockForDeoptimizationIfReady); |
| Local<Function> fun = fun_templ->GetFunction(context).ToLocalChecked(); |
| CHECK(context->Global() |
| ->Set(context, v8_str("unlock_for_deoptimization"), fun) |
| .FromJust()); |
| |
| // Optimizes a function f, which will be deoptimized in another |
| // thread. |
| CompileRun( |
| "var obj = { x: 1 };" |
| "function f() { g(); return obj.x;}" |
| "function g() { " |
| " unlock_for_deoptimization(); }" |
| "%NeverOptimizeFunction(g);" |
| "%PrepareFunctionForOptimization(f);" |
| "f(); f(); %OptimizeFunctionOnNextCall(f);"); |
| |
| // Trigger the unlocking. |
| ready_to_deopt = true; |
| isolate->SetData(1, &ready_to_deopt); |
| Local<Value> v = CompileRun("f();"); |
| |
| // Once the isolate has been unlocked, the thread will wait for the |
| // other thread to finish its task. Once this happens, this thread |
| // continues with its execution, that is, with the execution of the |
| // function g, which then returns to f. The function f should have |
| // also been deoptimized. Otherwise, the test will fail here. |
| CHECK(v->IsNumber()); |
| CHECK_EQ(1, static_cast<int>(v->NumberValue(context).FromJust())); |
| } |
| isolate->Dispose(); |
| } |
| |
| TEST(EagerDeoptimizationMultithread) { |
| i::FLAG_allow_natives_syntax = true; |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| { |
| v8::Locker locker(isolate); |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| const char* trigger_deopt = "f({y: 0, x: 1});"; |
| |
| // We use the isolate to pass arguments to the UnlockForDeoptimization |
| // function. Namely, we pass a pointer to the deoptimizing thread. |
| DeoptimizeCodeThread deoptimize_thread(isolate, context, trigger_deopt); |
| isolate->SetData(0, &deoptimize_thread); |
| bool ready_to_deopt = false; |
| isolate->SetData(1, &ready_to_deopt); |
| v8::Context::Scope context_scope(context); |
| |
| // Create the function templace for C++ code that is invoked from |
| // JavaScript code. |
| Local<v8::FunctionTemplate> fun_templ = |
| v8::FunctionTemplate::New(isolate, UnlockForDeoptimizationIfReady); |
| Local<Function> fun = fun_templ->GetFunction(context).ToLocalChecked(); |
| CHECK(context->Global() |
| ->Set(context, v8_str("unlock_for_deoptimization"), fun) |
| .FromJust()); |
| |
| // Optimizes a function f, which will be deoptimized by another thread. |
| CompileRun( |
| "function f(obj) { unlock_for_deoptimization(); return obj.x; }" |
| "%PrepareFunctionForOptimization(f);" |
| "f({x: 1}); f({x: 1});" |
| "%OptimizeFunctionOnNextCall(f);" |
| "f({x: 1});"); |
| |
| // Trigger the unlocking. |
| ready_to_deopt = true; |
| isolate->SetData(1, &ready_to_deopt); |
| Local<Value> v = CompileRun("f({x: 1});"); |
| |
| // Once the isolate has been unlocked, the thread will wait for the |
| // other thread to finish its task. Once this happens, this thread |
| // continues with its execution, that is, with the execution of the |
| // function g, which then returns to f. The function f should have |
| // also been deoptimized. Otherwise, the test will fail here. |
| CHECK(v->IsNumber()); |
| CHECK_EQ(1, static_cast<int>(v->NumberValue(context).FromJust())); |
| } |
| isolate->Dispose(); |
| } |
| |
| // Migrating an isolate |
| class KangarooThread : public v8::base::Thread { |
| public: |
| KangarooThread(v8::Isolate* isolate, v8::Local<v8::Context> context) |
| : Thread(Options("KangarooThread")), |
| isolate_(isolate), |
| context_(isolate, context) {} |
| |
| void Run() override { |
| { |
| v8::Locker locker(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope scope(isolate_); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(isolate_, context_); |
| v8::Context::Scope context_scope(context); |
| Local<Value> v = CompileRun("getValue()"); |
| CHECK(v->IsNumber()); |
| CHECK_EQ(30, static_cast<int>(v->NumberValue(context).FromJust())); |
| } |
| { |
| v8::Locker locker(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope scope(isolate_); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(isolate_, context_); |
| v8::Context::Scope context_scope(context); |
| Local<Value> v = CompileRun("getValue()"); |
| CHECK(v->IsNumber()); |
| CHECK_EQ(30, static_cast<int>(v->NumberValue(context).FromJust())); |
| } |
| isolate_->Dispose(); |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| v8::Persistent<v8::Context> context_; |
| }; |
| |
| |
| // Migrates an isolate from one thread to another |
| TEST(KangarooIsolates) { |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| std::unique_ptr<KangarooThread> thread1; |
| { |
| v8::Locker locker(isolate); |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope handle_scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| CompileRun("function getValue() { return 30; }"); |
| thread1.reset(new KangarooThread(isolate, context)); |
| } |
| thread1->Start(); |
| thread1->Join(); |
| } |
| |
| |
| static void CalcFibAndCheck(v8::Local<v8::Context> context) { |
| Local<Value> v = CompileRun("function fib(n) {" |
| " if (n <= 2) return 1;" |
| " return fib(n-1) + fib(n-2);" |
| "}" |
| "fib(10)"); |
| CHECK(v->IsNumber()); |
| CHECK_EQ(55, static_cast<int>(v->NumberValue(context).FromJust())); |
| } |
| |
| class JoinableThread { |
| public: |
| explicit JoinableThread(const char* name) |
| : name_(name), |
| semaphore_(0), |
| thread_(this) { |
| } |
| |
| virtual ~JoinableThread() = default; |
| |
| void Start() { |
| thread_.Start(); |
| } |
| |
| void Join() { |
| semaphore_.Wait(); |
| thread_.Join(); |
| } |
| |
| virtual void Run() = 0; |
| |
| private: |
| class ThreadWithSemaphore : public v8::base::Thread { |
| public: |
| explicit ThreadWithSemaphore(JoinableThread* joinable_thread) |
| : Thread(Options(joinable_thread->name_)), |
| joinable_thread_(joinable_thread) {} |
| |
| void Run() override { |
| joinable_thread_->Run(); |
| joinable_thread_->semaphore_.Signal(); |
| } |
| |
| private: |
| JoinableThread* joinable_thread_; |
| }; |
| |
| const char* name_; |
| v8::base::Semaphore semaphore_; |
| ThreadWithSemaphore thread_; |
| |
| friend class ThreadWithSemaphore; |
| |
| DISALLOW_COPY_AND_ASSIGN(JoinableThread); |
| }; |
| |
| |
| class IsolateLockingThreadWithLocalContext : public JoinableThread { |
| public: |
| explicit IsolateLockingThreadWithLocalContext(v8::Isolate* isolate) |
| : JoinableThread("IsolateLockingThread"), |
| isolate_(isolate) { |
| } |
| |
| void Run() override { |
| v8::Locker locker(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope handle_scope(isolate_); |
| LocalContext local_context(isolate_); |
| CalcFibAndCheck(local_context.local()); |
| } |
| private: |
| v8::Isolate* isolate_; |
| }; |
| |
| static void StartJoinAndDeleteThreads( |
| const std::vector<JoinableThread*>& threads) { |
| for (const auto& thread : threads) { |
| thread->Start(); |
| } |
| for (const auto& thread : threads) { |
| thread->Join(); |
| } |
| for (const auto& thread : threads) { |
| delete thread; |
| } |
| } |
| |
| |
| // Run many threads all locking on the same isolate |
| TEST(IsolateLockingStress) { |
| i::FLAG_always_opt = false; |
| #if V8_TARGET_ARCH_MIPS |
| const int kNThreads = 50; |
| #else |
| const int kNThreads = 100; |
| #endif |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back(new IsolateLockingThreadWithLocalContext(isolate)); |
| } |
| StartJoinAndDeleteThreads(threads); |
| isolate->Dispose(); |
| } |
| |
| |
| class IsolateNestedLockingThread : public JoinableThread { |
| public: |
| explicit IsolateNestedLockingThread(v8::Isolate* isolate) |
| : JoinableThread("IsolateNestedLocking"), isolate_(isolate) { |
| } |
| void Run() override { |
| v8::Locker lock(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope handle_scope(isolate_); |
| LocalContext local_context(isolate_); |
| { |
| v8::Locker another_lock(isolate_); |
| CalcFibAndCheck(local_context.local()); |
| } |
| { |
| v8::Locker another_lock(isolate_); |
| CalcFibAndCheck(local_context.local()); |
| } |
| } |
| private: |
| v8::Isolate* isolate_; |
| }; |
| |
| |
| // Run many threads with nested locks |
| TEST(IsolateNestedLocking) { |
| i::FLAG_always_opt = false; |
| #if V8_TARGET_ARCH_MIPS |
| const int kNThreads = 50; |
| #else |
| const int kNThreads = 100; |
| #endif |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back(new IsolateNestedLockingThread(isolate)); |
| } |
| StartJoinAndDeleteThreads(threads); |
| isolate->Dispose(); |
| } |
| |
| |
| class SeparateIsolatesLocksNonexclusiveThread : public JoinableThread { |
| public: |
| SeparateIsolatesLocksNonexclusiveThread(v8::Isolate* isolate1, |
| v8::Isolate* isolate2) |
| : JoinableThread("SeparateIsolatesLocksNonexclusiveThread"), |
| isolate1_(isolate1), isolate2_(isolate2) { |
| } |
| |
| void Run() override { |
| v8::Locker lock(isolate1_); |
| v8::Isolate::Scope isolate_scope(isolate1_); |
| v8::HandleScope handle_scope(isolate1_); |
| LocalContext local_context(isolate1_); |
| |
| IsolateLockingThreadWithLocalContext threadB(isolate2_); |
| threadB.Start(); |
| CalcFibAndCheck(local_context.local()); |
| threadB.Join(); |
| } |
| private: |
| v8::Isolate* isolate1_; |
| v8::Isolate* isolate2_; |
| }; |
| |
| |
| // Run parallel threads that lock and access different isolates in parallel |
| TEST(SeparateIsolatesLocksNonexclusive) { |
| i::FLAG_always_opt = false; |
| #if V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_S390 |
| const int kNThreads = 50; |
| #else |
| const int kNThreads = 100; |
| #endif |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate1 = v8::Isolate::New(create_params); |
| v8::Isolate* isolate2 = v8::Isolate::New(create_params); |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back( |
| new SeparateIsolatesLocksNonexclusiveThread(isolate1, isolate2)); |
| } |
| StartJoinAndDeleteThreads(threads); |
| isolate2->Dispose(); |
| isolate1->Dispose(); |
| } |
| |
| class LockIsolateAndCalculateFibSharedContextThread : public JoinableThread { |
| public: |
| explicit LockIsolateAndCalculateFibSharedContextThread( |
| v8::Isolate* isolate, v8::Local<v8::Context> context) |
| : JoinableThread("LockIsolateAndCalculateFibThread"), |
| isolate_(isolate), |
| context_(isolate, context) {} |
| |
| void Run() override { |
| v8::Locker lock(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope handle_scope(isolate_); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(isolate_, context_); |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| private: |
| v8::Isolate* isolate_; |
| v8::Persistent<v8::Context> context_; |
| }; |
| |
| class LockerUnlockerThread : public JoinableThread { |
| public: |
| explicit LockerUnlockerThread(v8::Isolate* isolate) |
| : JoinableThread("LockerUnlockerThread"), |
| isolate_(isolate) { |
| } |
| |
| void Run() override { |
| isolate_->DiscardThreadSpecificMetadata(); // No-op |
| { |
| v8::Locker lock(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope handle_scope(isolate_); |
| v8::Local<v8::Context> context = v8::Context::New(isolate_); |
| { |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| { |
| LockIsolateAndCalculateFibSharedContextThread thread(isolate_, context); |
| isolate_->Exit(); |
| v8::Unlocker unlocker(isolate_); |
| thread.Start(); |
| thread.Join(); |
| } |
| isolate_->Enter(); |
| { |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| } |
| isolate_->DiscardThreadSpecificMetadata(); |
| isolate_->DiscardThreadSpecificMetadata(); // No-op |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| }; |
| |
| |
| // Use unlocker inside of a Locker, multiple threads. |
| TEST(LockerUnlocker) { |
| i::FLAG_always_opt = false; |
| #if V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_S390 |
| const int kNThreads = 50; |
| #else |
| const int kNThreads = 100; |
| #endif |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back(new LockerUnlockerThread(isolate)); |
| } |
| StartJoinAndDeleteThreads(threads); |
| isolate->Dispose(); |
| } |
| |
| class LockTwiceAndUnlockThread : public JoinableThread { |
| public: |
| explicit LockTwiceAndUnlockThread(v8::Isolate* isolate) |
| : JoinableThread("LockTwiceAndUnlockThread"), |
| isolate_(isolate) { |
| } |
| |
| void Run() override { |
| v8::Locker lock(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope handle_scope(isolate_); |
| v8::Local<v8::Context> context = v8::Context::New(isolate_); |
| { |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| { |
| v8::Locker second_lock(isolate_); |
| { |
| LockIsolateAndCalculateFibSharedContextThread thread(isolate_, context); |
| isolate_->Exit(); |
| v8::Unlocker unlocker(isolate_); |
| thread.Start(); |
| thread.Join(); |
| } |
| } |
| isolate_->Enter(); |
| { |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| }; |
| |
| |
| // Use Unlocker inside two Lockers. |
| TEST(LockTwiceAndUnlock) { |
| i::FLAG_always_opt = false; |
| #if V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_S390 |
| const int kNThreads = 50; |
| #else |
| const int kNThreads = 100; |
| #endif |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back(new LockTwiceAndUnlockThread(isolate)); |
| } |
| StartJoinAndDeleteThreads(threads); |
| isolate->Dispose(); |
| } |
| |
| class LockAndUnlockDifferentIsolatesThread : public JoinableThread { |
| public: |
| LockAndUnlockDifferentIsolatesThread(v8::Isolate* isolate1, |
| v8::Isolate* isolate2) |
| : JoinableThread("LockAndUnlockDifferentIsolatesThread"), |
| isolate1_(isolate1), |
| isolate2_(isolate2) { |
| } |
| |
| void Run() override { |
| std::unique_ptr<LockIsolateAndCalculateFibSharedContextThread> thread; |
| v8::Locker lock1(isolate1_); |
| CHECK(v8::Locker::IsLocked(isolate1_)); |
| CHECK(!v8::Locker::IsLocked(isolate2_)); |
| { |
| v8::Isolate::Scope isolate_scope(isolate1_); |
| v8::HandleScope handle_scope(isolate1_); |
| v8::Local<v8::Context> context1 = v8::Context::New(isolate1_); |
| { |
| v8::Context::Scope context_scope(context1); |
| CalcFibAndCheck(context1); |
| } |
| thread.reset(new LockIsolateAndCalculateFibSharedContextThread(isolate1_, |
| context1)); |
| } |
| v8::Locker lock2(isolate2_); |
| CHECK(v8::Locker::IsLocked(isolate1_)); |
| CHECK(v8::Locker::IsLocked(isolate2_)); |
| { |
| v8::Isolate::Scope isolate_scope(isolate2_); |
| v8::HandleScope handle_scope(isolate2_); |
| v8::Local<v8::Context> context2 = v8::Context::New(isolate2_); |
| { |
| v8::Context::Scope context_scope(context2); |
| CalcFibAndCheck(context2); |
| } |
| v8::Unlocker unlock1(isolate1_); |
| CHECK(!v8::Locker::IsLocked(isolate1_)); |
| CHECK(v8::Locker::IsLocked(isolate2_)); |
| v8::Context::Scope context_scope(context2); |
| thread->Start(); |
| CalcFibAndCheck(context2); |
| thread->Join(); |
| } |
| } |
| |
| private: |
| v8::Isolate* isolate1_; |
| v8::Isolate* isolate2_; |
| }; |
| |
| |
| // Lock two isolates and unlock one of them. |
| TEST(LockAndUnlockDifferentIsolates) { |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate1 = v8::Isolate::New(create_params); |
| v8::Isolate* isolate2 = v8::Isolate::New(create_params); |
| LockAndUnlockDifferentIsolatesThread thread(isolate1, isolate2); |
| thread.Start(); |
| thread.Join(); |
| isolate2->Dispose(); |
| isolate1->Dispose(); |
| } |
| |
| class LockUnlockLockThread : public JoinableThread { |
| public: |
| LockUnlockLockThread(v8::Isolate* isolate, v8::Local<v8::Context> context) |
| : JoinableThread("LockUnlockLockThread"), |
| isolate_(isolate), |
| context_(isolate, context) {} |
| |
| void Run() override { |
| v8::Locker lock1(isolate_); |
| CHECK(v8::Locker::IsLocked(isolate_)); |
| CHECK(!v8::Locker::IsLocked(CcTest::isolate())); |
| { |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope handle_scope(isolate_); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(isolate_, context_); |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| { |
| v8::Unlocker unlock1(isolate_); |
| CHECK(!v8::Locker::IsLocked(isolate_)); |
| CHECK(!v8::Locker::IsLocked(CcTest::isolate())); |
| { |
| v8::Locker lock2(isolate_); |
| v8::Isolate::Scope isolate_scope(isolate_); |
| v8::HandleScope handle_scope(isolate_); |
| CHECK(v8::Locker::IsLocked(isolate_)); |
| CHECK(!v8::Locker::IsLocked(CcTest::isolate())); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(isolate_, context_); |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| } |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| v8::Persistent<v8::Context> context_; |
| }; |
| |
| |
| // Locker inside an Unlocker inside a Locker. |
| TEST(LockUnlockLockMultithreaded) { |
| #if V8_TARGET_ARCH_MIPS |
| const int kNThreads = 50; |
| #else |
| const int kNThreads = 100; |
| #endif |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| { |
| v8::Locker locker_(isolate); |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope handle_scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back(new LockUnlockLockThread(isolate, context)); |
| } |
| } |
| StartJoinAndDeleteThreads(threads); |
| isolate->Dispose(); |
| } |
| |
| class LockUnlockLockDefaultIsolateThread : public JoinableThread { |
| public: |
| explicit LockUnlockLockDefaultIsolateThread(v8::Local<v8::Context> context) |
| : JoinableThread("LockUnlockLockDefaultIsolateThread"), |
| context_(CcTest::isolate(), context) {} |
| |
| void Run() override { |
| v8::Locker lock1(CcTest::isolate()); |
| { |
| v8::Isolate::Scope isolate_scope(CcTest::isolate()); |
| v8::HandleScope handle_scope(CcTest::isolate()); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(CcTest::isolate(), context_); |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| { |
| v8::Unlocker unlock1(CcTest::isolate()); |
| { |
| v8::Locker lock2(CcTest::isolate()); |
| v8::Isolate::Scope isolate_scope(CcTest::isolate()); |
| v8::HandleScope handle_scope(CcTest::isolate()); |
| v8::Local<v8::Context> context = |
| v8::Local<v8::Context>::New(CcTest::isolate(), context_); |
| v8::Context::Scope context_scope(context); |
| CalcFibAndCheck(context); |
| } |
| } |
| } |
| |
| private: |
| v8::Persistent<v8::Context> context_; |
| }; |
| |
| |
| // Locker inside an Unlocker inside a Locker for default isolate. |
| TEST(LockUnlockLockDefaultIsolateMultithreaded) { |
| #if V8_TARGET_ARCH_MIPS |
| const int kNThreads = 50; |
| #else |
| const int kNThreads = 100; |
| #endif |
| Local<v8::Context> context; |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| { |
| v8::Locker locker_(CcTest::isolate()); |
| v8::Isolate::Scope isolate_scope(CcTest::isolate()); |
| v8::HandleScope handle_scope(CcTest::isolate()); |
| context = v8::Context::New(CcTest::isolate()); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back(new LockUnlockLockDefaultIsolateThread(context)); |
| } |
| } |
| StartJoinAndDeleteThreads(threads); |
| } |
| |
| |
| TEST(Regress1433) { |
| for (int i = 0; i < 10; i++) { |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| { |
| v8::Locker lock(isolate); |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope handle_scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| v8::Local<v8::String> source = v8_str("1+1"); |
| v8::Local<v8::Script> script = |
| v8::Script::Compile(context, source).ToLocalChecked(); |
| v8::Local<v8::Value> result = script->Run(context).ToLocalChecked(); |
| v8::String::Utf8Value utf8(isolate, result); |
| } |
| isolate->Dispose(); |
| } |
| } |
| |
| |
| static const char* kSimpleExtensionSource = |
| "(function Foo() {" |
| " return 4;" |
| "})() "; |
| |
| class IsolateGenesisThread : public JoinableThread { |
| public: |
| IsolateGenesisThread(int count, const char* extension_names[]) |
| : JoinableThread("IsolateGenesisThread"), |
| count_(count), |
| extension_names_(extension_names) |
| {} |
| |
| void Run() override { |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| { |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::ExtensionConfiguration extensions(count_, extension_names_); |
| v8::HandleScope handle_scope(isolate); |
| v8::Context::New(isolate, &extensions); |
| } |
| isolate->Dispose(); |
| } |
| |
| private: |
| int count_; |
| const char** extension_names_; |
| }; |
| |
| |
| // Test installing extensions in separate isolates concurrently. |
| // http://code.google.com/p/v8/issues/detail?id=1821 |
| TEST(ExtensionsRegistration) { |
| #if V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_MIPS |
| const int kNThreads = 10; |
| #elif V8_TARGET_ARCH_S390 && V8_TARGET_ARCH_32_BIT |
| const int kNThreads = 10; |
| #else |
| const int kNThreads = 40; |
| #endif |
| const char* extension_names[] = {"test0", "test1", "test2", "test3", |
| "test4", "test5", "test6", "test7"}; |
| for (const char* name : extension_names) { |
| v8::RegisterExtension( |
| v8::base::make_unique<v8::Extension>(name, kSimpleExtensionSource)); |
| } |
| std::vector<JoinableThread*> threads; |
| threads.reserve(kNThreads); |
| for (int i = 0; i < kNThreads; i++) { |
| threads.push_back(new IsolateGenesisThread(8, extension_names)); |
| } |
| StartJoinAndDeleteThreads(threads); |
| } |
| |
| } // namespace test_lockers |
| } // namespace internal |
| } // namespace v8 |