blob: e810cadc30d9c20cad7324ee8619c5d832e65ac6 [file] [log] [blame]
// Copyright 2019 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_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_
#define BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_
#include <atomic>
#include "base/base_export.h"
#include "base/task/sequence_manager/associated_thread_id.h"
namespace base {
namespace sequence_manager {
namespace internal {
// This class's job is to prevent redundant DoWorks being posted, which are
// expensive. The idea is a DoWork will (maybe) run a task before computing the
// delay till the next task. If the task run posts another task, we don't want
// it to schedule work because the DoWork will post a continuation as needed
// with the latest state taken into consideration (fences, enable / disable
// queue, task cancellation, etc...) Other threads can also post DoWork at any
// time, including while we're computing the delay till the next task. To
// account for that, we have split a DoWork up into two sections:
// [OnWorkStarted .. WillCheckForMoreWork] and
// [WillCheckForMoreWork .. DidCheckForMoreWork] where DidCheckForMoreWork
// detects if another thread called OnWorkRequested.
//
// Nesting is assumed to be dealt with by the ThreadController.
//
// Most methods are thread-affine except for On(Delayed)WorkRequested which are
// is thread-safe.
class BASE_EXPORT WorkDeduplicator {
public:
// Creates an unbound WorkDeduplicator. BindToCurrentThread must be called
// before work can be scheduled.
explicit WorkDeduplicator(
scoped_refptr<AssociatedThreadId> associated_thread);
~WorkDeduplicator();
enum ShouldScheduleWork {
kScheduleImmediate,
kNotNeeded,
};
// Returns ShouldScheduleWork::kSchedule if OnWorkRequested was called while
// unbound. Must be called on the associated thread.
ShouldScheduleWork BindToCurrentThread();
// Returns true if it's OK to schedule a DoWork without risk of task
// duplication. Returns false if:
// * We are unbound
// * We are in a DoWork
// * There is a pending DoWork
//
// Otherwise sets the pending DoWork flag and returns true.
// Can be called on any thread.
//
// DoWork
// ---------------------------------------------------------------------
// | <- OnWorkStarted | |
// | WillCheckForMoreWork -> | |
// | | DidCheckForMoreWork -> |
// ---------------------------------------------------------------------
// ^ ^ ^ ^
// | | | |
// A B C D
//
// Consider a DoWork and calls to OnWorkRequested at various times:
// A: return ShouldScheduleWork::kNotNeeded because there's a pending DoWork.
// B: return ShouldScheduleWork::kNotNeeded because we're in a DoWork.
// C: return ShouldScheduleWork::kNotNeeded because we're in a DoWork, however
// DidCheckForMoreWork should subsequently return
// ShouldScheduleWork::kSchedule.
// D: If DidCheckForMoreWork(kIsImmediate::kIsImmediate) was called then it
// should ShouldScheduleWork::kNotNeeded because there's a pending DoWork.
// Otherwise it should return ShouldScheduleWork::kSchedule, but a
// subsequent call to OnWorkRequested should return
// ShouldScheduleWork::kNotNeeded because there's now a pending DoWork.
ShouldScheduleWork OnWorkRequested();
// Returns ShouldScheduleWork::kSchedule if it's OK to schedule a
// DoDelayedWork without risk of redundancy. Deduplication of delayed work is
// assumed to have been done by the caller, the purpose of this method it to
// check if there's a pending Do(Some)Work which would schedule a delayed
// continuation as needed.
//
// Returns ShouldScheduleWork::kNotNeeded if:
// * We are unbound
// * We are in a DoWork
// * There is a pending DoWork
//
// Must be called on the associated thread.
ShouldScheduleWork OnDelayedWorkRequested() const;
// Marks us as having entered a DoWork, clearing the pending DoWork flag.
// Must be called on the associated thread.
void OnWorkStarted();
// Marks us as being about to check if we have more work. This notification
// helps prevent DoWork duplication in two scenarios:
// * A cross-thread immediate task is posted while we are running a task. If
// the TaskQueue is disabled we can avoid a potentially spurious DoWork.
// * A task is run which posts an immediate task but the ThreadControllerImpl
// work batch size is 2, and there's no further work. The immediate task ran
// in the work batch so we don't need another DoWork.
void WillCheckForMoreWork();
enum NextTask {
kIsImmediate,
kIsDelayed,
};
// Marks us as exiting DoWork. Returns ShouldScheduleWork::kSchedule if an
// immediate DoWork continuation should be posted. This method takes into
// account any OnWorkRequested's called between BeforeComputeDelayTillNextTask
// and here. Must be called on the associated thread.
ShouldScheduleWork DidCheckForMoreWork(NextTask next_task);
// For ThreadControllerWithMessagePumpImpl. The MessagePump calls DoWork and
// DoDelayed work sequentially. If DoWork returns
// ShouldScheduleWork::kSchedule, the pump will call ScheduleWork. We remember
// if DoWork will be scheduled so we don't accidentally call it twice from
// DoDelayedWork. Must be called on the associated thread.
// TODO(alexclarke): Remove these when the DoWork/DoDelayed work merger
// happens.
void OnDelayedWorkStarted();
ShouldScheduleWork OnDelayedWorkEnded(NextTask next_task);
private:
enum Flags {
kInDoWorkFlag = 1 << 0,
kPendingDoWorkFlag = 1 << 1,
kBoundFlag = 1 << 2,
};
enum State {
kUnbound = 0,
kIdle = Flags::kBoundFlag,
kDoWorkPending = Flags::kPendingDoWorkFlag | Flags::kBoundFlag,
kInDoWork = Flags::kInDoWorkFlag | Flags::kBoundFlag,
};
std::atomic<int> state_{State::kUnbound};
scoped_refptr<AssociatedThreadId> associated_thread_;
// TODO(alexclarke): Remove when the DoWork/DoDelayed work merger happens.
ShouldScheduleWork last_work_check_result_ = ShouldScheduleWork::kNotNeeded;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_