userver: userver/engine/impl/task_context_factory.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
task_context_factory.hpp
1#pragma once
2
3#include <cstddef>
4#include <memory>
5#include <new>
6#include <utility>
7
8#include <userver/engine/impl/task_context_holder.hpp>
9#include <userver/engine/task/task.hpp>
10#include <userver/utils/fast_scope_guard.hpp>
11#include <userver/utils/impl/wrapped_call.hpp>
12
13USERVER_NAMESPACE_BEGIN
14
15namespace engine::impl {
16
17std::size_t GetTaskContextSize() noexcept;
18
19// cpp contains a static_assert that this matches the actual alignment.
20inline constexpr std::size_t kTaskContextAlignment = 16;
21
22struct TaskConfig final {
23 engine::TaskProcessor& task_processor;
26 engine::Deadline deadline;
27};
28
29[[nodiscard]] TaskContext& PlacementNewTaskContext(
30 std::byte* storage, TaskConfig config,
31 utils::impl::WrappedCallBase& payload);
32
33// Never returns nullptr, may throw.
34std::byte* AllocateFusedTaskContext(std::size_t total_size);
35
36void DeleteFusedTaskContext(std::byte* storage) noexcept;
37
38// The allocations for TaskContext and WrappedCall are manually fused. The
39// layout is as follows:
40// 1. TaskContext, guaranteed to be at the beginning of the allocation
41// 2. WrappedCall
42//
43// The whole allocation, as well as the lifetimes of the objects inside, are
44// managed by boost::intrusive_ptr<TaskContext> through intrusive_ptr_add_ref
45// and intrusive_ptr_release hooks.
46template <typename Function, typename... Args>
47TaskContextHolder MakeTask(TaskConfig config, Function&& f, Args&&... args) {
48 using WrappedCallType = utils::impl::WrappedCallImplType<Function, Args...>;
49
50 constexpr auto kPayloadSize = sizeof(WrappedCallType);
51 constexpr auto kPayloadAlignment = alignof(WrappedCallType);
52 // Makes sure that the WrappedCall is aligned appropriately just by being
53 // placed after TaskContext. To remove this limitation, we could store
54 // the dynamic alignment somewhere and use it in DeleteFusedTaskContext.
55 static_assert(kPayloadAlignment <= kTaskContextAlignment);
56
57 const auto task_context_size = GetTaskContextSize();
58 const auto total_size = task_context_size + kPayloadSize;
59
60 std::byte* const storage = AllocateFusedTaskContext(total_size);
61 utils::FastScopeGuard delete_guard{
62 [&]() noexcept { DeleteFusedTaskContext(storage); }};
63
64 std::byte* const payload_storage = storage + task_context_size;
65
66 auto& payload = utils::impl::PlacementNewWrapCall(
67 payload_storage, std::forward<Function>(f), std::forward<Args>(args)...);
68 utils::FastScopeGuard destroy_payload_guard{
69 [&]() noexcept { std::destroy_at(&payload); }};
70
71 auto& context = PlacementNewTaskContext(storage, config, payload);
72
73 destroy_payload_guard.Release();
74 delete_guard.Release();
75 return TaskContextHolder::Adopt(context);
76}
77
78} // namespace engine::impl
79
80USERVER_NAMESPACE_END