From: dslomov@chromium.org Date: Fri, 29 Apr 2011 21:26:03 +0000 (+0000) Subject: This implements per-isolate locking and unlocking, including tests X-Git-Tag: upstream/4.7.83~19533 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f4c120d6159b22a1f2d136091f302dfe505de66c;p=platform%2Fupstream%2Fv8.git This implements per-isolate locking and unlocking, including tests BUG= TEST= Review URL: http://codereview.chromium.org/6788023 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7734 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- diff --git a/include/v8.h b/include/v8.h index b621f0110..98d0b5dba 100644 --- a/include/v8.h +++ b/include/v8.h @@ -3355,26 +3355,20 @@ class V8EXPORT Context { * to the user of V8 to ensure (perhaps with locking) that this * constraint is not violated. * - * More then one thread and multiple V8 isolates can be used - * without any locking if each isolate is created and accessed - * by a single thread only. For example, one thread can use - * multiple isolates or multiple threads can each create and run - * their own isolate. + * v8::Locker is a scoped lock object. While it's + * active (i.e. between its construction and destruction) the current thread is + * allowed to use the locked isolate. V8 guarantees that an isolate can be locked + * by at most one thread at any time. In other words, the scope of a v8::Locker is + * a critical section. * - * If you wish to start using V8 isolate in more then one thread - * you can do this by constructing a v8::Locker object to guard - * access to the isolate. After the code using V8 has completed - * for the current thread you can call the destructor. This can - * be combined with C++ scope-based construction as follows - * (assumes the default isolate that is used if not specified as - * a parameter for the Locker): - * - * \code + * Sample usage: +* \code * ... * { - * v8::Locker locker; + * v8::Locker locker(isolate); + * v8::Isolate::Scope isolate_scope(isolate); * ... - * // Code using V8 goes here. + * // Code using V8 and isolate goes here. * ... * } // Destructor called here * \endcode @@ -3385,11 +3379,13 @@ class V8EXPORT Context { * * \code * { - * v8::Unlocker unlocker; + * isolate->Exit(); + * v8::Unlocker unlocker(isolate); * ... * // Code not using V8 goes here while V8 can run in another thread. * ... * } // Destructor called here. + * isolate->Enter(); * \endcode * * The Unlocker object is intended for use in a long-running callback @@ -3409,32 +3405,45 @@ class V8EXPORT Context { * \code * // V8 not locked. * { - * v8::Locker locker; + * v8::Locker locker(isolate); + * Isolate::Scope isolate_scope(isolate); * // V8 locked. * { - * v8::Locker another_locker; + * v8::Locker another_locker(isolate); * // V8 still locked (2 levels). * { - * v8::Unlocker unlocker; + * isolate->Exit(); + * v8::Unlocker unlocker(isolate); * // V8 not locked. * } + * isolate->Enter(); * // V8 locked again (2 levels). * } * // V8 still locked (1 level). * } * // V8 Now no longer locked. * \endcode + * + * */ class V8EXPORT Unlocker { public: - Unlocker(); + /** + * Initialize Unlocker for a given Isolate. NULL means default isolate. + */ + explicit Unlocker(Isolate* isolate = NULL); ~Unlocker(); + private: + internal::Isolate* isolate_; }; class V8EXPORT Locker { public: - Locker(); + /** + * Initialize Locker for a given Isolate. NULL means default isolate. + */ + explicit Locker(Isolate* isolate = NULL); ~Locker(); /** @@ -3452,9 +3461,10 @@ class V8EXPORT Locker { static void StopPreemption(); /** - * Returns whether or not the locker is locked by the current thread. + * Returns whether or not the locker for a given isolate, or default isolate if NULL is given, + * is locked by the current thread. */ - static bool IsLocked(); + static bool IsLocked(Isolate* isolate = NULL); /** * Returns whether v8::Locker is being used by this V8 instance. @@ -3464,6 +3474,7 @@ class V8EXPORT Locker { private: bool has_lock_; bool top_level_; + internal::Isolate* isolate_; static bool active_; diff --git a/src/api.cc b/src/api.cc index 84c0be46c..0ec27fe42 100644 --- a/src/api.cc +++ b/src/api.cc @@ -53,7 +53,6 @@ #define LOG_API(isolate, expr) LOG(isolate, ApiEntryCall(expr)) -// TODO(isolates): avoid repeated TLS reads in function prologues. #ifdef ENABLE_VMSTATE_TRACKING #define ENTER_V8(isolate) \ ASSERT((isolate)->IsInitialized()); \ @@ -290,6 +289,7 @@ static inline bool EnsureInitializedForIsolate(i::Isolate* isolate, if (isolate != NULL) { if (isolate->IsInitialized()) return true; } + ASSERT(isolate == i::Isolate::Current()); return ApiCheck(InitializeHelper(), location, "Error initializing V8"); } @@ -5687,9 +5687,8 @@ void HandleScopeImplementer::FreeThreadResources() { char* HandleScopeImplementer::ArchiveThread(char* storage) { - Isolate* isolate = Isolate::Current(); v8::ImplementationUtilities::HandleScopeData* current = - isolate->handle_scope_data(); + isolate_->handle_scope_data(); handle_scope_data_ = *current; memcpy(storage, this, sizeof(*this)); @@ -5707,7 +5706,7 @@ int HandleScopeImplementer::ArchiveSpacePerThread() { char* HandleScopeImplementer::RestoreThread(char* storage) { memcpy(this, storage, sizeof(*this)); - *Isolate::Current()->handle_scope_data() = handle_scope_data_; + *isolate_->handle_scope_data() = handle_scope_data_; return storage + ArchiveSpacePerThread(); } @@ -5733,7 +5732,7 @@ void HandleScopeImplementer::IterateThis(ObjectVisitor* v) { void HandleScopeImplementer::Iterate(ObjectVisitor* v) { v8::ImplementationUtilities::HandleScopeData* current = - Isolate::Current()->handle_scope_data(); + isolate_->handle_scope_data(); handle_scope_data_ = *current; IterateThis(v); } diff --git a/src/api.h b/src/api.h index 7423d28e2..6ea2df906 100644 --- a/src/api.h +++ b/src/api.h @@ -399,8 +399,9 @@ class StringTracker { ISOLATED_CLASS HandleScopeImplementer { public: - HandleScopeImplementer() - : blocks_(0), + explicit HandleScopeImplementer(Isolate* isolate) + : isolate_(isolate), + blocks_(0), entered_contexts_(0), saved_contexts_(0), spare_(NULL), @@ -466,6 +467,7 @@ ISOLATED_CLASS HandleScopeImplementer { ASSERT(call_depth_ == 0); } + Isolate* isolate_; List blocks_; // Used as a stack to keep track of entered contexts. List > entered_contexts_; diff --git a/src/execution.cc b/src/execution.cc index eb26438fe..d511b9015 100644 --- a/src/execution.cc +++ b/src/execution.cc @@ -457,8 +457,9 @@ void StackGuard::ClearThread(const ExecutionAccess& lock) { void StackGuard::InitThread(const ExecutionAccess& lock) { if (thread_local_.Initialize()) isolate_->heap()->SetStackLimits(); - uintptr_t stored_limit = - Isolate::CurrentPerIsolateThreadData()->stack_limit(); + Isolate::PerIsolateThreadData* per_thread = + isolate_->FindOrAllocatePerThreadDataForThisThread(); + uintptr_t stored_limit = per_thread->stack_limit(); // You should hold the ExecutionAccess lock when you call this. if (stored_limit != 0) { StackGuard::SetStackLimit(stored_limit); diff --git a/src/isolate.cc b/src/isolate.cc index 6fc671c50..0b0f0bf58 100644 --- a/src/isolate.cc +++ b/src/isolate.cc @@ -311,6 +311,17 @@ Isolate::PerIsolateThreadData* } +Isolate::PerIsolateThreadData* Isolate::FindPerThreadDataForThisThread() { + ThreadId thread_id = ThreadId::Current(); + PerIsolateThreadData* per_thread = NULL; + { + ScopedLock lock(process_wide_mutex_); + per_thread = thread_data_table_->Lookup(this, thread_id); + } + return per_thread; +} + + void Isolate::EnsureDefaultIsolate() { ScopedLock lock(process_wide_mutex_); if (default_isolate_ == NULL) { @@ -322,7 +333,9 @@ void Isolate::EnsureDefaultIsolate() { } // Can't use SetIsolateThreadLocals(default_isolate_, NULL) here // becase a non-null thread data may be already set. - Thread::SetThreadLocal(isolate_key_, default_isolate_); + if (Thread::GetThreadLocal(isolate_key_) == NULL) { + Thread::SetThreadLocal(isolate_key_, default_isolate_); + } CHECK(default_isolate_->PreInit()); } @@ -458,6 +471,11 @@ Isolate::Isolate() zone_.isolate_ = this; stack_guard_.isolate_ = this; + // ThreadManager is initialized early to support locking an isolate + // before it is entered. + thread_manager_ = new ThreadManager(); + thread_manager_->isolate_ = this; + #if defined(V8_TARGET_ARCH_ARM) && !defined(__arm__) || \ defined(V8_TARGET_ARCH_MIPS) && !defined(__mips__) simulator_initialized_ = false; @@ -643,7 +661,6 @@ bool Isolate::PreInit() { TRACE_ISOLATE(preinit); ASSERT(Isolate::Current() == this); - #ifdef ENABLE_DEBUGGER_SUPPORT debug_ = new Debug(this); debugger_ = new Debugger(); @@ -671,8 +688,6 @@ bool Isolate::PreInit() { string_tracker_ = new StringTracker(); string_tracker_->isolate_ = this; - thread_manager_ = new ThreadManager(); - thread_manager_->isolate_ = this; compilation_cache_ = new CompilationCache(this); transcendental_cache_ = new TranscendentalCache(); keyed_lookup_cache_ = new KeyedLookupCache(); @@ -683,7 +698,7 @@ bool Isolate::PreInit() { write_input_buffer_ = new StringInputBuffer(); global_handles_ = new GlobalHandles(this); bootstrapper_ = new Bootstrapper(); - handle_scope_implementer_ = new HandleScopeImplementer(); + handle_scope_implementer_ = new HandleScopeImplementer(this); stub_cache_ = new StubCache(this); ast_sentinels_ = new AstSentinels(); regexp_stack_ = new RegExpStack(); @@ -700,6 +715,7 @@ bool Isolate::PreInit() { void Isolate::InitializeThreadLocal() { + thread_local_top_.isolate_ = this; thread_local_top_.Initialize(); clear_pending_exception(); clear_pending_message(); diff --git a/src/isolate.h b/src/isolate.h index 52c7145e2..af3a99a89 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -224,6 +224,7 @@ class ThreadLocalTop BASE_EMBEDDED { ASSERT(try_catch_handler_address_ == NULL); } + Isolate* isolate_; // The context where the current execution method is created and for variable // lookups. Context* context_; @@ -486,6 +487,10 @@ class Isolate { // Safe to call multiple times. static void EnsureDefaultIsolate(); + // Find the PerThread for this particular (isolate, thread) combination + // If one does not yet exist, return null. + PerIsolateThreadData* FindPerThreadDataForThisThread(); + #ifdef ENABLE_DEBUGGER_SUPPORT // Get the debugger from the default isolate. Preinitializes the // default isolate if needed. @@ -1065,7 +1070,7 @@ class Isolate { // If one does not yet exist, allocate a new one. PerIsolateThreadData* FindOrAllocatePerThreadDataForThisThread(); - // PreInits and returns a default isolate. Needed when a new thread tries +// PreInits and returns a default isolate. Needed when a new thread tries // to create a Locker for the first time (the lock itself is in the isolate). static Isolate* GetDefaultIsolateForLocking(); @@ -1197,9 +1202,12 @@ class Isolate { friend class ExecutionAccess; friend class IsolateInitializer; + friend class ThreadManager; + friend class StackGuard; friend class ThreadId; friend class v8::Isolate; friend class v8::Locker; + friend class v8::Unlocker; DISALLOW_COPY_AND_ASSIGN(Isolate); }; diff --git a/src/objects.cc b/src/objects.cc index 7c2097fa3..b41b34c4a 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -4958,8 +4958,7 @@ int Relocatable::ArchiveSpacePerThread() { // Archive statics that are thread local. -char* Relocatable::ArchiveState(char* to) { - Isolate* isolate = Isolate::Current(); +char* Relocatable::ArchiveState(Isolate* isolate, char* to) { *reinterpret_cast(to) = isolate->relocatable_top(); isolate->set_relocatable_top(NULL); return to + ArchiveSpacePerThread(); @@ -4967,8 +4966,7 @@ char* Relocatable::ArchiveState(char* to) { // Restore statics that are thread local. -char* Relocatable::RestoreState(char* from) { - Isolate* isolate = Isolate::Current(); +char* Relocatable::RestoreState(Isolate* isolate, char* from) { isolate->set_relocatable_top(*reinterpret_cast(from)); return from + ArchiveSpacePerThread(); } diff --git a/src/objects.h b/src/objects.h index 556aaa2b7..1e62ce011 100644 --- a/src/objects.h +++ b/src/objects.h @@ -5924,8 +5924,8 @@ class Relocatable BASE_EMBEDDED { static void PostGarbageCollectionProcessing(); static int ArchiveSpacePerThread(); - static char* ArchiveState(char* to); - static char* RestoreState(char* from); + static char* ArchiveState(Isolate* isolate, char* to); + static char* RestoreState(Isolate* isolate, char* from); static void Iterate(ObjectVisitor* v); static void Iterate(ObjectVisitor* v, Relocatable* top); static char* Iterate(ObjectVisitor* v, char* t); diff --git a/src/top.cc b/src/top.cc index f44de7ad6..6d6823135 100644 --- a/src/top.cc +++ b/src/top.cc @@ -77,9 +77,9 @@ void ThreadLocalTop::Initialize() { InitializeInternal(); #ifdef USE_SIMULATOR #ifdef V8_TARGET_ARCH_ARM - simulator_ = Simulator::current(Isolate::Current()); + simulator_ = Simulator::current(isolate_); #elif V8_TARGET_ARCH_MIPS - simulator_ = Simulator::current(Isolate::Current()); + simulator_ = Simulator::current(isolate_); #endif #endif thread_id_ = ThreadId::Current(); diff --git a/src/v8threads.cc b/src/v8threads.cc index 4b033fcf0..e28797480 100644 --- a/src/v8threads.cc +++ b/src/v8threads.cc @@ -43,90 +43,85 @@ bool Locker::active_ = false; // Constructor for the Locker object. Once the Locker is constructed the -// current thread will be guaranteed to have the big V8 lock. -Locker::Locker() : has_lock_(false), top_level_(true) { - // TODO(isolates): When Locker has Isolate parameter and it is provided, grab - // that one instead of using the current one. - // We pull default isolate for Locker constructor w/o p[arameter. - // A thread should not enter an isolate before acquiring a lock, - // in cases which mandate using Lockers. - // So getting a lock is the first thing threads do in a scenario where - // multple threads share an isolate. Hence, we need to access - // 'locking isolate' before we can actually enter into default isolate. - internal::Isolate* isolate = internal::Isolate::GetDefaultIsolateForLocking(); - ASSERT(isolate != NULL); - +// current thread will be guaranteed to have the lock for a given isolate. +Locker::Locker(v8::Isolate* isolate) + : has_lock_(false), + top_level_(false), + isolate_(reinterpret_cast(isolate)) { + if (isolate_ == NULL) { + isolate_ = i::Isolate::GetDefaultIsolateForLocking(); + } // Record that the Locker has been used at least once. active_ = true; // Get the big lock if necessary. - if (!isolate->thread_manager()->IsLockedByCurrentThread()) { - isolate->thread_manager()->Lock(); + if (!isolate_->thread_manager()->IsLockedByCurrentThread()) { + isolate_->thread_manager()->Lock(); has_lock_ = true; - if (isolate->IsDefaultIsolate()) { - // This only enters if not yet entered. - internal::Isolate::EnterDefaultIsolate(); - } - - ASSERT(internal::Thread::HasThreadLocal( - internal::Isolate::thread_id_key())); - // Make sure that V8 is initialized. Archiving of threads interferes // with deserialization by adding additional root pointers, so we must // initialize here, before anyone can call ~Locker() or Unlocker(). - if (!isolate->IsInitialized()) { + if (isolate_->IsDefaultIsolate()) { + // This only enters if not yet entered. + internal::Isolate::EnterDefaultIsolate(); + } else if (!isolate_->IsInitialized()) { + isolate_->Enter(); V8::Initialize(); + isolate_->Exit(); } + // This may be a locker within an unlocker in which case we have to // get the saved state for this thread and restore it. - if (isolate->thread_manager()->RestoreThread()) { + if (isolate_->thread_manager()->RestoreThread()) { top_level_ = false; } else { - internal::ExecutionAccess access(isolate); - isolate->stack_guard()->ClearThread(access); - isolate->stack_guard()->InitThread(access); + internal::ExecutionAccess access(isolate_); + isolate_->stack_guard()->ClearThread(access); + isolate_->stack_guard()->InitThread(access); } } - ASSERT(isolate->thread_manager()->IsLockedByCurrentThread()); + ASSERT(isolate_->thread_manager()->IsLockedByCurrentThread()); } -bool Locker::IsLocked() { - return internal::Isolate::Current()->thread_manager()-> - IsLockedByCurrentThread(); +bool Locker::IsLocked(v8::Isolate* isolate) { + i::Isolate* internal_isolate = reinterpret_cast(isolate); + if (internal_isolate == NULL) { + internal_isolate = i::Isolate::GetDefaultIsolateForLocking(); + } + return internal_isolate->thread_manager()->IsLockedByCurrentThread(); } Locker::~Locker() { - // TODO(isolate): this should use a field storing the isolate it - // locked instead. - internal::Isolate* isolate = internal::Isolate::Current(); - ASSERT(isolate->thread_manager()->IsLockedByCurrentThread()); + ASSERT(isolate_->thread_manager()->IsLockedByCurrentThread()); if (has_lock_) { if (top_level_) { - isolate->thread_manager()->FreeThreadResources(); + isolate_->thread_manager()->FreeThreadResources(); } else { - isolate->thread_manager()->ArchiveThread(); + isolate_->thread_manager()->ArchiveThread(); } - isolate->thread_manager()->Unlock(); + isolate_->thread_manager()->Unlock(); } } -Unlocker::Unlocker() { - internal::Isolate* isolate = internal::Isolate::Current(); - ASSERT(isolate->thread_manager()->IsLockedByCurrentThread()); - isolate->thread_manager()->ArchiveThread(); - isolate->thread_manager()->Unlock(); +Unlocker::Unlocker(v8::Isolate* isolate) + : isolate_(reinterpret_cast(isolate)) { + if (isolate_ == NULL) { + isolate_ = i::Isolate::GetDefaultIsolateForLocking(); + } + + ASSERT(isolate_->thread_manager()->IsLockedByCurrentThread()); + isolate_->thread_manager()->ArchiveThread(); + isolate_->thread_manager()->Unlock(); } Unlocker::~Unlocker() { - // TODO(isolates): check it's the isolate we unlocked. - internal::Isolate* isolate = internal::Isolate::Current(); - ASSERT(!isolate->thread_manager()->IsLockedByCurrentThread()); - isolate->thread_manager()->Lock(); - isolate->thread_manager()->RestoreThread(); + ASSERT(!isolate_->thread_manager()->IsLockedByCurrentThread()); + isolate_->thread_manager()->Lock(); + isolate_->thread_manager()->RestoreThread(); } @@ -144,17 +139,20 @@ namespace internal { bool ThreadManager::RestoreThread() { + ASSERT(IsLockedByCurrentThread()); // First check whether the current thread has been 'lazily archived', ie // not archived at all. If that is the case we put the state storage we // had prepared back in the free list, since we didn't need it after all. if (lazily_archived_thread_.Equals(ThreadId::Current())) { lazily_archived_thread_ = ThreadId::Invalid(); - ASSERT(Isolate::CurrentPerIsolateThreadData()->thread_state() == - lazily_archived_thread_state_); + Isolate::PerIsolateThreadData* per_thread = + isolate_->FindPerThreadDataForThisThread(); + ASSERT(per_thread != NULL); + ASSERT(per_thread->thread_state() == lazily_archived_thread_state_); lazily_archived_thread_state_->set_id(ThreadId::Invalid()); lazily_archived_thread_state_->LinkInto(ThreadState::FREE_LIST); lazily_archived_thread_state_ = NULL; - Isolate::CurrentPerIsolateThreadData()->set_thread_state(NULL); + per_thread->set_thread_state(NULL); return true; } @@ -168,7 +166,7 @@ bool ThreadManager::RestoreThread() { EagerlyArchiveThread(); } Isolate::PerIsolateThreadData* per_thread = - Isolate::CurrentPerIsolateThreadData(); + isolate_->FindPerThreadDataForThisThread(); if (per_thread == NULL || per_thread->thread_state() == NULL) { // This is a new thread. isolate_->stack_guard()->InitThread(access); @@ -178,7 +176,7 @@ bool ThreadManager::RestoreThread() { char* from = state->data(); from = isolate_->handle_scope_implementer()->RestoreThread(from); from = isolate_->RestoreThread(from); - from = Relocatable::RestoreState(from); + from = Relocatable::RestoreState(isolate_, from); #ifdef ENABLE_DEBUGGER_SUPPORT from = isolate_->debug()->RestoreDebug(from); #endif @@ -300,9 +298,12 @@ ThreadManager::~ThreadManager() { void ThreadManager::ArchiveThread() { ASSERT(lazily_archived_thread_.Equals(ThreadId::Invalid())); ASSERT(!IsArchived()); + ASSERT(IsLockedByCurrentThread()); ThreadState* state = GetFreeThreadState(); state->Unlink(); - Isolate::CurrentPerIsolateThreadData()->set_thread_state(state); + Isolate::PerIsolateThreadData* per_thread = + isolate_->FindOrAllocatePerThreadDataForThisThread(); + per_thread->set_thread_state(state); lazily_archived_thread_ = ThreadId::Current(); lazily_archived_thread_state_ = state; ASSERT(state->id().Equals(ThreadId::Invalid())); @@ -312,6 +313,7 @@ void ThreadManager::ArchiveThread() { void ThreadManager::EagerlyArchiveThread() { + ASSERT(IsLockedByCurrentThread()); ThreadState* state = lazily_archived_thread_state_; state->LinkInto(ThreadState::IN_USE_LIST); char* to = state->data(); @@ -319,7 +321,7 @@ void ThreadManager::EagerlyArchiveThread() { // in ThreadManager::Iterate(ObjectVisitor*). to = isolate_->handle_scope_implementer()->ArchiveThread(to); to = isolate_->ArchiveThread(to); - to = Relocatable::ArchiveState(to); + to = Relocatable::ArchiveState(isolate_, to); #ifdef ENABLE_DEBUGGER_SUPPORT to = isolate_->debug()->ArchiveDebug(to); #endif @@ -344,11 +346,11 @@ void ThreadManager::FreeThreadResources() { bool ThreadManager::IsArchived() { - Isolate::PerIsolateThreadData* data = Isolate::CurrentPerIsolateThreadData(); + Isolate::PerIsolateThreadData* data = + isolate_->FindPerThreadDataForThisThread(); return data != NULL && data->thread_state() != NULL; } - void ThreadManager::Iterate(ObjectVisitor* v) { // Expecting no threads during serialization/deserialization for (ThreadState* state = FirstThreadStateInUse(); diff --git a/test/cctest/SConscript b/test/cctest/SConscript index f4cb4a9a6..e2b0fc9f5 100644 --- a/test/cctest/SConscript +++ b/test/cctest/SConscript @@ -64,6 +64,7 @@ SOURCES = { 'test-list.cc', 'test-liveedit.cc', 'test-lock.cc', + 'test-lockers.cc', 'test-log-utils.cc', 'test-log.cc', 'test-mark-compact.cc', diff --git a/test/cctest/cctest.gyp b/test/cctest/cctest.gyp index aa2b35583..51a5fc7de 100644 --- a/test/cctest/cctest.gyp +++ b/test/cctest/cctest.gyp @@ -93,6 +93,7 @@ 'test-list.cc', 'test-liveedit.cc', 'test-lock.cc', + 'test-lockers.cc', 'test-log.cc', 'test-log-utils.cc', 'test-mark-compact.cc', diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index a228a6f49..c2ab00d7d 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -30,6 +30,7 @@ #include "v8.h" #include "api.h" +#include "isolate.h" #include "compilation-cache.h" #include "execution.h" #include "snapshot.h" @@ -13386,6 +13387,28 @@ TEST(MultipleIsolatesOnIndividualThreads) { isolate2->Dispose(); } +TEST(IsolateDifferentContexts) { + v8::Isolate* isolate = v8::Isolate::New(); + Persistent context; + { + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope; + context = v8::Context::New(); + v8::Context::Scope context_scope(context); + Local v = CompileRun("2"); + CHECK(v->IsNumber()); + CHECK_EQ(2, static_cast(v->NumberValue())); + } + { + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope; + context = v8::Context::New(); + v8::Context::Scope context_scope(context); + Local v = CompileRun("22"); + CHECK(v->IsNumber()); + CHECK_EQ(22, static_cast(v->NumberValue())); + } +} class InitDefaultIsolateThread : public v8::internal::Thread { public: diff --git a/test/cctest/test-lockers.cc b/test/cctest/test-lockers.cc new file mode 100644 index 000000000..a8bfe648d --- /dev/null +++ b/test/cctest/test-lockers.cc @@ -0,0 +1,605 @@ +// 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 + +#include "v8.h" + +#include "api.h" +#include "isolate.h" +#include "compilation-cache.h" +#include "execution.h" +#include "snapshot.h" +#include "platform.h" +#include "utils.h" +#include "cctest.h" +#include "parser.h" +#include "unicode-inl.h" + +using ::v8::AccessorInfo; +using ::v8::Context; +using ::v8::Extension; +using ::v8::Function; +using ::v8::HandleScope; +using ::v8::Local; +using ::v8::Object; +using ::v8::ObjectTemplate; +using ::v8::Persistent; +using ::v8::Script; +using ::v8::String; +using ::v8::Value; +using ::v8::V8; + +namespace i = ::i; + + + + +// Migrating an isolate +class KangarooThread : public v8::internal::Thread { + public: + KangarooThread(v8::Isolate* isolate, + v8::Handle context, int value) + : Thread(NULL, "KangarooThread"), + isolate_(isolate), context_(context), value_(value) { + } + + void Run() { + { + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + CHECK_EQ(isolate_, v8::internal::Isolate::Current()); + v8::HandleScope scope; + v8::Context::Scope context_scope(context_); + Local v = CompileRun("getValue()"); + CHECK(v->IsNumber()); + CHECK_EQ(30, static_cast(v->NumberValue())); + } + { + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::Context::Scope context_scope(context_); + v8::HandleScope scope; + Local v = CompileRun("getValue()"); + CHECK(v->IsNumber()); + CHECK_EQ(30, static_cast(v->NumberValue())); + } + isolate_->Dispose(); + } + + private: + v8::Isolate* isolate_; + Persistent context_; + int value_; +}; + +// Migrates an isolate from one thread to another +TEST(KangarooIsolates) { + v8::Isolate* isolate = v8::Isolate::New(); + Persistent context; + { + v8::Locker locker(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope; + context = v8::Context::New(); + v8::Context::Scope context_scope(context); + CHECK_EQ(isolate, v8::internal::Isolate::Current()); + CompileRun("function getValue() { return 30; }"); + } + KangarooThread thread1(isolate, context, 1); + thread1.Start(); + thread1.Join(); +} + +static void CalcFibAndCheck() { + Local 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(v->NumberValue())); +} + +class JoinableThread { + public: + explicit JoinableThread(const char* name) + : name_(name), + semaphore_(i::OS::CreateSemaphore(0)), + thread_(this) { + } + + virtual ~JoinableThread() { + delete semaphore_; + } + + void Start() { + thread_.Start(); + } + + void Join() { + semaphore_->Wait(); + } + + virtual void Run() = 0; + private: + class ThreadWithSemaphore : public i::Thread { + public: + explicit ThreadWithSemaphore(JoinableThread* joinable_thread) + : Thread(NULL, joinable_thread->name_), + joinable_thread_(joinable_thread) { + } + + virtual void Run() { + joinable_thread_->Run(); + joinable_thread_->semaphore_->Signal(); + } + + private: + JoinableThread* joinable_thread_; + }; + + const char* name_; + i::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) { + } + + virtual void Run() { + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope; + LocalContext local_context; + CHECK_EQ(isolate_, v8::internal::Isolate::Current()); + CalcFibAndCheck(); + } + private: + v8::Isolate* isolate_; +}; + +static void StartJoinAndDeleteThreads(const i::List& threads) { + for (int i = 0; i < threads.length(); i++) { + threads[i]->Start(); + } + for (int i = 0; i < threads.length(); i++) { + threads[i]->Join(); + } + for (int i = 0; i < threads.length(); i++) { + delete threads[i]; + } +} + + +// Run many threads all locking on the same isolate +TEST(IsolateLockingStress) { + const int kNThreads = 100; + i::List threads(kNThreads); + v8::Isolate* isolate = v8::Isolate::New(); + for (int i = 0; i < kNThreads; i++) { + threads.Add(new IsolateLockingThreadWithLocalContext(isolate)); + } + StartJoinAndDeleteThreads(threads); + isolate->Dispose(); +} + +class IsolateNonlockingThread : public JoinableThread { + public: + explicit IsolateNonlockingThread() + : JoinableThread("IsolateNonlockingThread") { + } + + virtual void Run() { + v8::Isolate* isolate = v8::Isolate::New(); + { + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope; + v8::Handle context = v8::Context::New(); + v8::Context::Scope context_scope(context); + CHECK_EQ(isolate, v8::internal::Isolate::Current()); + CalcFibAndCheck(); + } + isolate->Dispose(); + } + private: +}; + +// Run many threads each accessing its own isolate without locking +TEST(MultithreadedParallelIsolates) { + const int kNThreads = 50; + i::List threads(kNThreads); + for (int i = 0; i < kNThreads; i++) { + threads.Add(new IsolateNonlockingThread()); + } + StartJoinAndDeleteThreads(threads); +} + + +class IsolateNestedLockingThread : public JoinableThread { + public: + explicit IsolateNestedLockingThread(v8::Isolate* isolate) + : JoinableThread("IsolateNestedLocking"), isolate_(isolate) { + } + virtual void Run() { + v8::Locker lock(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope; + LocalContext local_context; + { + v8::Locker another_lock(isolate_); + CalcFibAndCheck(); + } + { + v8::Locker another_lock(isolate_); + CalcFibAndCheck(); + } + } + private: + v8::Isolate* isolate_; +}; + +// Run many threads with nested locks +TEST(IsolateNestedLocking) { + const int kNThreads = 100; + v8::Isolate* isolate = v8::Isolate::New(); + i::List threads(kNThreads); + for (int i = 0; i < kNThreads; i++) { + threads.Add(new IsolateNestedLockingThread(isolate)); + } + StartJoinAndDeleteThreads(threads); +} + + +class SeparateIsolatesLocksNonexclusiveThread : public JoinableThread { + public: + SeparateIsolatesLocksNonexclusiveThread(v8::Isolate* isolate1, + v8::Isolate* isolate2) + : JoinableThread("SeparateIsolatesLocksNonexclusiveThread"), + isolate1_(isolate1), isolate2_(isolate2) { + } + + virtual void Run() { + v8::Locker lock(isolate1_); + v8::Isolate::Scope isolate_scope(isolate1_); + v8::HandleScope handle_scope; + LocalContext local_context; + + IsolateLockingThreadWithLocalContext threadB(isolate2_); + threadB.Start(); + CalcFibAndCheck(); + threadB.Join(); + } + private: + v8::Isolate* isolate1_; + v8::Isolate* isolate2_; +}; + +// Run parallel threads that lock and access different isolates in parallel +TEST(SeparateIsolatesLocksNonexclusive) { + const int kNThreads = 100; + v8::Isolate* isolate1 = v8::Isolate::New(); + v8::Isolate* isolate2 = v8::Isolate::New(); + i::List threads(kNThreads); + for (int i = 0; i < kNThreads; i++) { + threads.Add(new SeparateIsolatesLocksNonexclusiveThread(isolate1, + isolate2)); + } + StartJoinAndDeleteThreads(threads); + isolate2->Dispose(); + isolate1->Dispose(); +} + +class LockIsolateAndCalculateFibSharedContextThread : public JoinableThread { + public: + explicit LockIsolateAndCalculateFibSharedContextThread( + v8::Isolate* isolate, v8::Handle context) + : JoinableThread("LockIsolateAndCalculateFibThread"), + isolate_(isolate), + context_(context) { + } + + virtual void Run() { + v8::Locker lock(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + HandleScope handle_scope; + v8::Context::Scope context_scope(context_); + CalcFibAndCheck(); + } + private: + v8::Isolate* isolate_; + Persistent context_; +}; + +class LockerUnlockerThread : public JoinableThread { + public: + explicit LockerUnlockerThread(v8::Isolate* isolate) + : JoinableThread("LockerUnlockerThread"), + isolate_(isolate) { + } + + virtual void Run() { + v8::Locker lock(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope; + v8::Handle context = v8::Context::New(); + { + v8::Context::Scope context_scope(context); + CalcFibAndCheck(); + } + { + isolate_->Exit(); + v8::Unlocker unlocker(isolate_); + LockIsolateAndCalculateFibSharedContextThread thread(isolate_, context); + thread.Start(); + thread.Join(); + } + isolate_->Enter(); + { + v8::Context::Scope context_scope(context); + CalcFibAndCheck(); + } + } + private: + v8::Isolate* isolate_; +}; + +// Use unlocker inside of a Locker, multiple threads. +TEST(LockerUnlocker) { + const int kNThreads = 100; + i::List threads(kNThreads); + v8::Isolate* isolate = v8::Isolate::New(); + for (int i = 0; i < kNThreads; i++) { + threads.Add(new LockerUnlockerThread(isolate)); + } + StartJoinAndDeleteThreads(threads); + isolate->Dispose(); +} + +class LockTwiceAndUnlockThread : public JoinableThread { + public: + explicit LockTwiceAndUnlockThread(v8::Isolate* isolate) + : JoinableThread("LockTwiceAndUnlockThread"), + isolate_(isolate) { + } + + virtual void Run() { + v8::Locker lock(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope; + v8::Handle context = v8::Context::New(); + { + v8::Context::Scope context_scope(context); + CalcFibAndCheck(); + } + { + v8::Locker second_lock(isolate_); + { + isolate_->Exit(); + v8::Unlocker unlocker(isolate_); + LockIsolateAndCalculateFibSharedContextThread thread(isolate_, context); + thread.Start(); + thread.Join(); + } + } + isolate_->Enter(); + { + v8::Context::Scope context_scope(context); + CalcFibAndCheck(); + } + } + private: + v8::Isolate* isolate_; +}; + +// Use Unlocker inside two Lockers. +TEST(LockTwiceAndUnlock) { + const int kNThreads = 100; + i::List threads(kNThreads); + v8::Isolate* isolate = v8::Isolate::New(); + for (int i = 0; i < kNThreads; i++) { + threads.Add(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) { + } + + virtual void Run() { + Persistent context1; + Persistent context2; + v8::Locker lock1(isolate1_); + CHECK(v8::Locker::IsLocked(isolate1_)); + CHECK(!v8::Locker::IsLocked(isolate2_)); + { + v8::Isolate::Scope isolate_scope(isolate1_); + v8::HandleScope handle_scope; + context1 = v8::Context::New(); + { + v8::Context::Scope context_scope(context1); + CalcFibAndCheck(); + } + } + v8::Locker lock2(isolate2_); + CHECK(v8::Locker::IsLocked(isolate1_)); + CHECK(v8::Locker::IsLocked(isolate2_)); + { + v8::Isolate::Scope isolate_scope(isolate2_); + v8::HandleScope handle_scope; + context2 = v8::Context::New(); + { + v8::Context::Scope context_scope(context2); + CalcFibAndCheck(); + } + } + { + v8::Unlocker unlock1(isolate1_); + CHECK(!v8::Locker::IsLocked(isolate1_)); + CHECK(v8::Locker::IsLocked(isolate2_)); + v8::Isolate::Scope isolate_scope(isolate2_); + v8::HandleScope handle_scope; + v8::Context::Scope context_scope(context2); + LockIsolateAndCalculateFibSharedContextThread thread(isolate1_, context1); + thread.Start(); + CalcFibAndCheck(); + thread.Join(); + } + } + private: + v8::Isolate* isolate1_; + v8::Isolate* isolate2_; +}; + +// Lock two isolates and unlock one of them. +TEST(LockAndUnlockDifferentIsolates) { + v8::Isolate* isolate1 = v8::Isolate::New(); + v8::Isolate* isolate2 = v8::Isolate::New(); + LockAndUnlockDifferentIsolatesThread thread(isolate1, isolate2); + thread.Start(); + thread.Join(); + isolate2->Dispose(); + isolate1->Dispose(); +} + +class LockUnlockLockThread : public JoinableThread { + public: + LockUnlockLockThread(v8::Isolate* isolate, v8::Handle context) + : JoinableThread("LockUnlockLockThread"), + isolate_(isolate), + context_(context) { + } + + virtual void Run() { + v8::Locker lock1(isolate_); + CHECK(v8::Locker::IsLocked(isolate_)); + CHECK(!v8::Locker::IsLocked()); + { + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope; + v8::Context::Scope context_scope(context_); + CalcFibAndCheck(); + } + { + v8::Unlocker unlock1(isolate_); + CHECK(!v8::Locker::IsLocked(isolate_)); + CHECK(!v8::Locker::IsLocked()); + { + v8::Locker lock2(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope; + CHECK(v8::Locker::IsLocked(isolate_)); + CHECK(!v8::Locker::IsLocked()); + v8::Context::Scope context_scope(context_); + CalcFibAndCheck(); + } + } + } + + private: + v8::Isolate* isolate_; + v8::Persistent context_; +}; + +// Locker inside an Unlocker inside a Locker. +TEST(LockUnlockLockMultithreaded) { + const int kNThreads = 100; + v8::Isolate* isolate = v8::Isolate::New(); + Persistent context; + { + v8::Locker locker_(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope; + context = v8::Context::New(); + } + i::List threads(kNThreads); + for (int i = 0; i < kNThreads; i++) { + threads.Add(new LockUnlockLockThread(isolate, context)); + } + StartJoinAndDeleteThreads(threads); +} + +class LockUnlockLockDefaultIsolateThread : public JoinableThread { + public: + LockUnlockLockDefaultIsolateThread(v8::Handle context) + : JoinableThread("LockUnlockLockDefaultIsolateThread"), + context_(context) { + } + + virtual void Run() { + v8::Locker lock1; + { + v8::HandleScope handle_scope; + v8::Context::Scope context_scope(context_); + CalcFibAndCheck(); + } + { + v8::Unlocker unlock1; + { + v8::Locker lock2; + v8::HandleScope handle_scope; + v8::Context::Scope context_scope(context_); + CalcFibAndCheck(); + } + } + } + + private: + v8::Persistent context_; +}; + +// Locker inside an Unlocker inside a Locker for default isolate. +TEST(LockUnlockLockDefaultIsolateMultithreaded) { + const int kNThreads = 100; + Persistent context; + { + v8::Locker locker_; + v8::HandleScope handle_scope; + context = v8::Context::New(); + } + i::List threads(kNThreads); + for (int i = 0; i < kNThreads; i++) { + threads.Add(new LockUnlockLockDefaultIsolateThread(context)); + } + StartJoinAndDeleteThreads(threads); +}