// Copyright 2018 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 BASE_TASK_TASK_SCHEDULER_TRACKED_REF_H_ #define BASE_TASK_TASK_SCHEDULER_TRACKED_REF_H_ #include #include "base/atomic_ref_count.h" #include "base/gtest_prod_util.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/synchronization/waitable_event.h" namespace base { namespace internal { // TrackedRefs are effectively a ref-counting scheme for objects that have a // single owner. // // Deletion is still controlled by the single owner but ~T() itself will block // until all the TrackedRefs handed by its TrackedRefFactory have been released // (by ~TrackedRef()). // // Just like WeakPtrFactory: TrackedRefFactory should be the last member of T // to ensure ~TrackedRefFactory() runs first in ~T(). // // The owner of a T should hence be certain that the last TrackedRefs to T are // already gone or on their way out before destroying it or ~T() will hang // (indicating a bug in the tear down logic -- proper refcounting on the other // hand would result in a leak). // // TrackedRefFactory only makes sense to use on types that are always leaked in // production but need to be torn down in tests (blocking destruction is // impractical in production -- ref. ScopedAllowBaseSyncPrimitivesForTesting // below). // // Why would we ever need such a thing? In task_scheduler there is a clear // ownership hierarchy with mostly single owners and little refcounting. In // production nothing is ever torn down so this isn't a problem. In tests // however we must JoinForTesting(). At that point, all the raw back T* refs // used by the worker threads are problematic because they can result in use- // after-frees if a worker outlives the deletion of its corresponding // TaskScheduler/TaskTracker/SchedulerWorkerPool/etc. // // JoinForTesting() isn't so hard when all workers are managed. But with cleanup // semantics (reclaiming a worker who's been idle for too long) it becomes // tricky because workers can go unaccounted for before they exit their main // (https://crbug.com/827615). // // For that reason and to clearly document the ownership model, task_scheduler // uses TrackedRefs. // // On top of being a clearer ownership model than proper refcounting, a hang in // tear down in a test with out-of-order tear down logic is much preferred to // letting its worker thread and associated constructs outlive the test // (potentially resulting in flakes in unrelated tests running later in the same // process). // // Note: While there's nothing task_scheduler specific about TrackedRefs it // requires an ownership model where all the TrackedRefs are released on other // threads in sync with ~T(). This isn't a typical use case beyond shutting down // TaskScheduler in tests and as such this is kept internal here for now. template class TrackedRefFactory; // TrackedRef can be used like a T*. template class TrackedRef { public: // Moveable and copyable. TrackedRef(TrackedRef&& other) : ptr_(other.ptr_), factory_(other.factory_) { // Null out |other_|'s factory so its destructor doesn't decrement // |live_tracked_refs_|. other.factory_ = nullptr; } TrackedRef(const TrackedRef& other) : ptr_(other.ptr_), factory_(other.factory_) { factory_->live_tracked_refs_.Increment(); } // Intentionally not assignable for now because it makes the logic slightly // convoluted and it's not a use case that makes sense for the types using // this at the moment. TrackedRef& operator=(TrackedRef&& other) = delete; TrackedRef& operator=(const TrackedRef& other) = delete; ~TrackedRef() { if (factory_ && !factory_->live_tracked_refs_.Decrement()) { DCHECK(factory_->ready_to_destroy_); DCHECK(!factory_->ready_to_destroy_->IsSignaled()); factory_->ready_to_destroy_->Signal(); } } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } explicit operator bool() const { return ptr_ != nullptr; } private: friend class TrackedRefFactory; TrackedRef(T* ptr, TrackedRefFactory* factory) : ptr_(ptr), factory_(factory) { factory_->live_tracked_refs_.Increment(); } T* ptr_; TrackedRefFactory* factory_; }; // TrackedRefFactory should be the last member of T. template class TrackedRefFactory { public: TrackedRefFactory(T* ptr) : ptr_(ptr), self_ref_(WrapUnique(new TrackedRef(ptr_, this))) { DCHECK(ptr_); } ~TrackedRefFactory() { // Enter the destruction phase. ready_to_destroy_ = std::make_unique(); // Release self-ref (if this was the last one it will signal the event right // away). self_ref_.reset(); ready_to_destroy_->Wait(); } TrackedRef GetTrackedRef() { // TrackedRefs cannot be obtained after |live_tracked_refs_| has already // reached zero. In other words, the owner of a TrackedRefFactory shouldn't // vend new TrackedRefs while it's being destroyed (owners of TrackedRefs // may still copy/move their refs around during the destruction phase). DCHECK(!live_tracked_refs_.IsZero()); return TrackedRef(ptr_, this); } private: friend class TrackedRef; FRIEND_TEST_ALL_PREFIXES(TrackedRefTest, CopyAndMoveSemantics); T* const ptr_; // The number of live TrackedRefs vended by this factory. AtomicRefCount live_tracked_refs_{0}; // Non-null during the destruction phase. Signaled once |live_tracked_refs_| // reaches 0. Note: while this could a direct member, only initializing it in // the destruction phase avoids keeping a handle open for the entire session. std::unique_ptr ready_to_destroy_; // TrackedRefFactory holds a TrackedRef as well to prevent // |live_tracked_refs_| from ever reaching zero before ~TrackedRefFactory(). std::unique_ptr> self_ref_; DISALLOW_COPY_AND_ASSIGN(TrackedRefFactory); }; } // namespace internal } // namespace base #endif // BASE_TASK_TASK_SCHEDULER_TRACKED_REF_H_