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/constexpr.hpp>
9#include <userver/compiler/impl/lifetime.hpp>
10#include <userver/compiler/impl/tls.hpp>
11
12#if __cplusplus >= 202002L &&
13 (__clang_major__ >= 13 || !defined(__clang__) && __GNUC__ >= 9)
14// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
15#define USERVER_IMPL_UNEVALUATED_LAMBDAS
16#endif
17
18USERVER_NAMESPACE_BEGIN
19
20namespace compiler {
21
22namespace impl {
23
24bool AreCoroutineSwitchesAllowed() noexcept;
25
26void IncrementLocalCoroutineSwitchBans() noexcept;
27
28void DecrementLocalCoroutineSwitchBans() noexcept;
29
30#ifdef USERVER_IMPL_UNEVALUATED_LAMBDAS
31template <typename T, typename Factory = decltype([] { return T{}; })>
32using UniqueDefaultFactory = Factory;
33#else
34template <typename T>
35struct UniqueDefaultFactory final {
36 static_assert(!sizeof(T),
37 "Defaulted syntax for compiler::ThreadLocal is unavailable on "
38 "your compiler. Please use the lambda-factory syntax, see the "
39 "documentation for compiler::ThreadLocal.");
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 {
50 public:
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
71 private:
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,
137 typename Factory = impl::UniqueDefaultFactory<VariableType>>
138class ThreadLocal final {
139 static_assert(std::is_empty_v<Factory>);
140 static_assert(
141 std::is_same_v<VariableType, std::invoke_result_t<const Factory&>>);
142
143 public:
144 USERVER_IMPL_CONSTEVAL ThreadLocal() : factory_(Factory{}) {}
145
146 USERVER_IMPL_CONSTEVAL /*implicit*/ ThreadLocal(Factory factory)
147 : factory_(factory) {}
148
149 ThreadLocalScope<VariableType> Use() {
150 return ThreadLocalScope<VariableType>(impl::ThreadLocal(factory_));
151 }
152
153 private:
154 // The ThreadLocal instance should have static storage duration. Still, if a
155 // user defines it as a local variable or even a thread_local variable, it
156 // should be harmless in practice, because ThreadLocal is an empty type,
157 // mainly used to store the `FactoryFunc` template parameter.
158 Factory factory_;
159};
160
161template <typename Factory>
162ThreadLocal(Factory factory)
163 -> ThreadLocal<std::invoke_result_t<const Factory&>, Factory>;
164
165template <typename VariableType>
166ThreadLocalScope<VariableType>::ThreadLocalScope(
167 VariableType& variable) noexcept
168 : variable_(variable) {
169#ifndef NDEBUG
170 impl::IncrementLocalCoroutineSwitchBans();
171#endif
172}
173
174template <typename VariableType>
175ThreadLocalScope<VariableType>::~ThreadLocalScope() {
176#ifndef NDEBUG
177 impl::DecrementLocalCoroutineSwitchBans();
178#endif
179}
180
181template <typename VariableType>
182VariableType& ThreadLocalScope<VariableType>::operator*() & noexcept //
184 return variable_;
185}
186
187template <typename VariableType>
188VariableType* ThreadLocalScope<VariableType>::operator->() & noexcept //
190 return &**this;
191}
192
193} // namespace compiler
194
195USERVER_NAMESPACE_END