xref: /aosp_15_r20/external/cronet/base/task/sequence_manager/thread_controller.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2018 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_THREAD_CONTROLLER_H_
6 #define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_
7 
8 #include <optional>
9 #include <stack>
10 #include <string_view>
11 #include <vector>
12 
13 #include "base/base_export.h"
14 #include "base/check.h"
15 #include "base/features.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/memory/raw_ref.h"
18 #include "base/memory/scoped_refptr.h"
19 #include "base/message_loop/message_pump.h"
20 #include "base/profiler/sample_metadata.h"
21 #include "base/rand_util.h"
22 #include "base/run_loop.h"
23 #include "base/task/common/lazy_now.h"
24 #include "base/task/sequence_manager/associated_thread_id.h"
25 #include "base/task/sequence_manager/tasks.h"
26 #include "base/task/single_thread_task_runner.h"
27 #include "base/thread_annotations.h"
28 #include "base/time/time.h"
29 #include "base/trace_event/base_tracing.h"
30 #include "base/tracing_buildflags.h"
31 #include "build/build_config.h"
32 
33 namespace base {
34 
35 class HistogramBase;
36 class MessageLoopBase;
37 class TickClock;
38 struct PendingTask;
39 
40 namespace sequence_manager {
41 namespace internal {
42 
43 class SequencedTaskSource;
44 
45 // Implementation of this interface is used by SequenceManager to schedule
46 // actual work to be run. Hopefully we can stop using MessageLoop and this
47 // interface will become more concise.
48 class BASE_EXPORT ThreadController {
49  public:
50   // Phases the top-RunLevel can go through. While these are more precise than
51   // RunLevelTracker::State, unlike it: phases are determined retrospectively
52   // as we often only find out the type of work that was just performed at the
53   // end of a phase. Or even find out about past phases later in the timeline
54   // (i.e. kScheduled is only known after the first kSelectingApplicationTask
55   // phase out-of-idle).
56   // Public for unit tests.
57   // These values are logged to UMA. Entries should not be renumbered and
58   // numeric values should never be reused. Please keep in sync
59   // with "MessagePumpPhases" in src/tools/metrics/histograms/enums.xml.
60   enum Phase {
61     kScheduled = 1,
62     kPumpOverhead = 2,
63     // Any work item, in practice application tasks are mapped to
64     // kApplicationTask so this only accounts for native work.
65     kWorkItem = 3,
66     kNativeWork = kWorkItem,
67     kSelectingApplicationTask = 4,
68     kApplicationTask = 5,
69     kIdleWork = 6,
70     kNested = 7,
71     kLastPhase = kNested,
72     // Reported as a kWorkItem but doesn't clear state relevant to the ongoing
73     // work item as it isn't finished (will resume after nesting).
74     kWorkItemSuspendedOnNested,
75   };
76 
77   explicit ThreadController(const TickClock* time_source);
78   virtual ~ThreadController();
79 
80   // Sets the number of tasks executed in a single invocation of DoWork.
81   // Increasing the batch size can reduce the overhead of yielding back to the
82   // main message loop.
83   virtual void SetWorkBatchSize(int work_batch_size = 1) = 0;
84 
85   // Notifies that |pending_task| is about to be enqueued. Needed for tracing
86   // purposes. The impl may use this opportunity add metadata to |pending_task|
87   // before it is moved into the queue.
88   virtual void WillQueueTask(PendingTask* pending_task) = 0;
89 
90   // Notify the controller that its associated sequence has immediate work
91   // to run. Shortly after this is called, the thread associated with this
92   // controller will run a task returned by sequence->TakeTask(). Can be called
93   // from any sequence.
94   //
95   // TODO(altimin): Change this to "the thread associated with this
96   // controller will run tasks returned by sequence->TakeTask() until it
97   // returns null or sequence->DidRunTask() returns false" once the
98   // code is changed to work that way.
99   virtual void ScheduleWork() = 0;
100 
101   // Notify the controller that SequencedTaskSource will have a delayed work
102   // ready to be run at |wake_up|. This call cancels any previously
103   // scheduled delayed work. Can only be called from the main sequence.
104   // NOTE: GetPendingWakeUp might return a different value as it also takes
105   // immediate work into account.
106   // TODO(kraynov): Remove |lazy_now| parameter.
107   virtual void SetNextDelayedDoWork(LazyNow* lazy_now,
108                                     std::optional<WakeUp> wake_up) = 0;
109 
110   // Sets the sequenced task source from which to take tasks after
111   // a Schedule*Work() call is made.
112   // Must be called before the first call to Schedule*Work().
113   virtual void SetSequencedTaskSource(SequencedTaskSource*) = 0;
114 
115   // Completes delayed initialization of unbound ThreadControllers.
116   // BindToCurrentThread(MessageLoopBase*) or BindToCurrentThread(MessagePump*)
117   // may only be called once.
118   virtual void BindToCurrentThread(
119       std::unique_ptr<MessagePump> message_pump) = 0;
120 
121   // Explicitly allow or disallow task execution. Implicitly disallowed when
122   // entering a nested runloop.
123   virtual void SetTaskExecutionAllowedInNativeNestedLoop(bool allowed) = 0;
124 
125   // Whether task execution is allowed or not.
126   virtual bool IsTaskExecutionAllowed() const = 0;
127 
128   // Returns the MessagePump we're bound to if any.
129   virtual MessagePump* GetBoundMessagePump() const = 0;
130 
131   // Returns true if the current run loop should quit when idle.
132   virtual bool ShouldQuitRunLoopWhenIdle() = 0;
133 
134 #if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
135   // On iOS, the main message loop cannot be Run().  Instead call
136   // AttachToMessagePump(), which connects this ThreadController to the
137   // UI thread's CFRunLoop and allows PostTask() to work.
138   virtual void AttachToMessagePump() = 0;
139 #endif
140 
141 #if BUILDFLAG(IS_IOS)
142   // Detaches this ThreadController from the message pump, allowing the
143   // controller to be shut down cleanly.
144   virtual void DetachFromMessagePump() = 0;
145 #endif
146 
147   // Initializes features for this class. See `base::features::Init()`.
148   static void InitializeFeatures(
149       features::EmitThreadControllerProfilerMetadata emit_profiler_metadata);
150 
151   // Enables TimeKeeper metrics. `thread_name` will be used as a suffix.
152   void EnableMessagePumpTimeKeeperMetrics(const char* thread_name);
153 
154   // Currently only overridden on ThreadControllerWithMessagePumpImpl.
155   //
156   // While Now() is less than |prioritize_until| we will alternate between
157   // |work_batch_size| tasks before setting |yield_to_native| on the
158   // NextWorkInfo and yielding to the underlying sequence (e.g. the message
159   // pump).
160   virtual void PrioritizeYieldingToNative(base::TimeTicks prioritize_until) = 0;
161 
162   // Sets the SingleThreadTaskRunner that will be returned by
163   // SingleThreadTaskRunner::GetCurrentDefault on the thread controlled by this
164   // ThreadController.
165   virtual void SetDefaultTaskRunner(scoped_refptr<SingleThreadTaskRunner>) = 0;
166 
167   // TODO(altimin): Get rid of the methods below.
168   // These methods exist due to current integration of SequenceManager
169   // with MessageLoop.
170 
171   virtual bool RunsTasksInCurrentSequence() = 0;
172   void SetTickClock(const TickClock* clock);
173   virtual scoped_refptr<SingleThreadTaskRunner> GetDefaultTaskRunner() = 0;
174   virtual void RestoreDefaultTaskRunner() = 0;
175   virtual void AddNestingObserver(RunLoop::NestingObserver* observer) = 0;
176   virtual void RemoveNestingObserver(RunLoop::NestingObserver* observer) = 0;
177 
GetAssociatedThread()178   const scoped_refptr<AssociatedThreadId>& GetAssociatedThread() const {
179     return associated_thread_;
180   }
181 
182  protected:
183   const scoped_refptr<AssociatedThreadId> associated_thread_;
184 
185   // The source of TimeTicks for this ThreadController.
186   // Must only be accessed from the `associated_thread_`.
187   // TODO(scheduler-dev): This could be made
188   // `GUARDED_BY_CONTEXT(associated_thread_->thread_checker)` when
189   // switching MainThreadOnly to thread annotations and annotating all
190   // thread-affine ThreadController methods. Without that, this lone annotation
191   // would result in an inconsistent set of DCHECKs...
192   raw_ptr<const TickClock> time_source_;  // Not owned.
193 
194   // Tracks the state of each run-level (main and nested ones) in its associated
195   // ThreadController. It does so using two high-level principles:
196   //  1) #work-in-work-implies-nested :
197   //     If the |state_| is kRunningWorkItem and another work item starts
198   //     (OnWorkStarted()), it implies this inner-work-item is running from a
199   //  2) #done-work-at-lower-runlevel-implies-done-nested
200   //     WorkItems are required to pass in the nesting depth at which they were
201   //     created in OnWorkEnded(). Then, if |rundepth| is lower than the current
202   //     RunDepth(), we know the top RunLevel was an (already exited) nested
203   //     loop and will be popped off |run_levels_|.
204   // We need this logic because native nested loops can run from any work item
205   // without a RunLoop being involved, see
206   // ThreadControllerWithMessagePumpTest.ThreadControllerActive* tests for
207   // examples. Using these two heuristics is the simplest way, trying to
208   // capture all the ways in which work items can nest is harder than reacting
209   // as it happens.
210   //
211   // Note 1: "native work" is only captured if the MessagePump is
212   // instrumented to see them and shares them with ThreadController (via
213   // MessagePump::Delegate::OnBeginWorkItem). As such it is still possible to
214   // view trace events emanating from native work without "ThreadController
215   // active" being active.
216   // Note 2: Non-instrumented native work does not break the two high-level
217   // principles above because:
218   //  A) If a non-instrumented work item enters a nested loop, either:
219   //     i) No instrumented work run within the loop so it's invisible.
220   //     ii) Instrumented work runs *and* current state is kRunningWorkItem
221   //         ((A) is a work item within an instrumented work item):
222   //         #work-in-work-implies-nested triggers and the nested loop is
223   //         visible.
224   //     iii) Instrumented work runs *and* current state is kIdle or
225   //          kInBetweenWorkItems ((A) is a work item run by a native loop):
226   //          #work-in-work-implies-nested doesn't trigger and this instrumented
227   //          work (iii) looks like a non-nested continuation of work at the
228   //          current RunLevel.
229   //  B) When work item (A) exits its nested loop and completes, respectively:
230   //     i) The loop was invisible so no RunLevel was created for it and
231   //        #done-work-at-lower-runlevel-implies-done-nested doesn't trigger so
232   //        it balances out.
233   //     ii) Instrumented work did run, and so RunLevels() increased. However,
234   //         since instrumented work (the work which called the nested loop)
235   //         keeps track of its own run depth, on its exit, we know to pop the
236   //         RunLevel corresponding to the nested work.
237   //     iii) Nested instrumented work was visible but didn't appear nested,
238   //          state is now back to kInBetweenWorkItems or kIdle as before (A).
239   class BASE_EXPORT RunLevelTracker {
240    public:
241     // States each RunLevel can be in.
242     enum State {
243       // Waiting for work (pending wakeup).
244       kIdle,
245       // Between two work items but not idle.
246       kInBetweenWorkItems,
247       // Running and currently processing a work items (includes selecting the
248       // next work item, i.e. either peeking the native work queue or selecting
249       // the next application task).
250       kRunningWorkItem,
251     };
252 
253     explicit RunLevelTracker(const ThreadController& outer);
254     ~RunLevelTracker();
255 
256     void OnRunLoopStarted(State initial_state, LazyNow& lazy_now);
257     void OnRunLoopEnded();
258     void OnWorkStarted(LazyNow& lazy_now);
259     void OnApplicationTaskSelected(TimeTicks queue_time, LazyNow& lazy_now);
260     void OnWorkEnded(LazyNow& lazy_now, int run_level_depth);
261     void OnIdle(LazyNow& lazy_now);
262 
num_run_levels()263     size_t num_run_levels() const {
264       DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
265       return run_levels_.size();
266     }
267 
268     // Emits a perfetto::Flow (wakeup.flow) event associated with this
269     // RunLevelTracker.
270     void RecordScheduleWork();
271 
272     void EnableTimeKeeperMetrics(const char* thread_name);
273 
274     // Observes changes of state sent as trace-events so they can be tested.
275     class TraceObserverForTesting {
276      public:
277       virtual ~TraceObserverForTesting() = default;
278 
279       virtual void OnThreadControllerActiveBegin() = 0;
280       virtual void OnThreadControllerActiveEnd() = 0;
281       virtual void OnPhaseRecorded(Phase phase) = 0;
282     };
283 
284     static void SetTraceObserverForTesting(
285         TraceObserverForTesting* trace_observer_for_testing);
286 
287    private:
288     // Keeps track of the time spent in various Phases (ignores idle), reports
289     // via UMA to the corresponding phase every time one reaches >= 100ms of
290     // cumulative time, resulting in a metric of relative time spent in each
291     // non-idle phase. Also emits each phase as a trace event on its own
292     // MessagePumpPhases track when the disabled-by-default-base tracing
293     // category is enabled.
294     class TimeKeeper {
295      public:
296       explicit TimeKeeper(const RunLevelTracker& outer);
297 
298       void EnableRecording(const char* thread_name);
299 
300       // Records the start time of the first phase out-of-idle. The kScheduled
301       // phase will be attributed the time before this point once its
302       // `queue_time` is known.
303       void RecordWakeUp(LazyNow& lazy_now);
304 
305       // Accounts the time since OnWorkStarted() towards
306       // kSelectingApplicationTask. Accounts `queue_time - last_wakeup_` towards
307       // kScheduled (iff `queue_time` is not null nor later than
308       // `last_wakeup_`). And flags the current kWorkItem as a kApplicationTask,
309       // to be accounted from OnWorkEnded(). Emits a trace event for the
310       // kScheduled phase if applicable.
311       void OnApplicationTaskSelected(TimeTicks queue_time, LazyNow& lazy_now);
312 
313       // If recording is enabled: Records the end of a phase, attributing it the
314       // delta between `lazy_now` and `last_phase_end` and emit a trace event
315       // for it.
316       void RecordEndOfPhase(Phase phase, LazyNow& lazy_now);
317 
318       // If recording is enabled: If the `wakeup.flow` category is enabled,
319       // record a TerminatingFlow into the current "ThreadController Active"
320       // track event.
321       void MaybeEmitIncomingWakeupFlow(perfetto::EventContext& ctx);
322 
thread_name()323       const std::string& thread_name() const { return thread_name_; }
324 
325      private:
326       enum class ShouldRecordReqs {
327         // Regular should-record requirements.
328         kRegular,
329         // On wakeup there's an exception to the requirement that `last_wakeup_`
330         // be set.
331         kOnWakeUp,
332         // On end-nested there's an exception to the requirement that there's no
333         // ongoing nesting (as the kNested phase ends from ~RunLevel, before
334         // run_levels.pop() completes).
335         kOnEndNested,
336       };
337       bool ShouldRecordNow(ShouldRecordReqs reqs = ShouldRecordReqs::kRegular);
338 
339       // Common helper to actually record time in a phase and emitt histograms
340       // as needed.
341       void RecordTimeInPhase(Phase phase,
342                              TimeTicks phase_begin,
343                              TimeTicks phase_end);
344 
345       static const char* PhaseToEventName(Phase phase);
346 
347       std::string thread_name_;
348       // Cumulative time deltas for each phase, reported and reset when >=100ms.
349       std::array<TimeDelta, Phase::kLastPhase + 1> deltas_ = {};
350       // Set at the start of the first work item out-of-idle. Consumed from the
351       // first application task found in that work cycle
352       // (in OnApplicationTaskSelected).
353       TimeTicks last_wakeup_;
354       // The end of the last phase (used as the beginning of the next one).
355       TimeTicks last_phase_end_;
356       // The end of the last kIdleWork phase. Used as a minimum for the next
357       // kScheduled phase's begin (as it's possible that the next wake-up is
358       // scheduled during DoIdleWork adn we don't want overlapping phases).
359       TimeTicks last_sleep_;
360       // Assumes each kWorkItem is native unless OnApplicationTaskSelected() is
361       // invoked in a given [OnWorkStarted, OnWorkEnded].
362       bool current_work_item_is_native_ = true;
363 
364       // non-null when recording is enabled.
365       raw_ptr<HistogramBase> histogram_ = nullptr;
366 #if BUILDFLAG(ENABLE_BASE_TRACING)
367       std::optional<perfetto::Track> perfetto_track_;
368 
369       // True if tracing was enabled during the last pass of RecordTimeInPhase.
370       bool was_tracing_enabled_ = false;
371 #endif
372       const raw_ref<const RunLevelTracker> outer_;
373     } time_keeper_{*this};
374 
375     class RunLevel {
376      public:
377       RunLevel(State initial_state,
378                bool is_nested,
379                TimeKeeper& time_keeper,
380                LazyNow& lazy_now);
381       ~RunLevel();
382 
383       // Move-constructible for STL compat. Flags `other.was_moved_` so it noops
384       // on destruction after handing off its responsibility. Move-assignment
385       // is not necessary nor possible as not all members are assignable.
386       RunLevel(RunLevel&& other);
387       RunLevel& operator=(RunLevel&&) = delete;
388 
389       void UpdateState(State new_state, LazyNow& lazy_now);
390 
state()391       State state() const { return state_; }
392 
set_exit_lazy_now(LazyNow * exit_lazy_now)393       void set_exit_lazy_now(LazyNow* exit_lazy_now) {
394         DCHECK(exit_lazy_now);
395         DCHECK(!exit_lazy_now_);
396         exit_lazy_now_ = exit_lazy_now;
397       }
398 
399      private:
400       void LogPercentageMetric(const char* name,
401                                int value,
402                                base::TimeDelta interval_duration);
403       void LogIntervalMetric(const char* name,
404                              base::TimeDelta value,
405                              base::TimeDelta interval_duration);
406       void LogOnActiveMetrics(LazyNow& lazy_now);
407       void LogOnIdleMetrics(LazyNow& lazy_now);
408 
409       base::TimeTicks last_active_end_;
410       base::TimeTicks last_active_start_;
411       base::ThreadTicks last_active_threadtick_start_;
412       MetricsSubSampler metrics_sub_sampler_;
413 
414       State state_ = kIdle;
415       bool is_nested_;
416 
417       bool ShouldRecordSampleMetadata();
418 
419       // Get full suffix for histogram logging purposes. |duration| should equal
420       // TimeDelta() when not applicable.
421       std::string GetSuffixForHistogram(TimeDelta duration);
422 
423       std::string GetSuffixForCatchAllHistogram();
424       std::string_view GetThreadName();
425 
426       const raw_ref<TimeKeeper> time_keeper_;
427       // Must be set shortly before ~RunLevel.
428       raw_ptr<LazyNow> exit_lazy_now_ = nullptr;
429 
430       SampleMetadata thread_controller_sample_metadata_;
431       size_t thread_controller_active_id_ = 0;
432 
433       // Toggles to true when used as RunLevel&& input to construct another
434       // RunLevel. This RunLevel's destructor will then no-op.
435       class TruePostMove {
436        public:
437         TruePostMove() = default;
TruePostMove(TruePostMove && other)438         TruePostMove(TruePostMove&& other) { other.was_moved_ = true; }
439         // Not necessary for now.
440         TruePostMove& operator=(TruePostMove&&) = delete;
441 
442         explicit operator bool() { return was_moved_; }
443 
444        private:
445         bool was_moved_ = false;
446       };
447       TruePostMove was_moved_;
448     };
449 
450     [[maybe_unused]] const raw_ref<const ThreadController> outer_;
451 
452     std::stack<RunLevel, std::vector<RunLevel>> run_levels_
453         GUARDED_BY_CONTEXT(outer_->associated_thread_->thread_checker);
454 
455     static TraceObserverForTesting* trace_observer_for_testing_;
456   } run_level_tracker_{*this};
457 };
458 
459 }  // namespace internal
460 }  // namespace sequence_manager
461 }  // namespace base
462 
463 #endif  // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_
464