// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PPAPI_SHARED_IMPL_PROXY_LOCK_H_ #define PPAPI_SHARED_IMPL_PROXY_LOCK_H_ #include "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" #include "base/threading/thread_checker.h" #include "ppapi/shared_impl/ppapi_shared_export.h" namespace base { class Lock; } namespace content { class HostGlobals; } namespace ppapi { // This is the one lock to rule them all for the ppapi proxy. All PPB interface // functions that need to be synchronized should lock this lock on entry. This // is normally accomplished by using an appropriate Enter RAII object at the // beginning of each thunk function. // // TODO(dmichael): If this turns out to be too slow and contentious, we'll want // to use multiple locks. E.g., one for the var tracker, one for the resource // tracker, etc. class PPAPI_SHARED_EXPORT ProxyLock { public: // Return the global ProxyLock. Normally, you should not access this // directly but instead use ProxyAutoLock or ProxyAutoUnlock. But sometimes // you need access to the ProxyLock, for example to create a condition // variable. static base::Lock* Get(); // Acquire the proxy lock. If it is currently held by another thread, block // until it is available. If the lock has not been set using the 'Set' method, // this operation does nothing. That is the normal case for the host side; // see PluginResourceTracker for where the lock gets set for the out-of- // process plugin case. static void Acquire(); // Relinquish the proxy lock. If the lock has not been set, this does nothing. static void Release(); // Assert that the lock is owned by the current thread (in the plugin // process). Does nothing when running in-process (or in the host process). static void AssertAcquired(); static void AssertAcquiredDebugOnly() { #ifndef NDEBUG AssertAcquired(); #endif } // We have some unit tests where one thread pretends to be the host and one // pretends to be the plugin. This allows the lock to do nothing on only one // thread to support these tests. See TwoWayTest for more information. static void DisableLockingOnThreadForTest(); // Enables locking on the current thread. Although locking is enabled by // default, unit tests that rely on the lock being enabled should *still* // call this, since a previous test may have disabled locking. static void EnableLockingOnThreadForTest(); private: friend class content::HostGlobals; // On the host side, we do not lock. This must be called at most once at // startup, before other threads that may access the ProxyLock have had a // chance to run. static void DisableLocking(); DISALLOW_IMPLICIT_CONSTRUCTORS(ProxyLock); }; // A simple RAII class for locking the PPAPI proxy lock on entry and releasing // on exit. This is for simple interfaces that don't use the 'thunk' system, // such as PPB_Var and PPB_Core. class ProxyAutoLock { public: ProxyAutoLock() { ProxyLock::Acquire(); } ~ProxyAutoLock() { ProxyLock::Release(); } private: DISALLOW_COPY_AND_ASSIGN(ProxyAutoLock); }; // The inverse of the above; unlock on construction, lock on destruction. This // is useful for calling out to the plugin, when we need to unlock but ensure // that we re-acquire the lock when the plugin is returns or raises an // exception. class ProxyAutoUnlock { public: ProxyAutoUnlock() { ProxyLock::Release(); } ~ProxyAutoUnlock() { ProxyLock::Acquire(); } private: DISALLOW_COPY_AND_ASSIGN(ProxyAutoUnlock); }; // A set of function template overloads for invoking a function pointer while // the ProxyLock is unlocked. This assumes that the luck is held. // CallWhileUnlocked unlocks the ProxyLock just before invoking the given // function. The lock is immediately re-acquired when the invoked function // function returns. CallWhileUnlocked returns whatever the given function // returned. // // Example usage: // *result = CallWhileUnlocked(ppp_input_event_impl_->HandleInputEvent, // instance, // resource->pp_resource()); template ReturnType CallWhileUnlocked(ReturnType (*function)()) { ProxyAutoUnlock unlock; return function(); } template ReturnType CallWhileUnlocked(ReturnType (*function)(P1), const P1& p1) { ProxyAutoUnlock unlock; return function(p1); } template ReturnType CallWhileUnlocked(ReturnType (*function)(P1, P2), const P1& p1, const P2& p2) { ProxyAutoUnlock unlock; return function(p1, p2); } template ReturnType CallWhileUnlocked(ReturnType (*function)(P1, P2, P3), const P1& p1, const P2& p2, const P3& p3) { ProxyAutoUnlock unlock; return function(p1, p2, p3); } template ReturnType CallWhileUnlocked(ReturnType (*function)(P1, P2, P3, P4), const P1& p1, const P2& p2, const P3& p3, const P4& p4) { ProxyAutoUnlock unlock; return function(p1, p2, p3, p4); } template ReturnType CallWhileUnlocked(ReturnType (*function)(P1, P2, P3, P4, P5), const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { ProxyAutoUnlock unlock; return function(p1, p2, p3, p4, p5); } void PPAPI_SHARED_EXPORT CallWhileUnlocked(const base::Closure& closure); namespace internal { template class RunWhileLockedHelper; template <> class RunWhileLockedHelper { public: typedef base::Callback CallbackType; explicit RunWhileLockedHelper(const CallbackType& callback) : callback_(new CallbackType(callback)) { // Copying |callback| may adjust reference counts for bound Vars or // Resources; we should have the lock already. ProxyLock::AssertAcquired(); // CallWhileLocked and destruction might happen on a different thread from // creation. thread_checker_.DetachFromThread(); } void CallWhileLocked() { // Bind thread_checker_ to this thread so we can check in the destructor. DCHECK(thread_checker_.CalledOnValidThread()); ProxyAutoLock lock; { // Use a scope and local Callback to ensure that the callback is cleared // before the lock is released, even in the unlikely event that Run() // throws an exception. scoped_ptr temp_callback(callback_.Pass()); temp_callback->Run(); } } ~RunWhileLockedHelper() { // Check that the Callback is destroyed on the same thread as where // CallWhileLocked happened (if CallWhileLocked happened). DCHECK(thread_checker_.CalledOnValidThread()); // Here we read callback_ without the lock. This is why the callback must be // destroyed on the same thread where it runs. There are 2 cases where // callback_ will be NULL: // 1) This is the original RunWhileLockedHelper that RunWhileLocked // created. When it was copied somewhere else (e.g., to a MessageLoop // queue), callback_ was passed to the new copy, and the original // RunWhileLockedHelper's callback_ was set to NULL (since scoped_ptrs // only ever have 1 owner). In this case, we don't want to acquire the // lock, because we already have it. // 2) callback_ has already been run via CallWhileLocked. In this case, // there's no need to acquire the lock, because we don't touch any // shared data. if (callback_) { // If the callback was not run, we still need to have the lock when we // destroy the callback in case it had a Resource bound to it. This // ensures that the Resource's destructor is invoked only with the lock // held. // // Also: Resource and Var inherit RefCounted (not ThreadSafeRefCounted), // and these callbacks need to be usable on any thread. So we need to lock // when releasing the callback to avoid ref counting races. ProxyAutoLock lock; callback_.reset(); } } private: scoped_ptr callback_; // Used to ensure that the Callback is run and deleted on the same thread. base::ThreadChecker thread_checker_; }; template class RunWhileLockedHelper { public: typedef base::Callback CallbackType; explicit RunWhileLockedHelper(const CallbackType& callback) : callback_(new CallbackType(callback)) { ProxyLock::AssertAcquired(); thread_checker_.DetachFromThread(); } void CallWhileLocked(P1 p1) { DCHECK(thread_checker_.CalledOnValidThread()); ProxyAutoLock lock; { scoped_ptr temp_callback(callback_.Pass()); temp_callback->Run(p1); } } ~RunWhileLockedHelper() { DCHECK(thread_checker_.CalledOnValidThread()); if (callback_) { ProxyAutoLock lock; callback_.reset(); } } private: scoped_ptr callback_; base::ThreadChecker thread_checker_; }; template class RunWhileLockedHelper { public: typedef base::Callback CallbackType; explicit RunWhileLockedHelper(const CallbackType& callback) : callback_(new CallbackType(callback)) { ProxyLock::AssertAcquired(); thread_checker_.DetachFromThread(); } void CallWhileLocked(P1 p1, P2 p2) { DCHECK(thread_checker_.CalledOnValidThread()); ProxyAutoLock lock; { scoped_ptr temp_callback(callback_.Pass()); temp_callback->Run(p1, p2); } } ~RunWhileLockedHelper() { DCHECK(thread_checker_.CalledOnValidThread()); if (callback_) { ProxyAutoLock lock; callback_.reset(); } } private: scoped_ptr callback_; base::ThreadChecker thread_checker_; }; template class RunWhileLockedHelper { public: typedef base::Callback CallbackType; explicit RunWhileLockedHelper(const CallbackType& callback) : callback_(new CallbackType(callback)) { ProxyLock::AssertAcquired(); thread_checker_.DetachFromThread(); } void CallWhileLocked(P1 p1, P2 p2, P3 p3) { DCHECK(thread_checker_.CalledOnValidThread()); ProxyAutoLock lock; { scoped_ptr temp_callback(callback_.Pass()); temp_callback->Run(p1, p2, p3); } } ~RunWhileLockedHelper() { DCHECK(thread_checker_.CalledOnValidThread()); if (callback_) { ProxyAutoLock lock; callback_.reset(); } } private: scoped_ptr callback_; base::ThreadChecker thread_checker_; }; } // namespace internal // RunWhileLocked wraps the given Callback in a new Callback that, when invoked: // 1) Locks the ProxyLock. // 2) Runs the original Callback (forwarding arguments, if any). // 3) Clears the original Callback (while the lock is held). // 4) Unlocks the ProxyLock. // Note that it's important that the callback is cleared in step (3), in case // clearing the Callback causes a destructor (e.g., for a Resource) to run, // which should hold the ProxyLock to avoid data races. // // This is for cases where you want to run a task or store a Callback, but you // want to ensure that the ProxyLock is acquired for the duration of the task // that the Callback runs. // EXAMPLE USAGE: // GetMainThreadMessageLoop()->PostDelayedTask( // FROM_HERE, // RunWhileLocked(base::Bind(&CallbackWrapper, callback, result)), // delay_in_ms); // // In normal usage like the above, this all should "just work". However, if you // do something unusual, you may get a runtime crash due to deadlock. Here are // the ways that the returned Callback must be used to avoid a deadlock: // (1) copied to another Callback. After that, the original callback can be // destroyed with or without the proxy lock acquired, while the newly assigned // callback has to conform to these same restrictions. Or // (2) run without proxy lock acquired (e.g., being posted to a MessageLoop // and run there). The callback must be destroyed on the same thread where it // was run (but can be destroyed with or without the proxy lock acquired). Or // (3) destroyed without the proxy lock acquired. template inline base::Callback RunWhileLocked( const base::Callback& callback) { internal::RunWhileLockedHelper* helper = new internal::RunWhileLockedHelper(callback); return base::Bind( &internal::RunWhileLockedHelper::CallWhileLocked, base::Owned(helper)); } } // namespace ppapi #endif // PPAPI_SHARED_IMPL_PROXY_LOCK_H_