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