// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_TASK_POST_JOB_H_ #define BASE_TASK_POST_JOB_H_ #include <limits> #include "base/base_export.h" #include "base/dcheck_is_on.h" #include "base/functional/callback.h" #include "base/location.h" #include "base/memory/stack_allocated.h" namespace base { namespace internal { class JobTaskSource; class PooledTaskRunnerDelegate; } class TaskTraits; enum class TaskPriority : uint8_t; // Delegate that's passed to Job's worker task, providing an entry point to // communicate with the scheduler. To prevent deadlocks, JobDelegate methods // should never be called while holding a user lock. class BASE_EXPORT JobDelegate { … }; // Handle returned when posting a Job. Provides methods to control execution of // the posted Job. To prevent deadlocks, JobHandle methods should never be // called while holding a user lock. class BASE_EXPORT JobHandle { … }; // Callback used in PostJob() to control the maximum number of threads calling // the worker task concurrently. // Returns the maximum number of threads which may call a job's worker task // concurrently. |worker_count| is the number of threads currently assigned to // this job which some callers may need to determine their return value. MaxConcurrencyCallback; // Posts a repeating |worker_task| with specific |traits| to run in parallel on // base::ThreadPool. // Returns a JobHandle associated with the Job, which can be joined, canceled or // detached. // ThreadPool APIs, including PostJob() and methods of the returned JobHandle, // must never be called while holding a lock that could be acquired by // |worker_task| or |max_concurrency_callback| -- that could result in a // deadlock. This is because [1] |max_concurrency_callback| may be invoked while // holding internal ThreadPool lock (A), hence |max_concurrency_callback| can // only use a lock (B) if that lock is *never* held while calling back into a // ThreadPool entry point from any thread (A=>B/B=>A deadlock) and [2] // |worker_task| or |max_concurrency_callback| is invoked synchronously from // JobHandle::Join() (A=>JobHandle::Join()=>A deadlock). // To avoid scheduling overhead, |worker_task| should do as much work as // possible in a loop when invoked, and JobDelegate::ShouldYield() should be // periodically invoked to conditionally exit and let the scheduler prioritize // work. // // A canonical implementation of |worker_task| looks like: // void WorkerTask(JobDelegate* job_delegate) { // while (!job_delegate->ShouldYield()) { // auto work_item = worker_queue.TakeWorkItem(); // Smallest unit of work. // if (!work_item) // return: // ProcessWork(work_item); // } // } // // |max_concurrency_callback| controls the maximum number of threads calling // |worker_task| concurrently. |worker_task| is only invoked if the number of // threads previously running |worker_task| was less than the value returned by // |max_concurrency_callback|. In general, |max_concurrency_callback| should // return the latest number of incomplete work items (smallest unit of work) // left to processed. JobHandle/JobDelegate::NotifyConcurrencyIncrease() *must* // be invoked shortly after |max_concurrency_callback| starts returning a value // larger than previously returned values. This usually happens when new work // items are added and the API user wants additional threads to invoke // |worker_task| concurrently. The callbacks may be called concurrently on any // thread until the job is complete. If the job handle is detached, the // callbacks may still be called, so they must not access global state that // could be destroyed. // // |traits| requirements: // - base::ThreadPolicy must be specified if the priority of the task runner // will ever be increased from BEST_EFFORT. JobHandle BASE_EXPORT PostJob(const Location& from_here, const TaskTraits& traits, RepeatingCallback<void(JobDelegate*)> worker_task, MaxConcurrencyCallback max_concurrency_callback); // Creates and returns a JobHandle associated with a Job. Unlike PostJob(), this // doesn't immediately schedules |worker_task| to run on base::ThreadPool // workers; the Job is then scheduled by calling either // NotifyConcurrencyIncrease() or Join(). JobHandle BASE_EXPORT CreateJob(const Location& from_here, const TaskTraits& traits, RepeatingCallback<void(JobDelegate*)> worker_task, MaxConcurrencyCallback max_concurrency_callback); } // namespace base #endif // BASE_TASK_POST_JOB_H_