// Copyright 2008 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 "v8.h" #include "api.h" #include "bootstrapper.h" #include "debug.h" #include "execution.h" #include "v8threads.h" #include "regexp-stack.h" namespace v8 { static internal::Thread::LocalStorageKey thread_state_key = internal::Thread::CreateThreadLocalKey(); static internal::Thread::LocalStorageKey thread_id_key = internal::Thread::CreateThreadLocalKey(); // Track whether this V8 instance has ever called v8::Locker. This allows the // API code to verify that the lock is always held when V8 is being entered. 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) { // Record that the Locker has been used at least once. active_ = true; // Get the big lock if necessary. if (!internal::ThreadManager::IsLockedByCurrentThread()) { internal::ThreadManager::Lock(); has_lock_ = true; // 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 (!internal::V8::IsRunning()) { V8::Initialize(); } // 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 (internal::ThreadManager::RestoreThread()) { top_level_ = false; } else { internal::ExecutionAccess access; internal::StackGuard::ClearThread(access); internal::StackGuard::InitThread(access); } } ASSERT(internal::ThreadManager::IsLockedByCurrentThread()); // Make sure this thread is assigned a thread id. internal::ThreadManager::AssignId(); } bool Locker::IsLocked() { return internal::ThreadManager::IsLockedByCurrentThread(); } Locker::~Locker() { ASSERT(internal::ThreadManager::IsLockedByCurrentThread()); if (has_lock_) { if (top_level_) { internal::ThreadManager::FreeThreadResources(); } else { internal::ThreadManager::ArchiveThread(); } internal::ThreadManager::Unlock(); } } Unlocker::Unlocker() { ASSERT(internal::ThreadManager::IsLockedByCurrentThread()); internal::ThreadManager::ArchiveThread(); internal::ThreadManager::Unlock(); } Unlocker::~Unlocker() { ASSERT(!internal::ThreadManager::IsLockedByCurrentThread()); internal::ThreadManager::Lock(); internal::ThreadManager::RestoreThread(); } void Locker::StartPreemption(int every_n_ms) { v8::internal::ContextSwitcher::StartPreemption(every_n_ms); } void Locker::StopPreemption() { v8::internal::ContextSwitcher::StopPreemption(); } namespace internal { bool ThreadManager::RestoreThread() { // 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_.IsSelf()) { lazily_archived_thread_.Initialize(ThreadHandle::INVALID); ASSERT(Thread::GetThreadLocal(thread_state_key) == lazily_archived_thread_state_); lazily_archived_thread_state_->set_id(kInvalidId); lazily_archived_thread_state_->LinkInto(ThreadState::FREE_LIST); lazily_archived_thread_state_ = NULL; Thread::SetThreadLocal(thread_state_key, NULL); return true; } // Make sure that the preemption thread cannot modify the thread state while // it is being archived or restored. ExecutionAccess access; // If there is another thread that was lazily archived then we have to really // archive it now. if (lazily_archived_thread_.IsValid()) { EagerlyArchiveThread(); } ThreadState* state = reinterpret_cast<ThreadState*>(Thread::GetThreadLocal(thread_state_key)); if (state == NULL) { // This is a new thread. StackGuard::InitThread(access); return false; } char* from = state->data(); from = HandleScopeImplementer::RestoreThread(from); from = Top::RestoreThread(from); from = Relocatable::RestoreState(from); #ifdef ENABLE_DEBUGGER_SUPPORT from = Debug::RestoreDebug(from); #endif from = StackGuard::RestoreStackGuard(from); from = RegExpStack::RestoreStack(from); from = Bootstrapper::RestoreState(from); Thread::SetThreadLocal(thread_state_key, NULL); if (state->terminate_on_restore()) { StackGuard::TerminateExecution(); state->set_terminate_on_restore(false); } state->set_id(kInvalidId); state->Unlink(); state->LinkInto(ThreadState::FREE_LIST); return true; } void ThreadManager::Lock() { mutex_->Lock(); mutex_owner_.Initialize(ThreadHandle::SELF); ASSERT(IsLockedByCurrentThread()); } void ThreadManager::Unlock() { mutex_owner_.Initialize(ThreadHandle::INVALID); mutex_->Unlock(); } static int ArchiveSpacePerThread() { return HandleScopeImplementer::ArchiveSpacePerThread() + Top::ArchiveSpacePerThread() + #ifdef ENABLE_DEBUGGER_SUPPORT Debug::ArchiveSpacePerThread() + #endif StackGuard::ArchiveSpacePerThread() + RegExpStack::ArchiveSpacePerThread() + Bootstrapper::ArchiveSpacePerThread() + Relocatable::ArchiveSpacePerThread(); } ThreadState* ThreadState::free_anchor_ = new ThreadState(); ThreadState* ThreadState::in_use_anchor_ = new ThreadState(); ThreadState::ThreadState() : id_(ThreadManager::kInvalidId), terminate_on_restore_(false), next_(this), previous_(this) { } void ThreadState::AllocateSpace() { data_ = NewArray<char>(ArchiveSpacePerThread()); } void ThreadState::Unlink() { next_->previous_ = previous_; previous_->next_ = next_; } void ThreadState::LinkInto(List list) { ThreadState* flying_anchor = list == FREE_LIST ? free_anchor_ : in_use_anchor_; next_ = flying_anchor->next_; previous_ = flying_anchor; flying_anchor->next_ = this; next_->previous_ = this; } ThreadState* ThreadState::GetFree() { ThreadState* gotten = free_anchor_->next_; if (gotten == free_anchor_) { ThreadState* new_thread_state = new ThreadState(); new_thread_state->AllocateSpace(); return new_thread_state; } return gotten; } // Gets the first in the list of archived threads. ThreadState* ThreadState::FirstInUse() { return in_use_anchor_->Next(); } ThreadState* ThreadState::Next() { if (next_ == in_use_anchor_) return NULL; return next_; } // Thread ids must start with 1, because in TLS having thread id 0 can't // be distinguished from not having a thread id at all (since NULL is // defined as 0.) int ThreadManager::last_id_ = 0; Mutex* ThreadManager::mutex_ = OS::CreateMutex(); ThreadHandle ThreadManager::mutex_owner_(ThreadHandle::INVALID); ThreadHandle ThreadManager::lazily_archived_thread_(ThreadHandle::INVALID); ThreadState* ThreadManager::lazily_archived_thread_state_ = NULL; void ThreadManager::ArchiveThread() { ASSERT(!lazily_archived_thread_.IsValid()); ASSERT(!IsArchived()); ThreadState* state = ThreadState::GetFree(); state->Unlink(); Thread::SetThreadLocal(thread_state_key, reinterpret_cast<void*>(state)); lazily_archived_thread_.Initialize(ThreadHandle::SELF); lazily_archived_thread_state_ = state; ASSERT(state->id() == kInvalidId); state->set_id(CurrentId()); ASSERT(state->id() != kInvalidId); } void ThreadManager::EagerlyArchiveThread() { ThreadState* state = lazily_archived_thread_state_; state->LinkInto(ThreadState::IN_USE_LIST); char* to = state->data(); // Ensure that data containing GC roots are archived first, and handle them // in ThreadManager::Iterate(ObjectVisitor*). to = HandleScopeImplementer::ArchiveThread(to); to = Top::ArchiveThread(to); to = Relocatable::ArchiveState(to); #ifdef ENABLE_DEBUGGER_SUPPORT to = Debug::ArchiveDebug(to); #endif to = StackGuard::ArchiveStackGuard(to); to = RegExpStack::ArchiveStack(to); to = Bootstrapper::ArchiveState(to); lazily_archived_thread_.Initialize(ThreadHandle::INVALID); lazily_archived_thread_state_ = NULL; } void ThreadManager::FreeThreadResources() { HandleScopeImplementer::FreeThreadResources(); Top::FreeThreadResources(); #ifdef ENABLE_DEBUGGER_SUPPORT Debug::FreeThreadResources(); #endif StackGuard::FreeThreadResources(); RegExpStack::FreeThreadResources(); Bootstrapper::FreeThreadResources(); } bool ThreadManager::IsArchived() { return Thread::HasThreadLocal(thread_state_key); } void ThreadManager::Iterate(ObjectVisitor* v) { // Expecting no threads during serialization/deserialization for (ThreadState* state = ThreadState::FirstInUse(); state != NULL; state = state->Next()) { char* data = state->data(); data = HandleScopeImplementer::Iterate(v, data); data = Top::Iterate(v, data); data = Relocatable::Iterate(v, data); } } void ThreadManager::MarkCompactPrologue(bool is_compacting) { for (ThreadState* state = ThreadState::FirstInUse(); state != NULL; state = state->Next()) { char* data = state->data(); data += HandleScopeImplementer::ArchiveSpacePerThread(); Top::MarkCompactPrologue(is_compacting, data); } } void ThreadManager::MarkCompactEpilogue(bool is_compacting) { for (ThreadState* state = ThreadState::FirstInUse(); state != NULL; state = state->Next()) { char* data = state->data(); data += HandleScopeImplementer::ArchiveSpacePerThread(); Top::MarkCompactEpilogue(is_compacting, data); } } int ThreadManager::CurrentId() { return Thread::GetThreadLocalInt(thread_id_key); } void ThreadManager::AssignId() { if (!HasId()) { ASSERT(Locker::IsLocked()); int thread_id = ++last_id_; ASSERT(thread_id > 0); // see the comment near last_id_ definition. Thread::SetThreadLocalInt(thread_id_key, thread_id); Top::set_thread_id(thread_id); } } bool ThreadManager::HasId() { return Thread::HasThreadLocal(thread_id_key); } void ThreadManager::TerminateExecution(int thread_id) { for (ThreadState* state = ThreadState::FirstInUse(); state != NULL; state = state->Next()) { if (thread_id == state->id()) { state->set_terminate_on_restore(true); } } } // This is the ContextSwitcher singleton. There is at most a single thread // running which delivers preemption events to V8 threads. ContextSwitcher* ContextSwitcher::singleton_ = NULL; ContextSwitcher::ContextSwitcher(int every_n_ms) : keep_going_(true), sleep_ms_(every_n_ms) { } // Set the scheduling interval of V8 threads. This function starts the // ContextSwitcher thread if needed. void ContextSwitcher::StartPreemption(int every_n_ms) { ASSERT(Locker::IsLocked()); if (singleton_ == NULL) { // If the ContextSwitcher thread is not running at the moment start it now. singleton_ = new ContextSwitcher(every_n_ms); singleton_->Start(); } else { // ContextSwitcher thread is already running, so we just change the // scheduling interval. singleton_->sleep_ms_ = every_n_ms; } } // Disable preemption of V8 threads. If multiple threads want to use V8 they // must cooperatively schedule amongst them from this point on. void ContextSwitcher::StopPreemption() { ASSERT(Locker::IsLocked()); if (singleton_ != NULL) { // The ContextSwitcher thread is running. We need to stop it and release // its resources. singleton_->keep_going_ = false; singleton_->Join(); // Wait for the ContextSwitcher thread to exit. // Thread has exited, now we can delete it. delete(singleton_); singleton_ = NULL; } } // Main loop of the ContextSwitcher thread: Preempt the currently running V8 // thread at regular intervals. void ContextSwitcher::Run() { while (keep_going_) { OS::Sleep(sleep_ms_); StackGuard::Preempt(); } } // Acknowledge the preemption by the receiving thread. void ContextSwitcher::PreemptionReceived() { ASSERT(Locker::IsLocked()); // There is currently no accounting being done for this. But could be in the // future, which is why we leave this in. } } // namespace internal } // namespace v8