userver: userver/utils/async.hpp Source File
Loading...
Searching...
No Matches
async.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/async.hpp
4/// @brief Utility functions to start asynchronous tasks.
5
6#include <functional>
7#include <string>
8#include <utility>
9
10#include <userver/engine/async.hpp>
11#include <userver/utils/fast_pimpl.hpp>
12#include <userver/utils/lazy_prvalue.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace utils {
17
18namespace impl {
19
20// A wrapper that obtains a Span from args, attaches it to current coroutine,
21// and applies a function to the rest of arguments.
22struct SpanWrapCall {
23 enum class InheritVariables { kYes, kNo };
24
25 explicit SpanWrapCall(std::string&& name, InheritVariables inherit_variables);
26
27 SpanWrapCall(const SpanWrapCall&) = delete;
28 SpanWrapCall(SpanWrapCall&&) = delete;
29 SpanWrapCall& operator=(const SpanWrapCall&) = delete;
30 SpanWrapCall& operator=(SpanWrapCall&&) = delete;
31 ~SpanWrapCall();
32
33 template <typename Function, typename... Args>
34 auto operator()(Function&& f, Args&&... args) {
35 DoBeforeInvoke();
36 return std::invoke(std::forward<Function>(f), std::forward<Args>(args)...);
37 }
38
39private:
40 void DoBeforeInvoke();
41
42 struct Impl;
43
44 static constexpr std::size_t kImplSize = 4264;
45 static constexpr std::size_t kImplAlign = 8;
46 utils::FastPimpl<Impl, kImplSize, kImplAlign> pimpl_;
47};
48
49// Note: 'name' must outlive the result of this function
50inline auto SpanLazyPrvalue(std::string&& name) {
51 return utils::LazyPrvalue([&name] { return SpanWrapCall(std::move(name), SpanWrapCall::InheritVariables::kYes); });
52}
53
54} // namespace impl
55
56/// @ingroup userver_concurrency
57///
58/// @brief Starts an asynchronous task.
59///
60/// By default, arguments are copied or moved inside the resulting
61/// `TaskWithResult`, like `std::thread` does. To pass an argument by reference,
62/// wrap it in `std::ref / std::cref` or capture the arguments using a lambda.
63///
64/// @anchor flavors_of_async
65/// ## Flavors of `Async`
66///
67/// There are multiple orthogonal parameters of the task being started.
68/// Use this specific overload by default (`utils::Async`).
69///
70/// By engine::TaskProcessor:
71///
72/// * By default, task processor of the current task is used.
73/// * Custom task processor can be passed as the first or second function
74/// parameter (see function signatures).
75///
76/// By shared-ness:
77///
78/// * By default, functions return engine::TaskWithResult, which can be awaited
79/// from 1 task at once. This is a reasonable choice for most cases.
80/// * Functions from `utils::Shared*Async*` and `engine::Shared*AsyncNoSpan`
81/// families return engine::SharedTaskWithResult, which can be awaited
82/// from multiple tasks at the same time, at the cost of some overhead.
83///
84/// By engine::TaskBase::Importance ("critical-ness"):
85///
86/// * By default, functions can be cancelled due to engine::TaskProcessor
87/// overload. Also, if the task is cancelled before being started, it will not
88/// run at all.
89/// * If the whole service's health (not just one request) depends on the task
90/// being run, then functions from `utils::*CriticalAsync*` and
91/// `engine::*CriticalAsyncNoSpan*` families can be used. There, execution of
92/// the function is guaranteed to start regardless of engine::TaskProcessor
93/// load limits
94///
95/// By tracing::Span:
96///
97/// * Functions from `utils::*Async*` family (which you should use by default)
98/// create tracing::Span with inherited `trace_id` and `link`, a new `span_id`
99/// and the specified `stopwatch_name`, which ensures that logs from the task
100/// are categorized correctly and will not get lost.
101/// * Functions from `engine::*AsyncNoSpan*` family create span-less tasks:
102/// * A possible usage scenario is to create a task that will mostly wait
103/// in the background and do various unrelated work every now and then.
104/// In this case it might make sense to trace execution of work items,
105/// but not of the task itself.
106/// * Its usage can (albeit very rarely) be justified to squeeze some
107/// nanoseconds of performance where no logging is expected.
108/// But beware! Using tracing::Span::CurrentSpan() will trigger asserts
109/// and lead to UB in production.
110///
111/// By the propagation of engine::TaskInheritedVariable instances:
112///
113/// * Functions from `utils::*Async*` family (which you should use by default)
114/// inherit all task-inherited variables from the parent task.
115/// * Functions from `engine::*AsyncNoSpan*` family do not inherit any
116/// task-inherited variables.
117///
118/// By deadline: some `utils::*Async*` functions accept an `engine::Deadline`
119/// parameter. If the deadline expires, the task is cancelled. See `*Async*`
120/// function signatures for details.
121///
122/// ## Lifetime of captures
123///
124/// @note To launch background tasks, which are not awaited in the local scope,
125/// use concurrent::BackgroundTaskStorage.
126///
127/// When launching a task, it's important to ensure that it will not access its
128/// lambda captures after they are destroyed. Plain data captured by value
129/// (including by move) is always safe. By-reference captures and objects that
130/// store references inside are always something to be aware of. Of course,
131/// copying the world will degrade performance, so let's see how to ensure
132/// lifetime safety with captured references.
133///
134/// Task objects are automatically cancelled and awaited on destruction, if not
135/// already finished. The lifetime of the task object is what determines when
136/// the task may be running and accessing its captures. The task can only safely
137/// capture by reference objects that outlive the task object.
138///
139/// When the task is just stored in a new local variable and is not moved or
140/// returned from a function, capturing anything is safe:
141///
142/// @code
143/// int x{};
144/// int y{};
145/// // It's recommended to write out captures explicitly when launching tasks.
146/// auto task = utils::Async("frobnicate", [&x, &y] {
147/// // Capturing anything defined before the `task` variable is safe.
148/// Use(x, y);
149/// });
150/// // ...
151/// task.Get();
152/// @endcode
153///
154/// A more complicated example, where the task is moved into a container:
155///
156/// @code
157/// // Variables are destroyed in the reverse definition order: y, tasks, x.
158/// int x{};
159/// std::vector<engine::TaskWithResult<void>> tasks;
160/// int y{};
161///
162/// tasks.push_back(utils::Async("frobnicate", [&x, &y] {
163/// // Capturing x is safe, because `tasks` outlives `x`.
164/// Use(x);
165///
166/// // BUG! The task may keep running for some time after `y` is destroyed.
167/// Use(y);
168/// }));
169/// @endcode
170///
171/// The bug above can be fixed by placing the declaration of `tasks` after `y`.
172///
173/// In the case above people often think that calling `.Get()` in appropriate
174/// places solves the problem. It does not! If an exception is thrown somewhere
175/// before `.Get()`, then the variables' definition order is the source
176/// of truth.
177///
178/// Same guidelines apply when tasks are stored in classes or structs: the task
179/// object must be defined below everything that it accesses:
180///
181/// @code
182/// private:
183/// Foo foo_;
184/// // Can access foo_ but not bar_.
185/// engine::TaskWithResult<void> task_;
186/// Bar bar_;
187/// @endcode
188///
189/// Generally, it's a good idea to put task objects as low as possible
190/// in the list of class members.
191///
192/// Although, tasks are rarely stored in classes on practice,
193/// concurrent::BackgroundTaskStorage is typically used for that purpose.
194///
195/// Components and their clients can always be safely captured by reference:
196///
197/// @see @ref scripts/docs/en/userver/component_system.md
198///
199/// ## About this specific overload
200///
201/// This is the overload that should be used by default.
202///
203/// * The task will be launched on the current TaskProcessor.
204/// * Only 1 task may call `Wait` or `Get` on this task.
205/// * The task may be cancelled before the function starts execution
206/// in case of TaskProcessor overload. Also, if the task is cancelled for any
207/// reason before the function starts execution, it will not run at all.
208/// * The task will create a child tracing::Span with the specified name
209/// * The task will inherit all engine::TaskInheritedVariable instances
210/// from the current task.
211///
212/// For details on the various other overloads:
213/// @see @ref flavors_of_async
214///
215/// @param name Name of the task to show in logs
216/// @param f Function to execute asynchronously
217/// @param args Arguments to pass to the function
218/// @returns engine::TaskWithResult
219template <typename Function, typename... Args>
220[[nodiscard]] auto Async(std::string name, Function&& f, Args&&... args) {
221 return engine::AsyncNoSpan(
222 engine::current_task::GetTaskProcessor(),
223 impl::SpanLazyPrvalue(std::move(name)),
224 std::forward<Function>(f),
225 std::forward<Args>(args)...
226 );
227}
228
229/// @overload
230/// @ingroup userver_concurrency
231///
232/// Task execution may be cancelled before the function starts execution
233/// in case of TaskProcessor overload.
234///
235/// @param task_processor Task processor to run on
236/// @param name Name of the task to show in logs
237/// @param f Function to execute asynchronously
238/// @param args Arguments to pass to the function
239/// @returns engine::TaskWithResult
240template <typename Function, typename... Args>
241[[nodiscard]] auto Async(engine::TaskProcessor& task_processor, std::string name, Function&& f, Args&&... args) {
242 return engine::AsyncNoSpan(
243 task_processor, impl::SpanLazyPrvalue(std::move(name)), std::forward<Function>(f), std::forward<Args>(args)...
244 );
245}
246
247/// @overload
248/// @ingroup userver_concurrency
249///
250/// Execution of function is guaranteed to start regardless
251/// of engine::TaskProcessor load limits. Prefer utils::Async by default.
252///
253/// @param task_processor Task processor to run on
254/// @param name Name for the tracing::Span to use with this task
255/// @param f Function to execute asynchronously
256/// @param args Arguments to pass to the function
257/// @returns engine::TaskWithResult
258template <typename Function, typename... Args>
259[[nodiscard]] auto
260CriticalAsync(engine::TaskProcessor& task_processor, std::string name, Function&& f, Args&&... args) {
261 return engine::CriticalAsyncNoSpan(
262 task_processor, impl::SpanLazyPrvalue(std::move(name)), std::forward<Function>(f), std::forward<Args>(args)...
263 );
264}
265
266/// @overload
267/// @ingroup userver_concurrency
268///
269/// Execution of function is guaranteed to start regardless
270/// of engine::TaskProcessor load limits. Prefer utils::SharedAsync by default.
271///
272/// @param task_processor Task processor to run on
273/// @param name Name for the tracing::Span to use with this task
274/// @param f Function to execute asynchronously
275/// @param args Arguments to pass to the function
276/// @returns engine::SharedTaskWithResult
277template <typename Function, typename... Args>
278[[nodiscard]] auto
279SharedCriticalAsync(engine::TaskProcessor& task_processor, std::string name, Function&& f, Args&&... args) {
280 return engine::SharedCriticalAsyncNoSpan(
281 task_processor, impl::SpanLazyPrvalue(std::move(name)), std::forward<Function>(f), std::forward<Args>(args)...
282 );
283}
284
285/// @overload
286/// @ingroup userver_concurrency
287///
288/// Task execution may be cancelled before the function starts execution
289/// in case of TaskProcessor overload.
290///
291/// @param task_processor Task processor to run on
292/// @param name Name of the task to show in logs
293/// @param f Function to execute asynchronously
294/// @param args Arguments to pass to the function
295/// @returns engine::SharedTaskWithResult
296template <typename Function, typename... Args>
297[[nodiscard]] auto SharedAsync(engine::TaskProcessor& task_processor, std::string name, Function&& f, Args&&... args) {
298 return engine::SharedAsyncNoSpan(
299 task_processor, impl::SpanLazyPrvalue(std::move(name)), std::forward<Function>(f), std::forward<Args>(args)...
300 );
301}
302
303/// @overload
304/// @ingroup userver_concurrency
305///
306/// Task execution may be cancelled before the function starts execution
307/// in case of TaskProcessor overload.
308///
309/// @param task_processor Task processor to run on
310/// @param name Name of the task to show in logs
311/// @param f Function to execute asynchronously
312/// @param args Arguments to pass to the function
313/// @returns engine::TaskWithResult
314template <typename Function, typename... Args>
315[[nodiscard]] auto Async(
316 engine::TaskProcessor& task_processor,
317 std::string name,
318 engine::Deadline deadline,
319 Function&& f,
320 Args&&... args
321) {
322 return engine::AsyncNoSpan(
323 task_processor,
324 deadline,
325 impl::SpanLazyPrvalue(std::move(name)),
326 std::forward<Function>(f),
327 std::forward<Args>(args)...
328 );
329}
330
331/// @overload
332/// @ingroup userver_concurrency
333///
334/// Task execution may be cancelled before the function starts execution
335/// in case of TaskProcessor overload.
336///
337/// @param task_processor Task processor to run on
338/// @param name Name of the task to show in logs
339/// @param f Function to execute asynchronously
340/// @param args Arguments to pass to the function
341/// @returns engine::SharedTaskWithResult
342template <typename Function, typename... Args>
343[[nodiscard]] auto SharedAsync(
344 engine::TaskProcessor& task_processor,
345 std::string name,
346 engine::Deadline deadline,
347 Function&& f,
348 Args&&... args
349) {
350 return engine::SharedAsyncNoSpan(
351 task_processor,
352 deadline,
353 impl::SpanLazyPrvalue(std::move(name)),
354 std::forward<Function>(f),
355 std::forward<Args>(args)...
356 );
357}
358
359/// @overload
360/// @ingroup userver_concurrency
361///
362/// Execution of function is guaranteed to start regardless
363/// of engine::TaskProcessor load limits. Prefer utils::Async by default.
364///
365/// @param name Name for the tracing::Span to use with this task
366/// @param f Function to execute asynchronously
367/// @param args Arguments to pass to the function
368/// @returns engine::TaskWithResult
369template <typename Function, typename... Args>
370[[nodiscard]] auto CriticalAsync(std::string name, Function&& f, Args&&... args) {
371 return utils::CriticalAsync(
373 std::move(name),
374 std::forward<Function>(f),
375 std::forward<Args>(args)...
376 );
377}
378
379/// @overload
380/// @ingroup userver_concurrency
381///
382/// Execution of function is guaranteed to start regardless
383/// of engine::TaskProcessor load limits. Prefer utils::SharedAsync by default.
384///
385/// @param name Name for the tracing::Span to use with this task
386/// @param f Function to execute asynchronously
387/// @param args Arguments to pass to the function
388/// @returns engine::SharedTaskWithResult
389template <typename Function, typename... Args>
390[[nodiscard]] auto SharedCriticalAsync(std::string name, Function&& f, Args&&... args) {
391 return utils::SharedCriticalAsync(
393 std::move(name),
394 std::forward<Function>(f),
395 std::forward<Args>(args)...
396 );
397}
398
399/// @overload
400/// @ingroup userver_concurrency
401///
402/// Task execution may be cancelled before the function starts execution
403/// in case of TaskProcessor overload.
404///
405/// @param name Name of the task to show in logs
406/// @param f Function to execute asynchronously
407/// @param args Arguments to pass to the function
408/// @returns engine::SharedTaskWithResult
409template <typename Function, typename... Args>
410[[nodiscard]] auto SharedAsync(std::string name, Function&& f, Args&&... args) {
411 return utils::SharedAsync(
413 std::move(name),
414 std::forward<Function>(f),
415 std::forward<Args>(args)...
416 );
417}
418
419/// @overload
420/// @ingroup userver_concurrency
421///
422/// Task execution may be cancelled before the function starts execution
423/// in case of TaskProcessor overload.
424///
425/// @param name Name of the task to show in logs
426/// @param f Function to execute asynchronously
427/// @param args Arguments to pass to the function
428/// @returns engine::TaskWithResult
429template <typename Function, typename... Args>
430[[nodiscard]] auto Async(std::string name, engine::Deadline deadline, Function&& f, Args&&... args) {
431 return utils::Async(
433 std::move(name),
434 deadline,
435 std::forward<Function>(f),
436 std::forward<Args>(args)...
437 );
438}
439
440/// @overload
441/// @ingroup userver_concurrency
442///
443/// Task execution may be cancelled before the function starts execution
444/// in case of TaskProcessor overload.
445///
446/// @param name Name of the task to show in logs
447/// @param f Function to execute asynchronously
448/// @param args Arguments to pass to the function
449/// @returns engine::SharedTaskWithResult
450template <typename Function, typename... Args>
451[[nodiscard]] auto SharedAsync(std::string name, engine::Deadline deadline, Function&& f, Args&&... args) {
452 return utils::SharedAsync(
454 std::move(name),
455 deadline,
456 std::forward<Function>(f),
457 std::forward<Args>(args)...
458 );
459}
460
461/// @ingroup userver_concurrency
462///
463/// Starts an asynchronous task without propagating
464/// engine::TaskInheritedVariable. tracing::Span and baggage::Baggage are
465/// inherited. Task execution may be cancelled before the function starts
466/// execution in case of engine::TaskProcessor overload.
467///
468/// Typically used from a request handler to launch tasks that outlive the
469/// request and do not effect its completion.
470///
471/// ## Usage example
472/// Suppose you have some component that runs asynchronous tasks:
473/// @snippet utils/async_test.cpp AsyncBackground component
474/// @snippet utils/async_test.cpp AsyncBackground handler
475///
476/// If the tasks logically belong to the component itself (not to the method
477/// caller), then they should be launched using utils::AsyncBackground instead
478/// of the regular utils::Async
479/// @snippet utils/async_test.cpp AsyncBackground FooAsync
480///
481/// ## Arguments
482/// By default, arguments are copied or moved inside the resulting
483/// `TaskWithResult`, like `std::thread` does. To pass an argument by reference,
484/// wrap it in `std::ref / std::cref` or capture the arguments using a lambda.
485///
486/// @param name Name of the task to show in logs
487/// @param task_processor Task processor to run on
488/// @param f Function to execute asynchronously
489/// @param args Arguments to pass to the function
490/// @returns engine::TaskWithResult
491template <typename Function, typename... Args>
492[[nodiscard]] auto
493AsyncBackground(std::string name, engine::TaskProcessor& task_processor, Function&& f, Args&&... args) {
494 return engine::AsyncNoSpan(
495 task_processor,
496 utils::LazyPrvalue([&] {
497 return impl::SpanWrapCall(std::move(name), impl::SpanWrapCall::InheritVariables::kNo);
498 }),
499 std::forward<Function>(f),
500 std::forward<Args>(args)...
501 );
502}
503
504/// @overload
505/// @ingroup userver_concurrency
506///
507/// Execution of function is guaranteed to start regardless
508/// of engine::TaskProcessor load limits. Use for background tasks for which
509/// failing to start not just breaks handling of a single request, but harms
510/// the whole service instance.
511///
512/// @param name Name of the task to show in logs
513/// @param task_processor Task processor to run on
514/// @param f Function to execute asynchronously
515/// @param args Arguments to pass to the function
516/// @returns engine::TaskWithResult
517template <typename Function, typename... Args>
518[[nodiscard]] auto
519CriticalAsyncBackground(std::string name, engine::TaskProcessor& task_processor, Function&& f, Args&&... args) {
520 return engine::CriticalAsyncNoSpan(
521 task_processor,
522 utils::LazyPrvalue([&] {
523 return impl::SpanWrapCall(std::move(name), impl::SpanWrapCall::InheritVariables::kNo);
524 }),
525 std::forward<Function>(f),
526 std::forward<Args>(args)...
527 );
528}
529
530} // namespace utils
531
532USERVER_NAMESPACE_END