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