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
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 tasks_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 tasks_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 tasks_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 tasks_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 tasks_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 tasks_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 tasks_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} // namespace utils
479
480USERVER_NAMESPACE_END