| // 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 <memory> | |
| #include <utility> | |
| #include "base/bind.h" | |
| #include "base/callback.h" | |
| #include "base/macros.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. | |
| class PPAPI_SHARED_EXPORT LockingDisablerForTest { | |
| public: | |
| LockingDisablerForTest(); | |
| ~LockingDisablerForTest(); | |
| }; | |
| 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 <class ReturnType> | |
| ReturnType CallWhileUnlocked(ReturnType (*function)()) { | |
| ProxyAutoUnlock unlock; | |
| return function(); | |
| } | |
| // Note we use 2 types for the params, even though for the most part we expect | |
| // A1 to match P1. We let the compiler determine if P1 can convert safely to | |
| // A1. This allows callers to avoid having to do things like | |
| // const_cast to add const. | |
| template <class ReturnType, class A1, class P1> | |
| ReturnType CallWhileUnlocked(ReturnType (*function)(A1), const P1& p1) { | |
| ProxyAutoUnlock unlock; | |
| return function(p1); | |
| } | |
| template <class ReturnType, class A1, class A2, class P1, class P2> | |
| ReturnType CallWhileUnlocked(ReturnType (*function)(A1, A2), | |
| const P1& p1, | |
| const P2& p2) { | |
| ProxyAutoUnlock unlock; | |
| return function(p1, p2); | |
| } | |
| template <class ReturnType, class A1, class A2, class A3, class P1, class P2, | |
| class P3> | |
| ReturnType CallWhileUnlocked(ReturnType (*function)(A1, A2, A3), | |
| const P1& p1, | |
| const P2& p2, | |
| const P3& p3) { | |
| ProxyAutoUnlock unlock; | |
| return function(p1, p2, p3); | |
| } | |
| template <class ReturnType, class A1, class A2, class A3, class A4, class P1, | |
| class P2, class P3, class P4> | |
| ReturnType CallWhileUnlocked(ReturnType (*function)(A1, A2, A3, A4), | |
| const P1& p1, | |
| const P2& p2, | |
| const P3& p3, | |
| const P4& p4) { | |
| ProxyAutoUnlock unlock; | |
| return function(p1, p2, p3, p4); | |
| } | |
| template <class ReturnType, class A1, class A2, class A3, class A4, class A5, | |
| class P1, class P2, class P3, class P4, class P5> | |
| ReturnType CallWhileUnlocked(ReturnType (*function)(A1, A2, A3, A4, A5), | |
| 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(base::OnceClosure closure); | |
| namespace internal { | |
| template <typename RunType> | |
| class RunWhileLockedHelper; | |
| // A helper class to ensure that a callback is always run and destroyed while | |
| // the ProxyLock is held. A callback that is bound with ref-counted Var or | |
| // Resource parameters may invoke methods on the VarTracker or the | |
| // ResourceTracker in its destructor, and these require the ProxyLock. | |
| template <> | |
| class RunWhileLockedHelper<void()> { | |
| public: | |
| typedef base::OnceCallback<void()> CallbackType; | |
| explicit RunWhileLockedHelper(CallbackType callback) | |
| : callback_(std::move(callback)) { | |
| // CallWhileLocked and destruction might happen on a different thread from | |
| // creation. | |
| thread_checker_.DetachFromThread(); | |
| } | |
| static void CallWhileLocked(std::unique_ptr<RunWhileLockedHelper> ptr) { | |
| // Bind thread_checker_ to this thread so we can check in the destructor. | |
| // *If* the callback gets invoked, it's important that RunWhileLockedHelper | |
| // is destroyed on the same thread (see the comments in the destructor). | |
| DCHECK(ptr->thread_checker_.CalledOnValidThread()); | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| { | |
| // 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. | |
| CallbackType temp_callback = std::move(ptr->callback_); | |
| std::move(temp_callback).Run(); | |
| } | |
| } | |
| ~RunWhileLockedHelper() { | |
| // Check that the Callback is destroyed on the same thread as where | |
| // CallWhileLocked happened if CallWhileLocked happened. If we weren't | |
| // invoked, thread_checked_ isn't bound to a thread. | |
| 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. Note that callback_ will be | |
| // NULL if it 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're in a case where the task queue | |
| // we got pushed to has been destroyed (e.g., the thread is shut down and | |
| // its MessageLoop destroyed before all tasks have run.) | |
| // | |
| // We still need to have the lock when we destroy the callback: | |
| // - Because Resource and Var inherit RefCounted (not | |
| // ThreadSafeRefCounted). | |
| // - Because if the callback owns the last ref to a Resource, it will | |
| // call the ResourceTracker and also the Resource's destructor, which | |
| // both require the ProxyLock. | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| callback_.Reset(); | |
| } | |
| } | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(RunWhileLockedHelper); | |
| CallbackType callback_; | |
| // Used to ensure that the Callback is run and deleted on the same thread. | |
| base::ThreadChecker thread_checker_; | |
| }; | |
| template <typename P1> | |
| class RunWhileLockedHelper<void(P1)> { | |
| public: | |
| typedef base::OnceCallback<void(P1)> CallbackType; | |
| explicit RunWhileLockedHelper(CallbackType callback) | |
| : callback_(std::move(callback)) { | |
| thread_checker_.DetachFromThread(); | |
| } | |
| static void CallWhileLocked(std::unique_ptr<RunWhileLockedHelper> ptr, | |
| P1 p1) { | |
| DCHECK(ptr->thread_checker_.CalledOnValidThread()); | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| { | |
| CallbackType temp_callback = std::move(ptr->callback_); | |
| std::move(temp_callback).Run(p1); | |
| } | |
| } | |
| ~RunWhileLockedHelper() { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| if (callback_) { | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| callback_.Reset(); | |
| } | |
| } | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(RunWhileLockedHelper); | |
| CallbackType callback_; | |
| base::ThreadChecker thread_checker_; | |
| }; | |
| template <typename P1, typename P2> | |
| class RunWhileLockedHelper<void(P1, P2)> { | |
| public: | |
| typedef base::OnceCallback<void(P1, P2)> CallbackType; | |
| explicit RunWhileLockedHelper(CallbackType callback) | |
| : callback_(std::move(callback)) { | |
| thread_checker_.DetachFromThread(); | |
| } | |
| static void CallWhileLocked(std::unique_ptr<RunWhileLockedHelper> ptr, | |
| P1 p1, | |
| P2 p2) { | |
| DCHECK(ptr->thread_checker_.CalledOnValidThread()); | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| { | |
| CallbackType temp_callback = std::move(ptr->callback_); | |
| std::move(temp_callback).Run(p1, p2); | |
| } | |
| } | |
| ~RunWhileLockedHelper() { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| if (callback_) { | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| callback_.Reset(); | |
| } | |
| } | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(RunWhileLockedHelper); | |
| CallbackType callback_; | |
| base::ThreadChecker thread_checker_; | |
| }; | |
| template <typename P1, typename P2, typename P3> | |
| class RunWhileLockedHelper<void(P1, P2, P3)> { | |
| public: | |
| typedef base::OnceCallback<void(P1, P2, P3)> CallbackType; | |
| explicit RunWhileLockedHelper(CallbackType callback) | |
| : callback_(std::move(callback)) { | |
| thread_checker_.DetachFromThread(); | |
| } | |
| static void CallWhileLocked(std::unique_ptr<RunWhileLockedHelper> ptr, | |
| P1 p1, | |
| P2 p2, | |
| P3 p3) { | |
| DCHECK(ptr->thread_checker_.CalledOnValidThread()); | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| { | |
| CallbackType temp_callback = std::move(ptr->callback_); | |
| std::move(temp_callback).Run(p1, p2, p3); | |
| } | |
| } | |
| ~RunWhileLockedHelper() { | |
| DCHECK(thread_checker_.CalledOnValidThread()); | |
| if (callback_) { | |
| ProxyAutoLock lock; | |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. | |
| // |lock| above protects the access to Resource instances. | |
| base::ScopedAllowCrossThreadRefCountAccess | |
| allow_cross_thread_ref_count_access; | |
| callback_.Reset(); | |
| } | |
| } | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(RunWhileLockedHelper); | |
| CallbackType 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::BindOnce(&CallbackWrapper, std::move(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 <class FunctionType> | |
| inline base::OnceCallback<FunctionType> RunWhileLocked( | |
| base::OnceCallback<FunctionType> callback) { | |
| // NOTE: the reason we use "scoped_ptr" here instead of letting the callback | |
| // own it via base::Owned is kind of subtle. Imagine for the moment that we | |
| // call RunWhileLocked without the ProxyLock: | |
| // { | |
| // base::OnceCallback<void ()> local_callback = base::BinOnced(&Foo); | |
| // some_task_runner.PostTask(FROM_HERE, | |
| // RunWhileLocked(std::move(local_callback))); | |
| // } | |
| // In this case, since we don't have a lock synchronizing us, it's possible | |
| // for the callback to run on the other thread before we return and destroy | |
| // |local_callback|. The important thing here is that even though the other | |
| // thread gets a copy of the callback, the internal "BindState" of the | |
| // callback is refcounted and shared between all copies of the callback. So | |
| // in that case, if we used base::Owned, we might delete RunWhileLockedHelper | |
| // on this thread, which will violate the RunWhileLockedHelper's assumption | |
| // that it is destroyed on the same thread where it is run. | |
| std::unique_ptr<internal::RunWhileLockedHelper<FunctionType>> helper( | |
| new internal::RunWhileLockedHelper<FunctionType>(std::move(callback))); | |
| return base::BindOnce( | |
| &internal::RunWhileLockedHelper<FunctionType>::CallWhileLocked, | |
| std::move(helper)); | |
| } | |
| } // namespace ppapi | |
| #endif // PPAPI_SHARED_IMPL_PROXY_LOCK_H_ |