| // Copyright 2018 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_TASK_QUEUE_H_ |
| #define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_ |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <type_traits> |
| |
| #include "base/base_export.h" |
| #include "base/check.h" |
| #include "base/task/common/checked_lock.h" |
| #include "base/task/common/lazy_now.h" |
| #include "base/task/sequence_manager/tasks.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/task_observer.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/base_tracing.h" |
| #include "base/trace_event/base_tracing_forward.h" |
| |
| namespace perfetto { |
| class EventContext; |
| } |
| |
| namespace base { |
| |
| class TaskObserver; |
| |
| namespace sequence_manager { |
| |
| using QueueName = ::perfetto::protos::pbzero::SequenceManagerTask::QueueName; |
| |
| namespace internal { |
| class SequenceManagerImpl; |
| class TaskQueueImpl; |
| } // namespace internal |
| |
| // A `TaskQueue` represents an ordered list of tasks sharing common properties, |
| // e.g. priority, throttling, etc. `TaskQueue`s are associated with a |
| // `SequenceManager` instance, which chooses the next task from its set of |
| // queues. `TaskQueue`s should typically be used on a single thread since most |
| // methods are not thread safe (enforeced via CHECKs), but cross-thread task |
| // posting is supported with thread-safe task runners. |
| // |
| // A `TaskQueue` is unregistered (stops accepting and running tasks) when either |
| // its associated `TaskQueue::Handle` or `SequenceManager` is destroyed. If the |
| // handle is destroyed while the `SequenceManager` is still alive, the |
| // `SequenceManager` takes ownership of the queue and schedules it for deletion |
| // after the current task finishes. Otherwise, if the handle outlives the |
| // sequence manager, the queue is destroyed when the handle is destroyed. |
| class BASE_EXPORT TaskQueue { |
| public: |
| // Interface that lets a task queue be throttled by changing the wake up time |
| // and optionally, by inserting fences. A wake up in this context is a |
| // notification at a given time that lets this TaskQueue know of newly ripe |
| // delayed tasks if it's enabled. By delaying the desired wake up time to a |
| // different allowed wake up time, the Throttler can hold off delayed tasks |
| // that would otherwise by allowed to run sooner. |
| class BASE_EXPORT Throttler { |
| public: |
| // Invoked when the TaskQueue's next allowed wake up time is reached and is |
| // enabled, even if blocked by a fence. That wake up is defined by the last |
| // value returned from GetNextAllowedWakeUp(). |
| // This is always called on the thread this TaskQueue is associated with. |
| virtual void OnWakeUp(LazyNow* lazy_now) = 0; |
| |
| // Invoked when the TaskQueue newly gets a pending immediate task and is |
| // enabled, even if blocked by a fence. Redundant calls are possible when |
| // the TaskQueue already had a pending immediate task. |
| // The implementation may use this to: |
| // - Restrict task execution by inserting/updating a fence. |
| // - Update the TaskQueue's next delayed wake up via UpdateWakeUp(). |
| // This allows the Throttler to perform additional operations later from |
| // OnWakeUp(). |
| // This is always called on the thread this TaskQueue is associated with. |
| virtual void OnHasImmediateTask() = 0; |
| |
| // Invoked when the TaskQueue is enabled and wants to know when to schedule |
| // the next delayed wake-up (which happens at least every time this queue is |
| // about to cause the next wake up) provided |next_desired_wake_up|, the |
| // wake-up for the next pending delayed task in this queue (pending delayed |
| // tasks that are ripe may be ignored), or nullopt if there's no pending |
| // delayed task. |has_ready_task| indicates whether there are immediate |
| // tasks or ripe delayed tasks. The implementation should return the next |
| // allowed wake up, or nullopt if no future wake-up is necessary. |
| // This is always called on the thread this TaskQueue is associated with. |
| virtual std::optional<WakeUp> GetNextAllowedWakeUp( |
| LazyNow* lazy_now, |
| std::optional<WakeUp> next_desired_wake_up, |
| bool has_ready_task) = 0; |
| |
| protected: |
| ~Throttler() = default; |
| }; |
| |
| // Wrapper around a `TaskQueue`, exposed by `SequenceManager` when creating a |
| // task queue. The handle owns the underlying queue and exposes it through a |
| // unique_ptr-like interface, and it's responsible for managing the queue's |
| // lifetime, ensuring the queue is properly unregistered with the queue's |
| // `SequenceManager` when the handle is destroyed. |
| class BASE_EXPORT Handle { |
| public: |
| Handle(); |
| |
| Handle(Handle&&); |
| Handle& operator=(Handle&&); |
| |
| ~Handle(); |
| |
| void reset(); |
| TaskQueue* get() const; |
| TaskQueue* operator->() const; |
| |
| explicit operator bool() const { return !!task_queue_; } |
| |
| private: |
| friend class internal::SequenceManagerImpl; |
| explicit Handle(std::unique_ptr<internal::TaskQueueImpl> task_queue); |
| |
| std::unique_ptr<internal::TaskQueueImpl> task_queue_; |
| WeakPtr<internal::SequenceManagerImpl> sequence_manager_; |
| }; |
| |
| // Queues with higher priority (smaller number) are selected to run before |
| // queues of lower priority. Note that there is no starvation protection, |
| // i.e., a constant stream of high priority work can mean that tasks in lower |
| // priority queues won't get to run. |
| using QueuePriority = uint8_t; |
| |
| // By default there is only a single priority. Sequences making use of |
| // priorities should parameterize the `SequenceManager` with the appropriate |
| // `SequenceManager::PrioritySettings`. |
| enum class DefaultQueuePriority : QueuePriority { |
| kNormalPriority = 0, |
| |
| // Must be the last entry. |
| kQueuePriorityCount = 1, |
| }; |
| |
| // Options for constructing a TaskQueue. |
| struct Spec { |
| explicit Spec(QueueName name) : name(name) {} |
| |
| Spec SetShouldMonitorQuiescence(bool should_monitor) { |
| should_monitor_quiescence = should_monitor; |
| return *this; |
| } |
| |
| Spec SetShouldNotifyObservers(bool run_observers) { |
| should_notify_observers = run_observers; |
| return *this; |
| } |
| |
| // Delayed fences require Now() to be sampled when posting immediate tasks |
| // which is not free. |
| Spec SetDelayedFencesAllowed(bool allow_delayed_fences) { |
| delayed_fence_allowed = allow_delayed_fences; |
| return *this; |
| } |
| |
| Spec SetNonWaking(bool non_waking_in) { |
| non_waking = non_waking_in; |
| return *this; |
| } |
| |
| QueueName name; |
| bool should_monitor_quiescence = false; |
| bool should_notify_observers = true; |
| bool delayed_fence_allowed = false; |
| bool non_waking = false; |
| }; |
| |
| // Information about task execution. |
| // |
| // Wall-time related methods (start_time, end_time, wall_duration) can be |
| // called only when |has_wall_time()| is true. |
| // Thread-time related mehtods (start_thread_time, end_thread_time, |
| // thread_duration) can be called only when |has_thread_time()| is true. |
| // |
| // start_* should be called after RecordTaskStart. |
| // end_* and *_duration should be called after RecordTaskEnd. |
| class BASE_EXPORT TaskTiming { |
| public: |
| enum class State { NotStarted, Running, Finished }; |
| enum class TimeRecordingPolicy { DoRecord, DoNotRecord }; |
| |
| TaskTiming(bool has_wall_time, bool has_thread_time); |
| |
| bool has_wall_time() const { return has_wall_time_; } |
| bool has_thread_time() const { return has_thread_time_; } |
| |
| base::TimeTicks start_time() const { |
| DCHECK(has_wall_time()); |
| return start_time_; |
| } |
| base::TimeTicks end_time() const { |
| DCHECK(has_wall_time()); |
| return end_time_; |
| } |
| base::TimeDelta wall_duration() const { |
| DCHECK(has_wall_time()); |
| return end_time_ - start_time_; |
| } |
| base::ThreadTicks start_thread_time() const { |
| DCHECK(has_thread_time()); |
| return start_thread_time_; |
| } |
| base::ThreadTicks end_thread_time() const { |
| DCHECK(has_thread_time()); |
| return end_thread_time_; |
| } |
| base::TimeDelta thread_duration() const { |
| DCHECK(has_thread_time()); |
| return end_thread_time_ - start_thread_time_; |
| } |
| |
| State state() const { return state_; } |
| |
| void RecordTaskStart(LazyNow* now); |
| void RecordTaskEnd(LazyNow* now); |
| |
| // Protected for tests. |
| protected: |
| State state_ = State::NotStarted; |
| |
| bool has_wall_time_; |
| bool has_thread_time_; |
| |
| base::TimeTicks start_time_; |
| base::TimeTicks end_time_; |
| base::ThreadTicks start_thread_time_; |
| base::ThreadTicks end_thread_time_; |
| }; |
| |
| // An interface that lets the owner vote on whether or not the associated |
| // TaskQueue should be enabled. |
| class BASE_EXPORT QueueEnabledVoter { |
| public: |
| ~QueueEnabledVoter(); |
| |
| QueueEnabledVoter(const QueueEnabledVoter&) = delete; |
| const QueueEnabledVoter& operator=(const QueueEnabledVoter&) = delete; |
| |
| // Votes to enable or disable the associated TaskQueue. The TaskQueue will |
| // only be enabled if all the voters agree it should be enabled, or if there |
| // are no voters. Voters don't keep the queue alive. |
| // NOTE this must be called on the thread the associated TaskQueue was |
| // created on. |
| void SetVoteToEnable(bool enabled); |
| |
| bool IsVotingToEnable() const { return enabled_; } |
| |
| private: |
| friend class internal::TaskQueueImpl; |
| explicit QueueEnabledVoter(WeakPtr<internal::TaskQueueImpl> task_queue); |
| |
| WeakPtr<internal::TaskQueueImpl> task_queue_; |
| bool enabled_ = true; |
| }; |
| |
| TaskQueue(const TaskQueue&) = delete; |
| TaskQueue& operator=(const TaskQueue&) = delete; |
| virtual ~TaskQueue() = default; |
| |
| // Returns an interface that allows the caller to vote on whether or not this |
| // TaskQueue is enabled. The TaskQueue will be enabled if there are no voters |
| // or if all agree it should be enabled. |
| // NOTE this must be called on the thread this TaskQueue was created by. |
| virtual std::unique_ptr<QueueEnabledVoter> CreateQueueEnabledVoter() = 0; |
| |
| // NOTE this must be called on the thread this TaskQueue was created by. |
| virtual bool IsQueueEnabled() const = 0; |
| |
| // Returns true if the queue is completely empty. |
| virtual bool IsEmpty() const = 0; |
| |
| // Returns the number of pending tasks in the queue. |
| virtual size_t GetNumberOfPendingTasks() const = 0; |
| |
| // Returns true iff this queue has immediate tasks or delayed tasks that are |
| // ripe for execution. Ignores the queue's enabled state and fences. |
| // NOTE: this must be called on the thread this TaskQueue was created by. |
| // TODO(etiennep): Rename to HasReadyTask() and add LazyNow parameter. |
| virtual bool HasTaskToRunImmediatelyOrReadyDelayedTask() const = 0; |
| |
| // Returns a wake-up for the next pending delayed task (pending delayed tasks |
| // that are ripe may be ignored), ignoring Throttler is any. If there are no |
| // such tasks (immediate tasks don't count) or the queue is disabled it |
| // returns nullopt. |
| // NOTE: this must be called on the thread this TaskQueue was created by. |
| virtual std::optional<WakeUp> GetNextDesiredWakeUp() = 0; |
| |
| // Can be called on any thread. |
| virtual const char* GetName() const = 0; |
| |
| // Set the priority of the queue to |priority|. NOTE this must be called on |
| // the thread this TaskQueue was created by. |
| virtual void SetQueuePriority(QueuePriority priority) = 0; |
| |
| // Same as above but with an enum value as the priority. |
| template <typename T, typename = typename std::enable_if_t<std::is_enum_v<T>>> |
| void SetQueuePriority(T priority) { |
| static_assert(std::is_same_v<std::underlying_type_t<T>, QueuePriority>, |
| "Enumerated priorites must have the same underlying type as " |
| "TaskQueue::QueuePriority"); |
| SetQueuePriority(static_cast<QueuePriority>(priority)); |
| } |
| |
| // Returns the current queue priority. |
| virtual QueuePriority GetQueuePriority() const = 0; |
| |
| // These functions can only be called on the same thread that the task queue |
| // manager executes its tasks on. |
| virtual void AddTaskObserver(TaskObserver* task_observer) = 0; |
| virtual void RemoveTaskObserver(TaskObserver* task_observer) = 0; |
| |
| enum class InsertFencePosition { |
| kNow, // Tasks posted on the queue up till this point further may run. |
| // All further tasks are blocked. |
| kBeginningOfTime, // No tasks posted on this queue may run. |
| }; |
| |
| // Inserts a barrier into the task queue which prevents tasks with an enqueue |
| // order greater than the fence from running until either the fence has been |
| // removed or a subsequent fence has unblocked some tasks within the queue. |
| // Note: delayed tasks get their enqueue order set once their delay has |
| // expired, and non-delayed tasks get their enqueue order set when posted. |
| // |
| // Fences come in three flavours: |
| // - Regular (InsertFence(NOW)) - all tasks posted after this moment |
| // are blocked. |
| // - Fully blocking (InsertFence(kBeginningOfTime)) - all tasks including |
| // already posted are blocked. |
| // - Delayed (InsertFenceAt(timestamp)) - blocks all tasks posted after given |
| // point in time (must be in the future). |
| // |
| // Only one fence can be scheduled at a time. Inserting a new fence |
| // will automatically remove the previous one, regardless of fence type. |
| virtual void InsertFence(InsertFencePosition position) = 0; |
| |
| // Delayed fences are only allowed for queues created with |
| // SetDelayedFencesAllowed(true) because this feature implies sampling Now() |
| // (which isn't free) for every PostTask, even those with zero delay. |
| virtual void InsertFenceAt(TimeTicks time) = 0; |
| |
| // Removes any previously added fence and unblocks execution of any tasks |
| // blocked by it. |
| virtual void RemoveFence() = 0; |
| |
| // Returns true if the queue has a fence but it isn't necessarily blocking |
| // execution of tasks (it may be the case if tasks enqueue order hasn't |
| // reached the number set for a fence). |
| virtual bool HasActiveFence() = 0; |
| |
| // Returns true if the queue has a fence which is blocking execution of tasks. |
| virtual bool BlockedByFence() const = 0; |
| |
| // Associates |throttler| to this queue. Only one throttler can be associated |
| // with this queue. |throttler| must outlive this TaskQueue, or remain valid |
| // until ResetThrottler(). |
| virtual void SetThrottler(Throttler* throttler) = 0; |
| // Disassociates the current throttler from this queue, if any. |
| virtual void ResetThrottler() = 0; |
| |
| // Updates the task queue's next wake up time in its time domain, taking into |
| // account the desired run time of queued tasks and policies enforced by the |
| // throttler if any. |
| virtual void UpdateWakeUp(LazyNow* lazy_now) = 0; |
| |
| // Controls whether or not the queue will emit traces events when tasks are |
| // posted to it while disabled. This only applies for the current or next |
| // period during which the queue is disabled. When the queue is re-enabled |
| // this will revert back to the default value of false. |
| virtual void SetShouldReportPostedTasksWhenDisabled(bool should_report) = 0; |
| |
| // Create a task runner for this TaskQueue which will annotate all |
| // posted tasks with the given task type. |
| // Must be called on the thread this task queue is associated with. |
| // |
| // NOTE: Task runners don't keep the TaskQueue alive, so task queues can be |
| // deleted with valid task runners. Posting a task in that case will fail. |
| virtual scoped_refptr<SingleThreadTaskRunner> CreateTaskRunner( |
| TaskType task_type) const = 0; |
| |
| // Default task runner which doesn't annotate tasks with a task type. |
| virtual const scoped_refptr<SingleThreadTaskRunner>& task_runner() const = 0; |
| |
| using OnTaskStartedHandler = |
| RepeatingCallback<void(const Task&, const TaskQueue::TaskTiming&)>; |
| using OnTaskCompletedHandler = |
| RepeatingCallback<void(const Task&, TaskQueue::TaskTiming*, LazyNow*)>; |
| using OnTaskPostedHandler = RepeatingCallback<void(const Task&)>; |
| using TaskExecutionTraceLogger = |
| RepeatingCallback<void(perfetto::EventContext&, const Task&)>; |
| |
| // Sets a handler to subscribe for notifications about started and completed |
| // tasks. |
| virtual void SetOnTaskStartedHandler(OnTaskStartedHandler handler) = 0; |
| |
| // |task_timing| may be passed in Running state and may not have the end time, |
| // so that the handler can run an additional task that is counted as a part of |
| // the main task. |
| // The handler can call TaskTiming::RecordTaskEnd, which is optional, to |
| // finalize the task, and use the resulting timing. |
| virtual void SetOnTaskCompletedHandler(OnTaskCompletedHandler handler) = 0; |
| |
| // RAII handle associated with an OnTaskPostedHandler. Unregisters the handler |
| // upon destruction. |
| class OnTaskPostedCallbackHandle { |
| public: |
| OnTaskPostedCallbackHandle(const OnTaskPostedCallbackHandle&) = delete; |
| OnTaskPostedCallbackHandle& operator=(const OnTaskPostedCallbackHandle&) = |
| delete; |
| virtual ~OnTaskPostedCallbackHandle() = default; |
| |
| protected: |
| OnTaskPostedCallbackHandle() = default; |
| }; |
| |
| // Add a callback for adding custom functionality for processing posted task. |
| // Callback will be dispatched while holding a scheduler lock. As a result, |
| // callback should not call scheduler APIs directly, as this can lead to |
| // deadlocks. For example, PostTask should not be called directly and |
| // ScopedDeferTaskPosting::PostOrDefer should be used instead. `handler` must |
| // not be a null callback. Must be called on the thread this task queue is |
| // associated with, and the handle returned must be destroyed on the same |
| // thread. |
| [[nodiscard]] virtual std::unique_ptr<OnTaskPostedCallbackHandle> |
| AddOnTaskPostedHandler(OnTaskPostedHandler handler) = 0; |
| |
| // Set a callback to fill trace event arguments associated with the task |
| // execution. |
| virtual void SetTaskExecutionTraceLogger(TaskExecutionTraceLogger logger) = 0; |
| |
| protected: |
| TaskQueue() = default; |
| }; |
| |
| } // namespace sequence_manager |
| } // namespace base |
| |
| #endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_ |