A task processor could be obtained from components::ComponentContext in the constructor of the component. References to task processors outlive the component system tear-down, they are safe to use from within any components:
If a blocking system call (for example, one that reads a file in a synchronous way) runs on main-task-processor, then the thread and the processor core are idle until the system call ends. As a result, the throughput of the service temporarily decreases. To prevent this from happening, use userver provided primitives or if the primitive is missing, run the blocking system call on a separate task processor.
Task processors intentionally hide their internals and member functions, so there's no way to call any of the task processor members directly.
Common Task Processors
In static configuration we use different names for task processors. The name does not affect the task processor behavior, only gives a hint on its usage for the developer.
main-task-processor
This is usually the default task processor for CPU bound tasks. It is used to
start the component system, call constructors of the components;
for initialization of the caches (if not specified otherwise);
for accepting incoming requests and processing them;
do all the other CPU bound things;
Note
congestion_control::Component at the moment monitors only the task processor that starts the component system. Handlers that run on other tasks processors are not controlled.
fs-task-processor
Task processor for blocking calls. For example, for functions from blocking namespaces, low-level blocking system calls or third-party library code that does blocking calls.
Note
Functions from the userver framework that are not in the blocking namespaces or do not have blocking in their name are non-blocking! For example, there's no need to run engine::io::Socket::ReadAll() in fs-task-processor, use the main-task-processor instead.
A common usage pattern for this task processor looks like:
// lib_sample synchronously reads some of /etc/* files.
auto result = engine::Async(fs_task_processor_, "torrent/validate", [path]() {
auto files = libtorrent::discover_files(path); // searches FS
return libtorrent::validate_hashes(files); // reads files and computes hashes
}).Get();
single-threaded-task-processors
Used for running task that do not permit concurrent execution (for example V8 or other interpreters). components::SingleThreadedTaskProcessors usually starts those task processors.
monitor-task-processor
This task processor is used for diagnostic and administration handlers and tasks. Separate task processor helps to control service under heavy load or get info from a server with deadlocked threads in the main-task-processor, e.g. server::handlers::InspectRequests handler.
Moving CPU-bound tasks to a separate task processor
Warning
Test and load-test your service, the feature may do things worse.
Some background tasks can slow down handles even if those tasks don't execute a blocking wait:
Multiple background tasks can start running at the same time
Worse if those background tasks are CPU-heavy and don't call engine::Yield
It can also be a problem if parallel tasks are launched in caches. Then one cache is able to fill the entire task processor with tasks
As a workaround for the issue the heavy background tasks could be moved to a separate bg-task-processor task processor.
There are multiple ways to prevent background tasks from consuming 100% of CPU cores:
Allocate the minimum possible number of threads for background task processors, less than CPU cores.
Experiment with the os-scheduling static option to give lower priority for background tasks.
Make sure that tasks execute faster than they arrive.