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