/* * Copyright (C) 2005, 2008 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the NU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA * */ #include "config.h" #include "JSLock.h" #include "Heap.h" #include "CallFrame.h" #include "JSGlobalObject.h" #include "JSObject.h" #include "ScopeChain.h" #if USE(PTHREADS) #include <pthread.h> #endif namespace JSC { // JSLock is only needed to support an obsolete execution model where JavaScriptCore // automatically protected against concurrent access from multiple threads. // So it's safe to disable it on non-mac platforms where we don't have native pthreads. #if (OS(DARWIN) || USE(PTHREADS)) static pthread_mutex_t sharedInstanceLock = PTHREAD_MUTEX_INITIALIZER; GlobalJSLock::GlobalJSLock() { pthread_mutex_lock(&sharedInstanceLock); } GlobalJSLock::~GlobalJSLock() { pthread_mutex_unlock(&sharedInstanceLock); } JSLockHolder::JSLockHolder(ExecState* exec) : m_globalData(&exec->globalData()) { m_globalData->apiLock().lock(); } JSLockHolder::JSLockHolder(JSGlobalData* globalData) : m_globalData(globalData) { m_globalData->apiLock().lock(); } JSLockHolder::JSLockHolder(JSGlobalData& globalData) : m_globalData(&globalData) { m_globalData->apiLock().lock(); } JSLockHolder::~JSLockHolder() { m_globalData->apiLock().unlock(); } JSLock::JSLock() : m_lockCount(0) { m_spinLock.Init(); } JSLock::~JSLock() { } void JSLock::lock() { ThreadIdentifier currentThread = WTF::currentThread(); { SpinLockHolder holder(&m_spinLock); if (m_ownerThread == currentThread && m_lockCount) { m_lockCount++; return; } } m_lock.lock(); { SpinLockHolder holder(&m_spinLock); m_ownerThread = currentThread; ASSERT(!m_lockCount); m_lockCount = 1; } } void JSLock::unlock() { ASSERT(currentThreadIsHoldingLock()); SpinLockHolder holder(&m_spinLock); m_lockCount--; if (!m_lockCount) m_lock.unlock(); } void JSLock::lock(ExecState* exec) { exec->globalData().apiLock().lock(); } void JSLock::unlock(ExecState* exec) { exec->globalData().apiLock().unlock(); } bool JSLock::currentThreadIsHoldingLock() { return m_lockCount && m_ownerThread == WTF::currentThread(); } // This is fairly nasty. We allow multiple threads to run on the same // context, and we do not require any locking semantics in doing so - // clients of the API may simply use the context from multiple threads // concurently, and assume this will work. In order to make this work, // We lock the context when a thread enters, and unlock it when it leaves. // However we do not only unlock when the thread returns from its // entry point (evaluate script or call function), we also unlock the // context if the thread leaves JSC by making a call out to an external // function through a callback. // // All threads using the context share the same JS stack (the RegisterFile). // Whenever a thread calls into JSC it starts using the RegisterFile from the // previous 'high water mark' - the maximum point the stack has ever grown to // (returned by RegisterFile::end()). So if a first thread calls out to a // callback, and a second thread enters JSC, then also exits by calling out // to a callback, we can be left with stackframes from both threads in the // RegisterFile. As such, a problem may occur should the first thread's // callback complete first, and attempt to return to JSC. Were we to allow // this to happen, and were its stack to grow further, then it may potentially // write over the second thread's call frames. // // To avoid JS stack corruption we enforce a policy of only ever allowing two // threads to use a JS context concurrently, and only allowing the second of // these threads to execute until it has completed and fully returned from its // outermost call into JSC. We enforce this policy using 'lockDropDepth'. The // first time a thread exits it will call DropAllLocks - which will do as expected // and drop locks allowing another thread to enter. Should another thread, or the // same thread again, enter JSC (through evaluate script or call function), and exit // again through a callback, then the locks will not be dropped when DropAllLocks // is called (since lockDropDepth is non-zero). Since this thread is still holding // the locks, only it will be able to re-enter JSC (either be returning from the // callback, or by re-entering through another call to evaulate script or call // function). // // This policy is slightly more restricive than it needs to be for correctness - // we could validly allow futher entries into JSC from other threads, we only // need ensure that callbacks return in the reverse chronological order of the // order in which they were made - though implementing the less restrictive policy // would likely increase complexity and overhead. // // This function returns the number of locks that were dropped. unsigned JSLock::dropAllLocks() { unsigned lockCount; { // Check if this thread is currently holding the lock. // FIXME: Maybe we want to require this, guard with an ASSERT? SpinLockHolder holder(&m_spinLock); lockCount = m_lockCount; if (!lockCount || m_ownerThread != WTF::currentThread()) return 0; } // Don't drop the locks if they've already been dropped once. // (If the prior drop came from another thread, and it resumed first, // it could trash our register file). if (m_lockDropDepth) return 0; // m_lockDropDepth is only incremented if any locks were dropped. m_lockDropDepth++; m_lockCount = 0; m_lock.unlock(); return lockCount; } unsigned JSLock::dropAllLocksUnconditionally() { unsigned lockCount; { // Check if this thread is currently holding the lock. // FIXME: Maybe we want to require this, guard with an ASSERT? SpinLockHolder holder(&m_spinLock); lockCount = m_lockCount; if (!lockCount || m_ownerThread != WTF::currentThread()) return 0; } // m_lockDropDepth is only incremented if any locks were dropped. m_lockDropDepth++; m_lockCount = 0; m_lock.unlock(); return lockCount; } void JSLock::grabAllLocks(unsigned lockCount) { // If no locks were dropped, nothing to do! if (!lockCount) return; ThreadIdentifier currentThread = WTF::currentThread(); { // Check if this thread is currently holding the lock. // FIXME: Maybe we want to prohibit this, guard against with an ASSERT? SpinLockHolder holder(&m_spinLock); if (m_ownerThread == currentThread && m_lockCount) { m_lockCount += lockCount; m_lockDropDepth--; return; } } m_lock.lock(); { SpinLockHolder holder(&m_spinLock); m_ownerThread = currentThread; ASSERT(!m_lockCount); m_lockCount = lockCount; m_lockDropDepth--; } } JSLock::DropAllLocks::DropAllLocks(ExecState* exec, AlwaysDropLocksTag alwaysDropLocks) : m_lockCount(0) , m_globalData(&exec->globalData()) { if (alwaysDropLocks) m_lockCount = m_globalData->apiLock().dropAllLocksUnconditionally(); else m_lockCount = m_globalData->apiLock().dropAllLocks(); } JSLock::DropAllLocks::DropAllLocks(JSGlobalData* globalData, AlwaysDropLocksTag alwaysDropLocks) : m_lockCount(0) , m_globalData(globalData) { if (alwaysDropLocks) m_lockCount = m_globalData->apiLock().dropAllLocksUnconditionally(); else m_lockCount = m_globalData->apiLock().dropAllLocks(); } JSLock::DropAllLocks::~DropAllLocks() { m_globalData->apiLock().grabAllLocks(m_lockCount); } #else // (OS(DARWIN) || USE(PTHREADS)) GlobalJSLock::GlobalJSLock() { } GlobalJSLock::~GlobalJSLock() { } JSLockHolder::JSLockHolder(JSGlobalData*) { } JSLockHolder::JSLockHolder(JSGlobalData&) { } JSLockHolder::JSLockHolder(ExecState*) { } JSLockHolder::~JSLockHolder() { } JSLock::JSLock() { } JSLock::~JSLock() { } bool JSLock::currentThreadIsHoldingLock() { return true; } void JSLock::lock() { } void JSLock::unlock() { } void JSLock::lock(ExecState*) { } void JSLock::unlock(ExecState*) { } void JSLock::lock(JSGlobalData&) { } void JSLock::unlock(JSGlobalData&) { } unsigned JSLock::dropAllLocks() { return 0; } unsigned JSLock::dropAllLocksUnconditionally() { return 0; } void JSLock::grabAllLocks(unsigned) { } JSLock::DropAllLocks::DropAllLocks(ExecState*) { } JSLock::DropAllLocks::DropAllLocks(JSGlobalData*) { } JSLock::DropAllLocks::~DropAllLocks() { } #endif // (OS(DARWIN) || USE(PTHREADS)) } // namespace JSC