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