477 lines
19 KiB
C++
477 lines
19 KiB
C++
// 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 <type_traits>
|
|
|
|
#include "base/base_export.h"
|
|
#include "base/check.h"
|
|
#include "base/memory/weak_ptr.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"
|
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
|
|
|
namespace perfetto {
|
|
class EventContext;
|
|
}
|
|
|
|
namespace base {
|
|
|
|
class TaskObserver;
|
|
|
|
namespace sequence_manager {
|
|
|
|
using QueueName = ::perfetto::protos::pbzero::SequenceManagerTask::QueueName;
|
|
|
|
namespace internal {
|
|
class AssociatedThreadId;
|
|
class SequenceManagerImpl;
|
|
class TaskQueueImpl;
|
|
} // namespace internal
|
|
|
|
// TODO(kraynov): Make TaskQueue to actually be an interface for TaskQueueImpl
|
|
// and stop using ref-counting because we're no longer tied to task runner
|
|
// lifecycle and there's no other need for ref-counting either.
|
|
// NOTE: When TaskQueue gets automatically deleted on zero ref-count,
|
|
// TaskQueueImpl gets gracefully shutdown. It means that it doesn't get
|
|
// unregistered immediately and might accept some last minute tasks until
|
|
// SequenceManager will unregister it at some point. It's done to ensure that
|
|
// task queue always gets unregistered on the main thread.
|
|
class BASE_EXPORT TaskQueue : public RefCountedThreadSafe<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 absl::optional<WakeUp> GetNextAllowedWakeUp(
|
|
LazyNow* lazy_now,
|
|
absl::optional<WakeUp> next_desired_wake_up,
|
|
bool has_ready_task) = 0;
|
|
|
|
protected:
|
|
~Throttler() = default;
|
|
};
|
|
|
|
// Shuts down the queue. All tasks currently queued will be discarded.
|
|
virtual void ShutdownTaskQueue();
|
|
|
|
// 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;
|
|
};
|
|
|
|
// TODO(altimin): Make this private after TaskQueue/TaskQueueImpl refactoring.
|
|
TaskQueue(std::unique_ptr<internal::TaskQueueImpl> impl,
|
|
const TaskQueue::Spec& spec);
|
|
TaskQueue(const TaskQueue&) = delete;
|
|
TaskQueue& operator=(const TaskQueue&) = delete;
|
|
|
|
// 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.
|
|
// 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 TaskQueue;
|
|
explicit QueueEnabledVoter(scoped_refptr<TaskQueue> task_queue);
|
|
|
|
scoped_refptr<TaskQueue> const task_queue_;
|
|
bool enabled_;
|
|
};
|
|
|
|
// 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.
|
|
std::unique_ptr<QueueEnabledVoter> CreateQueueEnabledVoter();
|
|
|
|
// NOTE this must be called on the thread this TaskQueue was created by.
|
|
bool IsQueueEnabled() const;
|
|
|
|
// Returns true if the queue is completely empty.
|
|
bool IsEmpty() const;
|
|
|
|
// Returns the number of pending tasks in the queue.
|
|
size_t GetNumberOfPendingTasks() const;
|
|
|
|
// 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.
|
|
bool HasTaskToRunImmediatelyOrReadyDelayedTask() const;
|
|
|
|
// 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.
|
|
absl::optional<WakeUp> GetNextDesiredWakeUp();
|
|
|
|
// Can be called on any thread.
|
|
virtual const char* GetName() const;
|
|
|
|
// Serialise this object into a trace.
|
|
void WriteIntoTrace(perfetto::TracedValue context) const;
|
|
|
|
// Set the priority of the queue to |priority|. NOTE this must be called on
|
|
// the thread this TaskQueue was created by.
|
|
void SetQueuePriority(QueuePriority priority);
|
|
|
|
// 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.
|
|
QueuePriority GetQueuePriority() const;
|
|
|
|
// These functions can only be called on the same thread that the task queue
|
|
// manager executes its tasks on.
|
|
void AddTaskObserver(TaskObserver* task_observer);
|
|
void RemoveTaskObserver(TaskObserver* task_observer);
|
|
|
|
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.
|
|
void InsertFence(InsertFencePosition position);
|
|
|
|
// 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.
|
|
void InsertFenceAt(TimeTicks time);
|
|
|
|
// Removes any previously added fence and unblocks execution of any tasks
|
|
// blocked by it.
|
|
void RemoveFence();
|
|
|
|
// 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).
|
|
bool HasActiveFence();
|
|
|
|
// Returns true if the queue has a fence which is blocking execution of tasks.
|
|
bool BlockedByFence() const;
|
|
|
|
// Associates |throttler| to this queue. Only one throttler can be associated
|
|
// with this queue. |throttler| must outlive this TaskQueue, or remain valid
|
|
// until ResetThrottler().
|
|
void SetThrottler(Throttler* throttler);
|
|
// Disassociates the current throttler from this queue, if any.
|
|
void ResetThrottler();
|
|
|
|
// 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.
|
|
void UpdateWakeUp(LazyNow* lazy_now);
|
|
|
|
// 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.
|
|
void SetShouldReportPostedTasksWhenDisabled(bool should_report);
|
|
|
|
// Create a task runner for this TaskQueue which will annotate all
|
|
// posted tasks with the given task type.
|
|
// May be called on any thread.
|
|
// NOTE: Task runners don't hold a reference to a TaskQueue, hence,
|
|
// it's required to retain that reference to prevent automatic graceful
|
|
// shutdown. Unique ownership of task queues will fix this issue soon.
|
|
scoped_refptr<SingleThreadTaskRunner> CreateTaskRunner(TaskType task_type);
|
|
|
|
// Default task runner which doesn't annotate tasks with a task type.
|
|
const scoped_refptr<SingleThreadTaskRunner>& task_runner() const {
|
|
return default_task_runner_;
|
|
}
|
|
|
|
// Checks whether or not this TaskQueue has a TaskQueueImpl.
|
|
// TODO(crbug.com/1143007): Remove this method when TaskQueueImpl inherits
|
|
// from TaskQueue and TaskQueue no longer owns an Impl.
|
|
bool HasImpl() { return !!impl_; }
|
|
|
|
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.
|
|
void SetOnTaskStartedHandler(OnTaskStartedHandler handler);
|
|
|
|
// |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.
|
|
void SetOnTaskCompletedHandler(OnTaskCompletedHandler handler);
|
|
|
|
// 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]] std::unique_ptr<OnTaskPostedCallbackHandle>
|
|
AddOnTaskPostedHandler(OnTaskPostedHandler handler);
|
|
|
|
// Set a callback to fill trace event arguments associated with the task
|
|
// execution.
|
|
void SetTaskExecutionTraceLogger(TaskExecutionTraceLogger logger);
|
|
|
|
base::WeakPtr<TaskQueue> AsWeakPtr() {
|
|
return weak_ptr_factory_.GetWeakPtr();
|
|
}
|
|
|
|
protected:
|
|
virtual ~TaskQueue();
|
|
|
|
internal::TaskQueueImpl* GetTaskQueueImpl() const { return impl_.get(); }
|
|
|
|
private:
|
|
friend class RefCountedThreadSafe<TaskQueue>;
|
|
friend class internal::SequenceManagerImpl;
|
|
friend class internal::TaskQueueImpl;
|
|
|
|
void AddQueueEnabledVoter(bool voter_is_enabled);
|
|
void RemoveQueueEnabledVoter(bool voter_is_enabled);
|
|
bool AreAllQueueEnabledVotersEnabled() const;
|
|
void OnQueueEnabledVoteChanged(bool enabled);
|
|
|
|
bool IsOnMainThread() const;
|
|
|
|
// Shuts down the queue when there are no more tasks queued.
|
|
void ShutdownTaskQueueGracefully();
|
|
|
|
// TaskQueue has ownership of an underlying implementation but in certain
|
|
// cases (e.g. detached frames) their lifetime may diverge.
|
|
// This method should be used to take away the impl for graceful shutdown.
|
|
// TaskQueue will disregard any calls or posting tasks thereafter.
|
|
std::unique_ptr<internal::TaskQueueImpl> TakeTaskQueueImpl();
|
|
|
|
// |impl_| can be written to on the main thread but can be read from
|
|
// any thread.
|
|
// |impl_lock_| must be acquired when writing to |impl_| or when accessing
|
|
// it from non-main thread. Reading from the main thread does not require
|
|
// a lock.
|
|
mutable base::internal::CheckedLock impl_lock_{
|
|
base::internal::UniversalPredecessor{}};
|
|
std::unique_ptr<internal::TaskQueueImpl> impl_;
|
|
|
|
const WeakPtr<internal::SequenceManagerImpl> sequence_manager_;
|
|
|
|
const scoped_refptr<const internal::AssociatedThreadId> associated_thread_;
|
|
const scoped_refptr<SingleThreadTaskRunner> default_task_runner_;
|
|
|
|
int enabled_voter_count_ = 0;
|
|
int voter_count_ = 0;
|
|
QueueName name_;
|
|
|
|
base::WeakPtrFactory<TaskQueue> weak_ptr_factory_{this};
|
|
};
|
|
|
|
} // namespace sequence_manager
|
|
} // namespace base
|
|
|
|
#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_
|