userver: userver/engine/impl/task_context_factory.hpp Source File
Loading...
Searching...
No Matches
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