This implements per-isolate locking and unlocking, including tests
authordslomov@chromium.org <dslomov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 29 Apr 2011 21:26:03 +0000 (21:26 +0000)
committerdslomov@chromium.org <dslomov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 29 Apr 2011 21:26:03 +0000 (21:26 +0000)
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

14 files changed:
include/v8.h
src/api.cc
src/api.h
src/execution.cc
src/isolate.cc
src/isolate.h
src/objects.cc
src/objects.h
src/top.cc
src/v8threads.cc
test/cctest/SConscript
test/cctest/cctest.gyp
test/cctest/test-api.cc
test/cctest/test-lockers.cc [new file with mode: 0644]

index b621f0110a519378621cae992e59f6ddc8635049..98d0b5dba4a3f8e08d7326ce2f30363bc2885b88 100644 (file)
@@ -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_;
 
index 84c0be46c901a64bad6c9d859057028c84b7e264..0ec27fe423d05562e0bee744d2e3349fe8d5361f 100644 (file)
@@ -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);
 }
index 7423d28e2f807d9947604f7dc1503bb72d7aaf36..6ea2df906a3c894dc64dac355146ae744a11620e 100644 (file)
--- 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<internal::Object**> blocks_;
   // Used as a stack to keep track of entered contexts.
   List<Handle<Object> > entered_contexts_;
index eb26438fe87af6a1d264b3b9ad32ad0efab6e8e8..d511b9015882fea11911ef874ce308906ad42415 100644 (file)
@@ -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);
index 6fc671c50c404d66b11c7bfe92eed7883ac698e9..0b0f0bf58259cd68c20ddb508a274e8d5dc879ca 100644 (file)
@@ -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();
index 52c7145e2c79020ec70e7e58aa630fe4868e307f..af3a99a89facac121957020bbae355673cdc95d1 100644 (file)
@@ -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);
 };
index 7c2097fa30f763fb3edf6db72e740bc821bf62a1..b41b34c4a46a7372b89b9552a7434939d62000f6 100644 (file)
@@ -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<Relocatable**>(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<Relocatable**>(from));
   return from + ArchiveSpacePerThread();
 }
index 556aaa2b75603f55cbbbc5642a96944d8c997f0b..1e62ce0110f74dcb9ed66d8ba04574770a49f4d2 100644 (file)
@@ -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);
index f44de7ad68d41c308f27fa9674925d91d08294ad..6d68231351a2ed66d168a0d56f8c3e5cf1ba40c9 100644 (file)
@@ -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();
index 4b033fcf0bd703cf12b5bdccfb116784b5a31d9a..e2879748029785c0ec5fa196356cceccbbc11cc6 100644 (file)
@@ -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<i::Isolate*>(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<i::Isolate*>(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<i::Isolate*>(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();
index f4cb4a9a60b3d7df0d907406f86f5261c27d5cd2..e2b0fc9f51d40a83c8532d5a0edeb2c606929030 100644 (file)
@@ -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',
index aa2b355831ad90139dd09a6b5c1355310a299246..51a5fc7dec2a1c155de69120f9d0f96121654739 100644 (file)
@@ -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',
index a228a6f4999b6d02748aaac768944aaf10213f2c..c2ab00d7d4ef525d9911441f327b8e1e36ab464d 100644 (file)
@@ -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<v8::Context> context;
+  {
+    v8::Isolate::Scope isolate_scope(isolate);
+    v8::HandleScope handle_scope;
+    context = v8::Context::New();
+    v8::Context::Scope context_scope(context);
+    Local<Value> v = CompileRun("2");
+    CHECK(v->IsNumber());
+    CHECK_EQ(2, static_cast<int>(v->NumberValue()));
+  }
+  {
+    v8::Isolate::Scope isolate_scope(isolate);
+    v8::HandleScope handle_scope;
+    context = v8::Context::New();
+    v8::Context::Scope context_scope(context);
+    Local<Value> v = CompileRun("22");
+    CHECK(v->IsNumber());
+    CHECK_EQ(22, static_cast<int>(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 (file)
index 0000000..a8bfe64
--- /dev/null
@@ -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 <limits.h>
+
+#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<v8::Context> 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<Value> v = CompileRun("getValue()");
+      CHECK(v->IsNumber());
+      CHECK_EQ(30, static_cast<int>(v->NumberValue()));
+    }
+    {
+      v8::Locker locker(isolate_);
+      v8::Isolate::Scope isolate_scope(isolate_);
+      v8::Context::Scope context_scope(context_);
+      v8::HandleScope scope;
+      Local<Value> v = CompileRun("getValue()");
+      CHECK(v->IsNumber());
+      CHECK_EQ(30, static_cast<int>(v->NumberValue()));
+    }
+    isolate_->Dispose();
+  }
+
+ private:
+  v8::Isolate* isolate_;
+  Persistent<v8::Context> context_;
+  int value_;
+};
+
+// Migrates an isolate from one thread to another
+TEST(KangarooIsolates) {
+  v8::Isolate* isolate = v8::Isolate::New();
+  Persistent<v8::Context> 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<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()));
+}
+
+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<JoinableThread*>& 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<JoinableThread*> 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<v8::Context> 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<JoinableThread*> 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<JoinableThread*> 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<JoinableThread*> 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<v8::Context> 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<v8::Context> 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<v8::Context> 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<JoinableThread*> 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<v8::Context> 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<JoinableThread*> 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<v8::Context> context1;
+    Persistent<v8::Context> 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<v8::Context> 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<v8::Context> context_;
+};
+
+// Locker inside an Unlocker inside a Locker.
+TEST(LockUnlockLockMultithreaded) {
+  const int kNThreads = 100;
+  v8::Isolate* isolate = v8::Isolate::New();
+  Persistent<v8::Context> context;
+  {
+    v8::Locker locker_(isolate);
+    v8::Isolate::Scope isolate_scope(isolate);
+    v8::HandleScope handle_scope;
+    context = v8::Context::New();
+  }
+  i::List<JoinableThread*> 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<v8::Context> 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<v8::Context> context_;
+};
+
+// Locker inside an Unlocker inside a Locker for default isolate.
+TEST(LockUnlockLockDefaultIsolateMultithreaded) {
+  const int kNThreads = 100;
+  Persistent<v8::Context> context;
+  {
+    v8::Locker locker_;
+    v8::HandleScope handle_scope;
+    context = v8::Context::New();
+  }
+  i::List<JoinableThread*> threads(kNThreads);
+  for (int i = 0; i < kNThreads; i++) {
+    threads.Add(new LockUnlockLockDefaultIsolateThread(context));
+  }
+  StartJoinAndDeleteThreads(threads);
+}