// 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.

#ifndef BASE_TASK_SEQUENCE_MANAGER_WORK_TRACKER_H_
#define BASE_TASK_SEQUENCE_MANAGER_WORK_TRACKER_H_

#include <atomic>
#include <cstdint>

#include "base/base_export.h"
#include "base/memory/raw_ptr.h"
#include "base/synchronization/condition_variable.h"
#include "base/task/common/checked_lock.h"
#include "base/threading/thread_checker.h"

namespace base::sequence_manager::internal {

class WorkTracker;

// When `IsValid()`, this represents an authorization to execute work
// synchronously inside `RunOrPostTask`.
class BASE_EXPORT SyncWorkAuthorization {
 public:
  SyncWorkAuthorization(SyncWorkAuthorization&&);
  SyncWorkAuthorization& operator=(SyncWorkAuthorization&&);
  ~SyncWorkAuthorization();

  bool IsValid() const { return !!tracker_; }

 private:
  friend class WorkTracker;

  explicit SyncWorkAuthorization(WorkTracker* state);

  raw_ptr<WorkTracker> tracker_ = nullptr;
};

// Tracks queued and running work to support `RunOrPostTask`.
class BASE_EXPORT WorkTracker {
 public:
  WorkTracker();
  ~WorkTracker();

  // Controls whether `RunOrPostTask()` can run its callback synchronously when
  // no work is tracked by this. Don't allow this when work that is sequenced
  // with `RunOrPostTask()` may run without being tracked by methods below.
  void SetRunTaskSynchronouslyAllowed(bool can_run_tasks_synchronously);

  // Invoked before requesting to reload an empty immediate work queue. After
  // this, `RunOrPostTask()` can't run tasks synchronously until
  // `WillReloadImmediateWorkQueues()` and `OnIdle()` have been called in
  // sequence.
  void WillRequestReloadImmediateWorkQueue();

  // Invoked before reloading empty immediate work queues.
  void WillReloadImmediateWorkQueues();

  // Invoked before doing work. After this `RunOrPostTask()` can't run tasks
  // until `OnIdle()` is called. Work may begin even if immediate work queues
  // haven't be reloaded since the last `OnIdle()`, e.g. when a task queue is
  // enabled, when tasks are moved from the delayed incoming queue to the
  // delayed work queue or when the pump performs internal work.
  void OnBeginWork();

  // Invoked when the thread is out of work.
  void OnIdle();

  // Returns a valid `SyncWorkAuthorization` iff all these conditions are true:
  // - Explicitly allowed by `SetRunTaskSynchronouslyAllowed()`
  // - `WillReloadImmediateWorkQueues()` and `OnIdle()` were called in
  //   sequence after the last call to `WillRequestReloadImmediateWorkQueue()`
  // - `OnIdle()` was called after the last call to `OnBeginWork()`
  SyncWorkAuthorization TryAcquireSyncWorkAuthorization();

  // Asserts that there is work tracked by this, i.e.
  // `TryAcquireSyncWorkAuthorization()` would not grant a sync work
  // authorization even if allowed by `SetRunTaskSynchronouslyAllowed()`.
  void AssertHasWork();

 private:
  friend class SyncWorkAuthorization;

  void WaitNoSyncWork();

  // An atomic variable to track:
  // - Whether there is an unfulfilled request to reload immediate work queues.
  static constexpr uint32_t kImmediateWorkQueueNeedsReload = 1 << 0;
  // - Whether all work queues are empty and no work is running.
  static constexpr uint32_t kWorkQueuesEmptyAndNoWorkRunning = 1 << 1;
  // - Whether a valid `SyncWorkAuthorization` exists.
  static constexpr uint32_t kActiveSyncWork = 1 << 2;
  // - Whether a valid `SyncWorkAuthorization` can be granted when no work is
  //   tracked by `this`.
  static constexpr uint32_t kSyncWorkSupported = 1 << 3;
  std::atomic_uint32_t state_{kWorkQueuesEmptyAndNoWorkRunning};

  // Memory order for `state_`:
  //
  // Sync work must see all memory written before it was allowed. Similarly,
  // non-sync work must see all memory written by sync work. As a result:
  //
  // Operations that may allow sync work are std::memory_order_release:
  //    - Set `kWorkQueuesEmptyAndNoWorkRunning`
  //    - Set `kSyncWorkSupported`
  //
  // Operations that may allow non-sync work are `std::memory_order_release`:
  //    - Clear `kActiveSyncWork`
  //
  // Operations that precede sync work are `std::memory_order_acquire`:
  //    - Set `kActiveSyncWork`
  //
  // Operations that precede non-sync work are `std::memory_order_acquire`:
  //    - Check that `kActiveSyncWork` is not set.
  static constexpr std::memory_order kMemoryReleaseAllowWork =
      std::memory_order_release;
  static constexpr std::memory_order kMemoryAcquireBeforeWork =
      std::memory_order_acquire;
  static constexpr std::memory_order kMemoryRelaxedNotAllowOrBeforeWork =
      std::memory_order_relaxed;

  // Allows `OnBeginWork()` to wait until there is no more valid
  // `SyncWorkAuthorization`.
  base::internal::CheckedLock active_sync_work_lock_;
  ConditionVariable active_sync_work_cv_ =
      active_sync_work_lock_.CreateConditionVariable();

  THREAD_CHECKER(thread_checker_);
};

}  // namespace base::sequence_manager::internal

#endif  // BASE_TASK_SEQUENCE_MANAGER_WORK_TRACKER_H_
