| /* |
| * 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" |
| |
| |
| #if USE(PTHREADS) |
| #include <pthread.h> |
| #endif |
| |
| namespace JSC { |
| |
| Mutex* GlobalJSLock::s_sharedInstanceLock = 0; |
| |
| GlobalJSLock::GlobalJSLock() |
| { |
| s_sharedInstanceLock->lock(); |
| } |
| |
| GlobalJSLock::~GlobalJSLock() |
| { |
| s_sharedInstanceLock->unlock(); |
| } |
| |
| void GlobalJSLock::initialize() |
| { |
| s_sharedInstanceLock = new Mutex(); |
| } |
| |
| 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_ownerThread(0) |
| , m_lockCount(0) |
| , m_lockDropDepth(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 JSStack). |
| // Whenever a thread calls into JSC it starts using the JSStack from the |
| // previous 'high water mark' - the maximum point the stack has ever grown to |
| // (returned by JSStack::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 |
| // JSStack. 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() |
| { |
| if (m_lockDropDepth++) |
| return 0; |
| |
| return dropAllLocksUnconditionally(); |
| } |
| |
| unsigned JSLock::dropAllLocksUnconditionally() |
| { |
| unsigned lockCount = m_lockCount; |
| for (unsigned i = 0; i < lockCount; i++) |
| unlock(); |
| |
| return lockCount; |
| } |
| |
| void JSLock::grabAllLocks(unsigned lockCount) |
| { |
| for (unsigned i = 0; i < lockCount; i++) |
| lock(); |
| |
| m_lockDropDepth--; |
| } |
| |
| JSLock::DropAllLocks::DropAllLocks(ExecState* exec) |
| : m_lockCount(0) |
| , m_globalData(&exec->globalData()) |
| { |
| m_lockCount = m_globalData->apiLock().dropAllLocks(); |
| } |
| |
| JSLock::DropAllLocks::DropAllLocks(JSGlobalData* globalData) |
| : m_lockCount(0) |
| , m_globalData(globalData) |
| { |
| m_lockCount = m_globalData->apiLock().dropAllLocks(); |
| } |
| |
| JSLock::DropAllLocks::~DropAllLocks() |
| { |
| m_globalData->apiLock().grabAllLocks(m_lockCount); |
| } |
| |
| } // namespace JSC |