userver: userver/compiler/thread_local.hpp Source File
Loading...
Searching...
No Matches
thread_local.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/compiler/thread_local.hpp
4/// @brief Utilities for using thread-local variables in a coroutine-safe way
5
6#include <type_traits>
7
8#include <userver/compiler/impl/lifetime.hpp>
9#include <userver/compiler/impl/tls.hpp>
10
11#if __cplusplus >= 202002L && (__clang_major__ >= 13 || !defined(__clang__) && __GNUC__ >= 9)
12// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
13#define USERVER_IMPL_UNEVALUATED_LAMBDAS
14#endif
15
16USERVER_NAMESPACE_BEGIN
17
18namespace compiler {
19
20namespace impl {
21
22bool AreCoroutineSwitchesAllowed() noexcept;
23
24void IncrementLocalCoroutineSwitchBans() noexcept;
25
26void DecrementLocalCoroutineSwitchBans() noexcept;
27
28#ifdef USERVER_IMPL_UNEVALUATED_LAMBDAS
29template <typename T, typename Factory = decltype([] { return T{}; })>
30using UniqueDefaultFactory = Factory;
31#else
32template <typename T>
33struct UniqueDefaultFactory final {
34 static_assert(
35 !sizeof(T),
36 "Defaulted syntax for compiler::ThreadLocal is unavailable on "
37 "your compiler. Please use the lambda-factory syntax, see the "
38 "documentation for compiler::ThreadLocal."
39 );
40};
41#endif
42
43} // namespace impl
44
45/// @brief The scope that grants access to a thread-local variable.
46///
47/// @see compiler::ThreadLocal
48template <typename VariableType>
49class ThreadLocalScope final {
50public:
51 ThreadLocalScope(ThreadLocalScope&&) = delete;
52 ThreadLocalScope& operator=(ThreadLocalScope&&) = delete;
53 ~ThreadLocalScope();
54
55 /// Access the thread-local variable.
56 VariableType& operator*() & noexcept USERVER_IMPL_LIFETIME_BOUND;
57
58 /// Access the thread-local variable.
59 VariableType* operator->() & noexcept USERVER_IMPL_LIFETIME_BOUND;
60
61 /// @cond
62 explicit ThreadLocalScope(VariableType& variable) noexcept;
63
64 // Store ThreadLocalScope to a variable before using.
65 VariableType& operator*() && noexcept = delete;
66
67 // Store ThreadLocalScope to a variable before using.
68 VariableType* operator->() && noexcept = delete;
69 /// @endcond
70
71private:
72 static_assert(!std::is_reference_v<VariableType>);
73 static_assert(!std::is_const_v<VariableType>);
74
75 VariableType& variable_;
76};
77
78/// @brief Creates a unique thread-local variable that can be used
79/// in a coroutine-safe manner.
80///
81/// Thread-local variables are known to cause issues when used together
82/// with userver coroutines:
83///
84/// * https://github.com/userver-framework/userver/issues/235
85/// * https://github.com/userver-framework/userver/issues/242
86///
87/// Thread-local variables created through this class are protected against
88/// these issues.
89///
90/// Example usage:
91///
92/// @snippet compiler/thread_local_test.cpp sample definition
93/// @snippet compiler/thread_local_test.cpp sample
94///
95/// The thread-local variable is value-initialized.
96///
97/// In C++17 mode, or if you need to initialize the variable with some
98/// arguments, the ThreadLocal should be passed a capture-less lambda that
99/// constructs the variable. Example:
100///
101/// @snippet compiler/thread_local_test.cpp sample factory
102///
103/// Once acquired through @a Use, the reference to the thread-local variable
104/// should not be returned or otherwise escape the scope
105/// of the ThreadLocalScope object. An example of buggy code:
106///
107/// @code
108/// compiler::ThreadLocal<std::string> local_buffer;
109///
110/// std::string_view PrepareBuffer(std::string_view x, std::string_view y) {
111/// const auto buffer = local_buffer.Use();
112/// buffer->clear();
113/// buffer->append(x);
114/// buffer->append(y);
115/// return *buffer; // <- BUG! Reference to thread-local escapes the scope
116/// }
117/// @endcode
118///
119/// Do not store a reference to the thread-local object in a separate variable:
120///
121/// @code
122/// compiler::ThreadLocal<std::string> local_buffer;
123///
124/// void WriteMessage(std::string_view x, std::string_view y) {
125/// auto buffer_scope = local_buffer.Use();
126/// // Code smell! This makes it more difficult to see that `buffer` is
127/// // a reference to thread-local at a glance.
128/// auto& buffer = *buffer_scope;
129/// buffer.clear();
130/// // ...
131/// }
132/// @endcode
133///
134/// Until the variable name goes out of scope, userver engine synchronization
135/// primitives and clients (web or db) should not be used.
136template <typename VariableType, typename Factory = impl::UniqueDefaultFactory<VariableType>>
137class ThreadLocal final {
138 static_assert(std::is_empty_v<Factory>);
139 static_assert(std::is_same_v<VariableType, std::invoke_result_t<const Factory&>>);
140
141public:
142 consteval ThreadLocal()
143 : factory_(Factory{})
144 {}
145
146 consteval /*implicit*/ ThreadLocal(Factory factory)
147 : factory_(factory)
148 {}
149
150 ThreadLocalScope<VariableType> Use() { return ThreadLocalScope<VariableType>(impl::ThreadLocal(factory_)); }
151
152private:
153 // The ThreadLocal instance should have static storage duration. Still, if a
154 // user defines it as a local variable or even a thread_local variable, it
155 // should be harmless in practice, because ThreadLocal is an empty type,
156 // mainly used to store the `FactoryFunc` template parameter.
157 Factory factory_;
158};
159
160template <typename Factory>
162
163template <typename VariableType>
164ThreadLocalScope<VariableType>::ThreadLocalScope(VariableType& variable) noexcept : variable_(variable) {
165#ifndef NDEBUG
166 impl::IncrementLocalCoroutineSwitchBans();
167#endif
168}
169
170template <typename VariableType>
171ThreadLocalScope<VariableType>::~ThreadLocalScope() {
172#ifndef NDEBUG
173 impl::DecrementLocalCoroutineSwitchBans();
174#endif
175}
176
177template <typename VariableType>
178VariableType& ThreadLocalScope<VariableType>::operator*() & noexcept //
179USERVER_IMPL_LIFETIME_BOUND {
180 return variable_;
181}
182
183template <typename VariableType>
184VariableType* ThreadLocalScope<VariableType>::operator->() & noexcept //
185USERVER_IMPL_LIFETIME_BOUND {
186 return &**this;
187}
188
189} // namespace compiler
190
191USERVER_NAMESPACE_END