| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/task/sequence_manager/work_tracker.h" |
| |
| #include "base/check.h" |
| #include "base/task/common/scoped_defer_task_posting.h" |
| #include "base/threading/thread_restrictions.h" |
| |
| namespace base::sequence_manager::internal { |
| |
| SyncWorkAuthorization::SyncWorkAuthorization(SyncWorkAuthorization&& other) |
| : tracker_(other.tracker_) { |
| other.tracker_ = nullptr; |
| } |
| |
| SyncWorkAuthorization& SyncWorkAuthorization::operator=( |
| SyncWorkAuthorization&& other) { |
| tracker_ = other.tracker_; |
| other.tracker_ = nullptr; |
| return *this; |
| } |
| |
| SyncWorkAuthorization::~SyncWorkAuthorization() { |
| if (!tracker_) { |
| return; |
| } |
| |
| { |
| base::internal::CheckedAutoLock auto_lock(tracker_->active_sync_work_lock_); |
| uint32_t prev = tracker_->state_.fetch_and( |
| ~WorkTracker::kActiveSyncWork, WorkTracker::kMemoryReleaseAllowWork); |
| DCHECK(prev & WorkTracker::kActiveSyncWork); |
| } |
| |
| tracker_->active_sync_work_cv_.Signal(); |
| } |
| |
| SyncWorkAuthorization::SyncWorkAuthorization(WorkTracker* state) |
| : tracker_(state) {} |
| |
| WorkTracker::WorkTracker() { |
| DETACH_FROM_THREAD(thread_checker_); |
| } |
| |
| WorkTracker::~WorkTracker() = default; |
| |
| void WorkTracker::SetRunTaskSynchronouslyAllowed( |
| bool can_run_tasks_synchronously) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (can_run_tasks_synchronously) { |
| state_.fetch_or(kSyncWorkSupported, kMemoryReleaseAllowWork); |
| } else { |
| // After this returns, non-sync work may run without being tracked by |
| // `this`. Ensures that such work is correctly sequenced with sync work by: |
| // - Waiting until sync work is complete. |
| // - Acquiring memory written by sync work (`kMemoryAcquireBeforeWork` here |
| // is paired with `kMemoryReleaseAllowWork` in `~SyncWorkAuthorization`). |
| uint32_t prev = |
| state_.fetch_and(~kSyncWorkSupported, kMemoryAcquireBeforeWork); |
| if (prev & kActiveSyncWork) { |
| WaitNoSyncWork(); |
| } |
| } |
| } |
| |
| void WorkTracker::WaitNoSyncWork() { |
| // Do not process new PostTasks, defer them. Tracing can call PostTask, but |
| // it will try to grab locks that are not allowed here. |
| ScopedDeferTaskPosting disallow_task_posting; |
| ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow; |
| // `std::memory_order_relaxed` instead of `kMemoryAcquireBeforeWork` because |
| // the lock implicitly acquires memory released by `~SyncWorkAuthorization`. |
| base::internal::CheckedAutoLock auto_lock(active_sync_work_lock_); |
| uint32_t prev = state_.load(std::memory_order_relaxed); |
| while (prev & kActiveSyncWork) { |
| active_sync_work_cv_.Wait(); |
| prev = state_.load(std::memory_order_relaxed); |
| } |
| } |
| |
| void WorkTracker::WillRequestReloadImmediateWorkQueue() { |
| // May be called from any thread. |
| |
| // Sync work is disallowed until `WillReloadImmediateWorkQueues()` and |
| // `OnIdle()` are called. |
| state_.fetch_or(kImmediateWorkQueueNeedsReload, |
| kMemoryRelaxedNotAllowOrBeforeWork); |
| } |
| |
| void WorkTracker::WillReloadImmediateWorkQueues() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // Sync work is disallowed until `OnIdle()` is called. |
| state_.fetch_and( |
| ~(kImmediateWorkQueueNeedsReload | kWorkQueuesEmptyAndNoWorkRunning), |
| kMemoryRelaxedNotAllowOrBeforeWork); |
| } |
| |
| void WorkTracker::OnBeginWork() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| uint32_t prev = state_.fetch_and(~kWorkQueuesEmptyAndNoWorkRunning, |
| kMemoryAcquireBeforeWork); |
| if (prev & kActiveSyncWork) { |
| DCHECK(prev & kSyncWorkSupported); |
| WaitNoSyncWork(); |
| } |
| } |
| |
| void WorkTracker::OnIdle() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // This may allow sync work. "release" so that sync work that runs after this |
| // sees all writes issued by previous sequenced work. |
| state_.fetch_or(kWorkQueuesEmptyAndNoWorkRunning, std::memory_order_release); |
| } |
| |
| SyncWorkAuthorization WorkTracker::TryAcquireSyncWorkAuthorization() { |
| // May be called from any thread. |
| |
| uint32_t state = state_.load(std::memory_order_relaxed); |
| // "acquire" so that sync work sees writes issued by sequenced work that |
| // precedes it. |
| if (state == (kSyncWorkSupported | kWorkQueuesEmptyAndNoWorkRunning) && |
| state_.compare_exchange_strong(state, state | kActiveSyncWork, |
| std::memory_order_acquire, |
| std::memory_order_relaxed)) { |
| return SyncWorkAuthorization(this); |
| } |
| |
| return SyncWorkAuthorization(nullptr); |
| } |
| |
| void WorkTracker::AssertHasWork() { |
| CHECK(!(state_.load(std::memory_order_relaxed) & |
| kWorkQueuesEmptyAndNoWorkRunning)); |
| } |
| |
| } // namespace base::sequence_manager::internal |