You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Clarified the behavior of @ref engine::TaskBase::State, @ref engine::TaskBase::IsValid and cancellation propagation.
commit_hash:a3c45aee640d0d431e4c82bea088a113c4578c72
Copy file name to clipboardExpand all lines: scripts/docs/en/userver/intro.md
+48-37Lines changed: 48 additions & 37 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,7 +49,7 @@ low-latency, then it may be fine to run all the code on the same task processor.
49
49
______
50
50
⚠️🐙❗ If you want to run code that uses standard synchronization primitives
51
51
(for example, code from a third-party library), then this code should be run in
52
-
a separate `engine::TaskProcessor` to avoid starvation of main task processors.
52
+
a separate @refengine::TaskProcessor to avoid starvation of main task processors.
53
53
See @ref scripts/docs/en/userver/task_processors_guide.md for more info.
54
54
______
55
55
@@ -95,21 +95,21 @@ By engine::TaskProcessor:
95
95
96
96
By shared-ness:
97
97
98
-
* By default, functions return engine::TaskWithResult, which can be awaited
98
+
* By default, functions return @refengine::TaskWithResult, which can be awaited
99
99
from 1 task at once. This is a reasonable choice for most cases.
100
100
* Functions from `utils::Shared*Async*` and `engine::Shared*AsyncNoSpan`
101
-
families return engine::SharedTaskWithResult, which can be awaited
101
+
families return @refengine::SharedTaskWithResult, which can be awaited
102
102
from multiple tasks at the same time, at the cost of some overhead.
103
103
104
-
By engine::TaskBase::Importance ("critical-ness"):
104
+
By @refengine::TaskBase::Importance ("critical-ness"):
105
105
106
-
* By default, functions can be cancelled due to engine::TaskProcessor
106
+
* By default, functions can be cancelled due to @refengine::TaskProcessor
107
107
overload. Also, if the task is cancelled before being started, it will not
108
108
run at all.
109
109
* If the whole service's health (not just one request) depends on the task
110
110
being run, then functions from `utils::*CriticalAsync*` and
111
111
`engine::*CriticalAsyncNoSpan*` families can be used. There, execution of
112
-
the function is guaranteed to start regardless of engine::TaskProcessor
112
+
the function is guaranteed to start regardless of @refengine::TaskProcessor
113
113
load limits
114
114
115
115
By tracing::Span:
@@ -128,7 +128,7 @@ By tracing::Span:
128
128
But beware! Using tracing::Span::CurrentSpan() will trigger asserts
129
129
and lead to UB in production.
130
130
131
-
By the propagation of engine::TaskInheritedVariable instances:
131
+
By the propagation of @refengine::TaskInheritedVariable instances:
132
132
133
133
* Functions from `utils::*Async*` family (which you should use by default)
134
134
inherit all task-inherited variables from the parent task.
@@ -270,76 +270,87 @@ Cancellation can occur:
270
270
271
271
* by an explicit request;
272
272
* due to the end of the task object lifetime;
273
-
* at coroutine engine shutdown (affects tasks launched via `engine::Task::Detach`);
273
+
* at coroutine engine shutdown (affects tasks launched via @refengine::DetachUnscopedUnsafe);
274
274
* due to the lack of resources.
275
275
276
-
To cancel a task explicitly, call the `engine::TaskBase::RequestCancel()` or `engine::TaskBase::SyncCancel()` method. It cancels only a single task and does not directly affect the subtasks that were created by the canceled task.
276
+
To cancel a task explicitly, call the @ref engine::TaskBase::RequestCancel or @ref engine::TaskBase::SyncCancel method.
277
+
For details on what happens with subtasks when a task is cancelled, see @ref task_cancellation_outside_world.
277
278
278
-
Another way to cancel a task it to drop the `engine::TaskWithResult` without awaiting it, e.g. by returning from the function that stored it in a local variable or by letting an exception fly out.
279
+
Another way to cancel a task it to drop the @refengine::TaskWithResult without awaiting it, e.g. by returning from the function that stored it in a local variable or by letting an exception fly out.
Tasks can be cancelled due to `engine::TaskProcessor` overload, if configured. This is a last-ditch effort to avoid OOM due to a spam of tasks. Read more in `utils::Async` and `engine::TaskBase::Importance`. Tasks started with `engine::CriticalAsync` are excepted from cancellations due to `TaskProcessor` overload.
283
+
In the example above, `child_task` is cancelled and awaited due to stack unwinding. In any case, the child task's
284
+
execution is guaranteed to be finished once its @ref engine::TaskWithResult handle is destroyed.
285
+
286
+
Tasks can be cancelled due to @ref engine::TaskProcessor overload, if configured. This is a last-ditch effort to avoid OOM due to a spam of tasks. Read more in @ref utils::Async and @ref engine::TaskBase::Importance. Tasks started with @ref engine::CriticalAsync are excepted from cancellations due to `TaskProcessor` overload.
283
287
284
288
### How the task sees its cancellation
285
289
286
290
Unlike C++20 coroutines, userver does not have a magical way to kill a task. The cancellation will somehow be signaled to the synchronization primitive being waited on, then it will go through the logic of the user's function, then the function will somehow complete.
287
291
288
292
How some synchronization primitives react to cancellations:
289
293
290
-
*`engine::TaskWithResult::Get` and `engine::TaskBase::Wait` throw `engine::WaitInterruptedException`, which typically leads to the destruction of the child task during stack unwinding, cancelling and awaiting it;
291
-
*`engine::ConditionVariable::Wait` and `engine::Future::wait` return a status code;
*`engine::SingleConsumerEvent::WaitForEventFor` returns `false` and needs an additional `engine::current_task::ShouldCancel()` check;
294
-
*`engine::InterruptibleSleepFor` needs an additional `engine::current_task::ShouldCancel()` check;
295
-
*`engine::CancellableSemaphore` returns `false` or throws `engine::SemaphoreLockCancelledError`.
294
+
*@refengine::TaskWithResult::Get and @refengine::TaskBase::Wait throw @refengine::WaitInterruptedException, which typically leads to the destruction of the child task during stack unwinding, cancelling and awaiting it;
295
+
*@refengine::ConditionVariable::Wait and @refengine::Future::wait return a status code;
*@refengine::SingleConsumerEvent::WaitForEventFor returns `false` and needs an additional @refengine::current_task::ShouldCancel check;
298
+
*@refengine::InterruptibleSleepFor needs an additional @refengine::current_task::ShouldCancel check;
299
+
*@refengine::CancellableSemaphore returns `false` or throws engine::SemaphoreLockCancelledError.
296
300
297
301
Some synchronization primitives deliberately ignore cancellations, notably:
298
302
299
-
*`engine::Mutex`;
300
-
*`engine::Semaphore` (use `engine::CancellableSemaphore` to support cancellations);
301
-
*`engine::SleepFor` (use `engine::InterruptibleSleepFor` to support cancellations).
303
+
*@refengine::Mutex;
304
+
*@refengine::Semaphore (use @refengine::CancellableSemaphore to support cancellations);
305
+
*@refengine::SleepFor (use @refengine::InterruptibleSleepFor to support cancellations).
302
306
303
-
Most clients throw a client-specific exception on cancellation. Please explore the docs of the client you are using to find out how it reacts to cancellations. Typically, there is a special exception type thrown in case of cancellations, e.g. `clients::http::CancelException`.
307
+
Most clients throw a client-specific exception on cancellation. Please explore the docs of the client you are using to find out how it reacts to cancellations. Typically, there is a special exception type thrown in case of cancellations, e.g. @refclients::http::CancelException.
304
308
309
+
@anchor task_cancellation_outside_world
305
310
### How the outside world sees the task's cancellation
306
311
307
-
The general theme is that a task's completion upon cancellation is still a completion. The task's function will ultimately return or throw something, and that is what the parent task will receive in `engine::TaskWithResult::Get` or `engine::TaskBase::Wait`.
312
+
The general theme is that a task's completion upon cancellation is still a completion. The task's function will ultimately return or throw something, and that is what the parent task will receive in @refengine::TaskWithResult::Get or @refengine::TaskBase::Wait.
308
313
309
-
If the cancellation is due to the parent task being cancelled, then its `engine::TaskWithResult::Get` or `engine::TaskBase::Wait` will throw an `engine::WaitInterruptedException`, leaving the child task running (for now), so the parent task will likely not have a chance to observe the child task's completion status. Usually the stack unwinding in the parent task then destroys the `engine::Task` handle, which causes it to be cancelled and awaited.
314
+
If the cancellation is due to the parent task being cancelled, then its @refengine::TaskWithResult::Get or @refengine::TaskBase::Wait will throw an @refengine::WaitInterruptedException, leaving the child task running (for now), so the parent task will likely not have a chance to observe the child task's completion status. Usually the stack unwinding in the parent task then destroys the @refengine::TaskWithResult handle, which causes it to be cancelled and awaited.
In the example above, `child_task` is cancelled and awaited due to stack unwinding. In any case, the child task's
319
+
execution is guaranteed to be finished once its @ref engine::TaskWithResult handle is destroyed.
320
+
313
321
If the child task got cancelled without the parent being cancelled, then:
314
322
315
-
*`engine::TaskWithResult::Get` will return or throw whatever the child task has returned or thrown, which is practically meaningless (because why else would someone cancel a task?);
316
-
*`engine::TaskBase::Wait` will return upon completion;
317
-
*`engine::TaskBase::IsFinished` will return `true` upon completion;
318
-
*`engine::TaskBase::GetStatus` will return `engine::TaskBase::Status::kCancelled` upon completion.
323
+
*@ref engine::TaskWithResult::Get will return or throw whatever the child task has returned or thrown, which is practically meaningless (because why else would someone cancel a task?);
324
+
*@ref engine::TaskBase::Wait will return upon completion;
325
+
*@ref engine::TaskBase::IsFinished will return `true` upon completion;
326
+
*@ref engine::TaskBase::GetStatus will return @ref engine::TaskBase::Status::kCancelled upon completion.
327
+
328
+
@anchor task_cancellation_before_start
329
+
### What happens to tasks that are cancelled before they start running
319
330
320
-
There is one extra quirk: if the task is cancelled before being started, then only the functor's destructor will be run by default. See details in `utils::Async`. In this case `engine::TaskWithResult::Get` will throw `engine::TaskCancelledException`.
331
+
There is one extra quirk: if the task is cancelled before being started, then only the functor's destructor will be run by default. See details in @refutils::Async. In this case @refengine::TaskWithResult::Get will throw @refengine::TaskCancelledException.
321
332
322
-
Tasks launched via `utils::CriticalAsync` are always started, even if cancelled before entering the function. The cancellation will take effect immediately after the function starts:
333
+
Tasks launched via @refutils::CriticalAsync are always started, even if cancelled before entering the function. The cancellation will take effect immediately after the function starts:
Note that the destructor of `engine::Task` cancels and waits for task to finish if the task has not finished yet. Use `concurrent::BackgroundTaskStorage` to continue task execution out of scope.
339
+
Note that the destructor of @refengine::Task cancels and waits for task to finish if the task has not finished yet. Use @refconcurrent::BackgroundTaskStorage to continue task execution out of scope.
329
340
330
-
The invariant that the task only runs within the lifetime of the `engine::Task` handle or `concurrent::BackgroundTaskStorage` is the backbone of structured concurrency in userver, see `utils::Async` and `concurrent::BackgroundTaskStorage` for details.
341
+
The invariant that the task only runs within the lifetime of the @refengine::Task handle or @refconcurrent::BackgroundTaskStorage is the backbone of structured concurrency in userver, see @refutils::Async and @refconcurrent::BackgroundTaskStorage for details.
331
342
332
343
### Utilities that interact with cancellations
333
344
334
345
The user is provided with several mechanisms to control the behavior of the application in case of cancellation:
335
346
336
-
*`engine::current_task::CancellationPoint()` -- if the task is canceled, calling this function throws an exception that is not caught during normal exception handling (not inherited from `std::exception`). This will result in stack unwinding with normal destructor calls for all local objects. The parent task will receive `engine::TaskCancelledException` from `engine::TaskWithResult::Get`.
347
+
*@refengine::current_task::CancellationPoint -- if the task is canceled, calling this function throws an exception that is not caught during normal exception handling (not inherited from `std::exception`). This will result in stack unwinding with normal destructor calls for all local objects. The parent task will receive @refengine::TaskCancelledException from @refengine::TaskWithResult::Get.
337
348
**⚠️🐙❗ Catching this exception results in UB, your code should not have `catch (...)` without `throw;` in the handler body**!
338
-
*`engine::current_task::ShouldCancel()` and `engine::current_task::IsCancelRequested()` -- predicates that return `true` if the task is canceled:
339
-
* by default, use `engine::current_task::ShouldCancel()`. It reports that a cancellation was requested for the task and the cancellation was not blocked (see below);
340
-
*`engine::current_task::IsCancelRequested()` notifies that the task was canceled even if cancellation was blocked; effectively ignoring caller's requests to complete the task regardless of cancellation.
341
-
*`engine::TaskCancellationBlocker` -- scope guard, preventing cancellation in the current task. As long as it is alive all the blocking calls are not interrupted, `engine::current_task::CancellationPoint` throws no exceptions, `engine::current_task::ShouldCancel` returns `false`.
342
-
**⚠️🐙❗ Disabling cancellation does not affect the return value of `engine::current_task::IsCancelRequested()`.**
349
+
*@refengine::current_task::ShouldCancel and @refengine::current_task::IsCancelRequested -- predicates that return `true` if the task is canceled:
350
+
* by default, use @refengine::current_task::ShouldCancel. It reports that a cancellation was requested for the task and the cancellation was not blocked (see below);
351
+
*@refengine::current_task::IsCancelRequested notifies that the task was canceled even if cancellation was blocked; effectively ignoring caller's requests to complete the task regardless of cancellation.
352
+
*@refengine::TaskCancellationBlocker -- scope guard, preventing cancellation in the current task. As long as it is alive all the blocking calls are not interrupted, @refengine::current_task::CancellationPoint throws no exceptions, @refengine::current_task::ShouldCancel returns `false`.
353
+
**⚠️🐙❗ Disabling cancellation does not affect the return value of @refengine::current_task::IsCancelRequested.**
0 commit comments