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