xref: /aosp_15_r20/external/cronet/base/task/sequence_manager/work_deduplicator.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_
6 #define BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_
7 
8 #include <atomic>
9 
10 #include "base/base_export.h"
11 #include "base/task/sequence_manager/associated_thread_id.h"
12 
13 namespace base {
14 namespace sequence_manager {
15 namespace internal {
16 
17 // This class's job is to prevent redundant DoWorks being posted, which are
18 // expensive. The idea is a DoWork will (maybe) run a task before computing the
19 // delay till the next task. If the task run posts another task, we don't want
20 // it to schedule work because the DoWork will post a continuation as needed
21 // with the latest state taken into consideration (fences, enable / disable
22 // queue, task cancellation, etc...) Other threads can also post DoWork at any
23 // time, including while we're computing the delay till the next task. To
24 // account for that, we have split a DoWork up into two sections:
25 // [OnWorkStarted .. WillCheckForMoreWork] and
26 // [WillCheckForMoreWork .. DidCheckForMoreWork] where DidCheckForMoreWork
27 // detects if another thread called OnWorkRequested.
28 //
29 // Nesting is assumed to be dealt with by the ThreadController.
30 //
31 // Most methods are thread-affine except for On(Delayed)WorkRequested which are
32 // is thread-safe.
33 class BASE_EXPORT WorkDeduplicator {
34  public:
35   // Creates an unbound WorkDeduplicator. BindToCurrentThread must be called
36   // before work can be scheduled.
37   explicit WorkDeduplicator(
38       scoped_refptr<const AssociatedThreadId> associated_thread);
39 
40   ~WorkDeduplicator();
41 
42   enum ShouldScheduleWork {
43     kScheduleImmediate,
44     kNotNeeded,
45   };
46 
47   // Returns ShouldScheduleWork::kSchedule if OnWorkRequested was called while
48   // unbound. Must be called on the associated thread.
49   ShouldScheduleWork BindToCurrentThread();
50 
51   // Returns true if it's OK to schedule a DoWork without risk of task
52   // duplication. Returns false if:
53   // * We are unbound
54   // * We are in a DoWork
55   // * There is a pending DoWork
56   //
57   // Otherwise sets the pending DoWork flag and returns true.
58   // Can be called on any thread.
59   //
60   //    DoWork
61   //    ---------------------------------------------------------------------
62   //    | <- OnWorkStarted                       |                          |
63   //    |                WillCheckForMoreWork -> |                          |
64   //    |                                        |   DidCheckForMoreWork -> |
65   //    ---------------------------------------------------------------------
66   // ^                            ^                           ^               ^
67   // |                            |                           |               |
68   // A                            B                           C               D
69   //
70   // Consider a DoWork and calls to OnWorkRequested at various times:
71   // A: return ShouldScheduleWork::kNotNeeded because there's a pending DoWork.
72   // B: return ShouldScheduleWork::kNotNeeded because we're in a DoWork.
73   // C: return ShouldScheduleWork::kNotNeeded because we're in a DoWork, however
74   //    DidCheckForMoreWork should subsequently return
75   //    ShouldScheduleWork::kScheduleImmediate.
76   // D: If DidCheckForMoreWork(NextTask::kIsImmediate) was called then it
77   //    should ShouldScheduleWork::kNotNeeded because there's a pending DoWork.
78   //    Otherwise it should return ShouldScheduleWork::kScheduleImmediate, but a
79   //    subsequent call to OnWorkRequested should return
80   //    ShouldScheduleWork::kNotNeeded because there's now a pending DoWork.
81   ShouldScheduleWork OnWorkRequested();
82 
83   // Returns ShouldScheduleWork::kScheduleImmediate if it's OK to schedule a
84   // DoDelayedWork without risk of redundancy. Deduplication of delayed work is
85   // assumed to have been done by the caller, the purpose of this method it to
86   // check if there's a pending DoWork which would schedule a delayed
87   // continuation as needed.
88   //
89   // Returns ShouldScheduleWork::kNotNeeded if:
90   // * We are unbound
91   // * We are in a DoWork
92   // * There is a pending DoWork
93   //
94   // Must be called on the associated thread.
95   ShouldScheduleWork OnDelayedWorkRequested() const;
96 
97   // Marks us as having entered a DoWork, clearing the pending DoWork flag.
98   // Must be called on the associated thread.
99   void OnWorkStarted();
100 
101   // Marks us as being about to check if we have more work. This notification
102   // helps prevent DoWork duplication in two scenarios:
103   // * A cross-thread immediate task is posted while we are running a task. If
104   //   the TaskQueue is disabled we can avoid a potentially spurious DoWork.
105   // * A task is run which posts an immediate task but the ThreadControllerImpl
106   //   work batch size is 2, and there's no further work. The immediate task ran
107   //   in the work batch so we don't need another DoWork.
108   void WillCheckForMoreWork();
109 
110   enum NextTask {
111     kIsImmediate,
112     kIsDelayed,
113   };
114 
115   // Marks us as exiting DoWork. Returns ShouldScheduleWork::kScheduleImmediate
116   // if an immediate DoWork continuation should be posted. This method
117   // atomically takes into account any OnWorkRequested's called between
118   // gathering information about |next_task| and this call. Must be called on
119   // the associated thread.
120   ShouldScheduleWork DidCheckForMoreWork(NextTask next_task);
121 
122  private:
123   enum Flags {
124     kInDoWorkFlag = 1 << 0,
125     kPendingDoWorkFlag = 1 << 1,
126     kBoundFlag = 1 << 2,
127   };
128 
129   enum State {
130     kUnbound = 0,
131     kIdle = Flags::kBoundFlag,
132     kDoWorkPending = Flags::kPendingDoWorkFlag | Flags::kBoundFlag,
133     kInDoWork = Flags::kInDoWorkFlag | Flags::kBoundFlag,
134   };
135 
136   std::atomic<int> state_{State::kUnbound};
137 
138   const scoped_refptr<const AssociatedThreadId> associated_thread_;
139 };
140 
141 }  // namespace internal
142 }  // namespace sequence_manager
143 }  // namespace base
144 
145 #endif  // BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_
146