Flow 3.0.0
Flow project: Full implementation reference.
thread_lcl.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
21#include <flow/util/util.hpp>
24#include <flow/log/log.hpp>
25#include <boost/thread.hpp>
26#include <boost/unordered_map.hpp>
27#include <boost/shared_ptr.hpp>
28#include <boost/weak_ptr.hpp>
29#include <atomic>
30#include <string>
31#include <utility>
32#include <vector>
33#include <typeinfo>
34#include <type_traits>
35
36namespace flow::util
37{
38// Types.
39
40/**
41 * Similar to Thread_local_ptr<T> (a perf-upgraded `boost::thread_specific_ptr<T>`) but allows iteration through
42 * all per-thread data (*registry*) under temp lock. It also has some slightly different conventions, including
43 * lazy/on-demand construction per-thread and destruction always via dtor; but these are mainly incidental:
44 * The under-lock iteration through extant #Thread_local_state objects is the key differentiator.
45 *
46 * An object of this type manages thread-local data as encapsulated in the user-supplied-as-template-arg type,
47 * each of which is instantiated (constructed) as needed for each given thread (on first this_thread_state() accessor
48 * call in that thread); and cleaned (via destructor) at each thread's exit or `*this` destruction -- whichever
49 * occurs earlier.
50 *
51 * ### Stability note ###
52 * Below we discuss, non-chalantly, the use-case wherein a `*this` dtor executes at a time when a different
53 * thread U has a non-null this_thread_state_or_null() (U is a/k/a *relevant thread*);
54 * and how we do support this and how things work in that case. However, since writing that stuff -- though it
55 * does remain broadly accurate -- we now, in short, suggest the following: avoid that use case.
56 * - When possible it is best to declare a `*this` as `static` (or global). (This is pretty typical.)
57 * - If not, then it is equally fine if one ensures all relevant threads join before `*this` dtor runs.
58 *
59 * If you heed this, then there is no latent stability concern. `*this` class is extremely useful regardless
60 * of this limitation.
61 *
62 * Otherwise -- a relevant thread continues operating while `*this` is destroyed -- the remaining lifetime of
63 * each such thread does have a stability-related danger. Once the to-do in Thread_local_ptr doc header is
64 * resolved -- if it is resolved -- then the danger automatically disappears. After this section here,
65 * code generally is written as-if there is no issue.
66 *
67 * @see Thread_local_ptr doc header "A corner case" section.
68 *
69 * Lastly: If it is essential to chance it anyway: There are ways in which even now this can be safe. We won't
70 * try to list the criteria here. At that point it's best if you head to Thread_local_ptr and read about it
71 * and draw the proper conclusions.
72 *
73 * ### Overview/rationale ###
74 * Fundamentally `Thread_local_state_registry<T>` is quite similar to Thread_local_ptr<T>
75 * which is identical to `boost::thread_specific_ptr` but faster w/r/t `.get()`.
76 * There are some minor differences (it is more rigid, always using `new` and `delete` instead of leaving it
77 * to the user; mandating a lazy-initialization semantic instead of leaving it to the user;
78 * disallowing any reset to null and back from null), but these are just happenstance/preference-based.
79 * Likely we'd have just used the Boost guy, if that's all we wanted.
80 *
81 * The main reason `Thread_local_state_registry<T>` exists comprises the following (mutually related) features:
82 * -# The ability to look at all per-thread data accumulated so far (from any
83 * thread). See while_locked(), particularly the `state_per_thread` argument passed to `task()` in-arg.
84 * -# If `~Thread_local_state_registry` (a `*this` dtor) executes before a given thread U (that has earlier
85 * caused the creation of a thread-local `T`, by calling `this->this_thread_state()` from U) is joined (exits),
86 * then:
87 * - That dtor, from whichever thread invoked it, deletes that thread-local `T` (for all `T`).
88 * - (There is a stability caveat. See "Stability note.")
89 *
90 * Feature 1 is a clear value-add over `thread_specific_ptr` (or just `static thread_local`). If one can get by
91 * without dealing with the set of per-thread objects from any 1 thread at a given time, that's good; it will involve
92 * far fewer corner cases and worries. Unfortunately it is not always possible to do that. In that case you want
93 * a *registry* of your `Thread_local_state`s; a `*this` provides this.
94 *
95 * As for feature 2: Consider `static thread_specific_ptr` (or `static thread_local`, broadly speaking, without getting
96 * into the formal details of C++ language guarantees as to how such per-thread items are cleaned up). By definition
97 * of `static` the `thread_specific_ptr` will outlive any threads to have been spawned by the time `main()` exits.
98 * Therefore an implicit `.reset()` will execute when extant threads are joined, and each thread-local object will
99 * be cleaned up. No problem! However, a non-`static Thread_local_ptr` offers no such behavior or guarantee:
100 * If the `~Thread_local_ptr()` dtor runs in thread U, at most that thread's TL object shall be auto-`.reset()`.
101 * The other extant threads' TL objects will live on (leak) at least until respective thread exit. (Nor can one
102 * iterate through them; that would be feature 1.)
103 *
104 * However a `*this` being destroyed in thread U will cause an automatic looping through the extant threads'
105 * objects (if any) and their cleanup as well. So if you want that, use a `*this` instead of
106 * non-`static thread_specific_ptr`.
107 *
108 * As a secondary reason (ignoring the above 2 features) `Thread_local_state_registry` has a more straightforward/rigid
109 * API that enforces certain assumptions/conventions (some of this is mentioned above). These might be appealing
110 * depending on one's taste/reasoning.
111 *
112 * ### How to use ###
113 * Ensure the template-arg #Thread_local_state has a proper destructor; and either ensure it has default (no-arg) ctor
114 * (in which case it'll be created via `new Thread_local_state`), or assign #m_create_state_func (to choose
115 * a method of creation yourself).
116 *
117 * Addendum: the default is `new Thread_local_state{L}` (where `L` is the one passed-to most-recent set_logger() or
118 * `*this` ctor) if and only if `Thread_local_state*` is convertible to `Log_context_mt*` (i.e., the latter
119 * is a `public` super-class of the former). See also Logging section below.
120 *
121 * From any thread where you need a #Thread_local_state, call `this->this_thread_state()`. The first time
122 * in a given thread, this shall perform and save `new Thread_local_state`; subsequent times it shall return
123 * the same pointer. (You can also save the pointer and reuse it; just be careful.)
124 *
125 * A given thread's #Thread_local_state object shall be deleted via `delete Thread_local_state` when one of
126 * the following occurs, whichever happens first:
127 * - The thread exits (is joined). (Deletion occurs shortly before.)
128 * - `*this` is destroyed (in some -- any -- thread, possibly a totally different one; or one of the ones
129 * for which this_thread_state() was called).
130 *
131 * That's it. It is simple. However we emphasize that using this properly may take some care: particularly
132 * as concerns the contents of the #Thread_local_state dtor (whether auto-generated or explicit or some mix), which
133 * might run from the relevant thread U in which the underlying object was created and used; but it might
134 * also instead run from whichever thread invokes the Thread_local_state_registry dtor first.
135 *
136 * See doc header for ~Thread_local_state_registry() (dtor) for more on that particular subject.
137 *
138 * ### The lifecycle of each `Thread_local_state`: locking ###
139 * In a given thread T (and w/r/t a given `*this`):
140 * -# *Birth*: the first this_thread_state() call in T locks the registry; constructs the #Thread_local_state
141 * (so its ctor runs *inside* the locked section -- a formal guarantee; rationale in this_thread_state()
142 * doc header); inserts it into the registry; unlocks.
143 * -# *Life*: this_thread_state() in T returns the same pointer, lock-free; other threads can reach the object
144 * via while_locked().
145 * -# *Death*, as T begins to exit (or in `*this` dtor -- whichever comes first): lock the registry; erase the
146 * #Thread_local_state from it; unlock; only *then* destroy it. So its dtor runs *outside* the locked
147 * section -- also a formal guarantee -- and by then while_locked() no longer lists the object.
148 *
149 * The death-side guarantee matters in practice: `~Thread_local_state()` may itself lock things -- in the
150 * thread-exit case even `this->while_locked()` -- without deadlock. (In the `*this`-dtor case do not call
151 * anything on `*this` from that dtor, of course; but the after-un-listing/outside-the-lock ordering holds
152 * there just the same.)
153 *
154 * @note Corollary: While `*this` is alive, it is possible to *create* a `Thread_local_state` -- via the usual
155 * this_thread_state() call -- in a `~Thread_local_state()` dtor. (Informally we recommend against this
156 * where possible, for general sanity/entropy reasons.) Per the following section it will experience the
157 * full lifecycle, notably including *death*, before the thread is gone.
158 *
159 * ### Cleanup phase ###
160 * (The following mirrors what `thread_specific_ptr`/`Thread_local_ptr` does. If you're familiar with the
161 * cleanup-phase semantics of these, probably OK to skip it.)
162 *
163 * Consider a given thread T exiting, with N `Thread_local_state_registry`-owned `Thread_local_state` objects
164 * live at that time. In this case consider *all* such registries, not just ones with any one particular
165 * `Thread_local_state` type. At a certain point close to thread death, the cleanup phase -- for (reiterating)
166 * all `Thread_local_state_registry`-owned TL-state objects -- executes.
167 *
168 * First, any registered `boost::this_thread::at_thread_exit` functions execute in some order. We mention this
169 * here, as for some advanced needs it may be helpful to execute something related to your `Thread_local_state`s
170 * that is (1) near thread death but (2) definitely precedes the `~Thread_local_state()` dtor(s) executing
171 * (which occurs in the next step).
172 *
173 * Next, for each of the N extant `Thread_local_state`s (again: of any concrete type): the *death* procedure executes
174 * (as explained in the preceding section: lock/remove/unlock/destroy). The order is unspecified. Now, the
175 * tricky part: the "destroy" part (`~Thread_local_state()` dtor) may itself re-insert more TL-state object(s)
176 * back into this (doomed) thread's set of extant TL-states. So there are again N (new value) extant
177 * TL-states. Therefore: cleanup phase continues by again invoking the *death* procedure for each such
178 * `Thread_local_state`, again in some unspecified order. This is repeated until no more extant TL-states are
179 * detected (N=0).
180 *
181 * Recap: birth-life-death lifecycles for various `Thread_local_state`s can execute during a *cleanup phase*
182 * of a thread, because of said cleanup phase's stubborn algorithm. Informally: this is best avoided, if possible
183 * (again for sanity/entropy), but in our experience it can be useful, especially when `~Thread_local_state1()`
184 * dtor accesses `Thread_local_state_registry<Thread_local_state2>` (different type).
185 *
186 * ### How to iterate over/possibly modify other threads' data (safely) ###
187 * In short please see while_locked() doc header.
188 *
189 * Also, particularly if you are employing some variation on the "thread-cached access to central state" pattern,
190 * it is potentially critical to read the relevant notes in the this_thread_state() doc header.
191 *
192 * ### Logging ###
193 * Logging is somewhat more subtle than is typically the case, because a Thread_local_state_registry is often
194 * declared `static` or even global, which means a log::Logger might not be available at times such as before `main()`,
195 * after `main()`, or inside `main()` but outside when some main `Logger` is available. Therefore it is
196 * often useful to, e.g., start with `Logger* = nullptr`, change it to something else, then change it back.
197 *
198 * Please use set_logger() accordingly.
199 *
200 * However to avoid any trouble if this_thread_state() is called during a `Logger` change:
201 * - set_logger() is guaranteed thread-safe.
202 * - Internally, perf-wise, we take steps to avoid this having any appreciable effect on fast-path performance.
203 * - If and only if `Thread_local_state*` is convertible to `Log_context_mt*` (i.e., the latter is a `public`
204 * super-class of the former), set_logger() shall invoke `state->set_logger()` to each `state` in
205 * `state_per_thread` as passed to `task()` in `while_locked()` (i.e., all `Thread_local_state`s currently extant).
206 * - Note that you can override `Log_context_mt::set_logger()` in your `Thread_local_state` so as to, e.g.,
207 * further propagate the new logger to other parts of `Thread_local_state` internals.
208 *
209 * @tparam Thread_local_state_t
210 * Managed object type -- see above. We repeat: must have no-arg (default) ctor, or be compatible with your
211 * custom #m_create_state_func; dtor must perform
212 * appropriate cleanup which in particular shall be run from exactly one of exactly the following 2 contexts:
213 * (1) from the thread in which it was created via this_thread_state(), just before the thread exits;
214 * (2) from within the `*this` dtor from whichever thread that was invoked (which may be the creation-thread;
215 * one of the other creation-threads; or some other thread entirely). See dtor doc header. Regarding (2)
216 * also see "Stability note" above.
217 */
218template<typename Thread_local_state_t>
220 /* Note 1: Not _mt; we use our own while_locked() around log-call-sites and avoid logging along fast-path(s).
221 * Note 2: private, not public; we don't need them doing thread-unsafe get_logger(), let alone bypassing our
222 * set_logger(). */
223 private log::Log_context,
224 private boost::noncopyable
225{
226public:
227 // Types.
228
229 /// Short-hand for template parameter type. See our class doc header for requirements.
230 using Thread_local_state = Thread_local_state_t;
231
232 /// General info (as of this writing for logging only) about a given entry (thread/object) in #State_per_thread_map.
233 struct Metadata
234 {
235 // Data.
236
237 /// Thread nickname as per log::Logger::set_thread_info().
238 std::string m_thread_nickname;
239 /// Thread ID. See #Thread_id doc header for important warning about how these can be recycled (not fully unique).
241 /// Unique thread token. See this_thread_unique_token() doc header for details; in short: it is fully unique.
243 };
244
245 /**
246 * Type of `state_per_thread` to `task()` to while_locked(): extant `Thread_local_state`s and info about their
247 * respective constructing threads.
248 */
249 using State_per_thread_map = boost::unordered_map<Thread_local_state*, Metadata>;
250
251 // Constants.
252
253 /**
254 * `true` if and only if #Thread_local_state is a public sub-class of log::Log_context_mt which has
255 * implications on set_logger() and default #m_create_state_func behavior.
256 */
257 static constexpr bool S_TL_STATE_HAS_MT_LOG_CONTEXT = std::is_convertible_v<Thread_local_state*,
259
260 static_assert(!std::is_convertible_v<Thread_local_state*, log::Log_context*>,
261 "Thread_local_state_t template param type should not derive from log::Log_context, as "
262 "then set_logger() is not thread-safe; please use Log_context_mt (but be mindful of "
263 "locking-while-logging perf effects in fast-paths).");
264
265 // Data.
266
267 /**
268 * this_thread_state(), when needing to create a thread's local new #Thread_local_state to return, makes
269 * a stack copy of this member, calls that copy with no args, and uses the `Thread_local_state*` result
270 * as the return value for that thread.
271 *
272 * If, when needed, this value is null (`m_create_state_func.empty() == true`), then:
273 * - If #Thread_local_state is default-ctible and #S_TL_STATE_HAS_MT_LOG_CONTEXT is `false`:
274 * uses `new Thread_local_state`.
275 * - If #Thread_local_state is ctible in form `Thread_local_state{lgr}` where (`lgr` is a `log::Logger*`),
276 * and #S_TL_STATE_HAS_MT_LOG_CONTEXT is `true`:
277 * uses `new Thread_local_state{L}`, where `L` is the `Logger*` from the last set_logger() or ctor.
278 * - Otherwise: Behavior is undefined (assertion may trip at this time).
279 *
280 * `m_create_state_func` must return a pointer that can be `delete`d in standard fashion.
281 * It must not throw; otherwise behavior is undefined.
282 *
283 * ### Thread safety ###
284 * It is not safe to assign this while a thread-first this_thread_state() is invoked.
285 */
287
288 // Constructors/destructor.
289
290 /**
291 * Create empty registry. Subsequently you may call this_thread_state() from any thread where you want to use
292 * (when called first time, create) thread-local state (a #Thread_local_state).
293 *
294 * @param logger_ptr
295 * Logger to use for logging subsequently.
296 * @param nickname_str
297 * See nickname().
298 * @param create_state_func
299 * Initial value for #m_create_state_func. Default is an `.empty()` (see member doc header for info).
300 */
301 explicit Thread_local_state_registry(log::Logger* logger_ptr, String_view nickname_str,
302 decltype(m_create_state_func)&& create_state_func = {});
303
304 /**
305 * Deletes each #Thread_local_state to have been created so far by calls to this_thread_state() from various
306 * threads (possibly but not necessarily including this thread).
307 *
308 * ### Careful! ###
309 * No thread (not the calling or any other thread) must access a #Thread_local_state returned from `*this`, once
310 * this dtor begins executing. This is usually pretty natural to guarantee by having
311 * your Thread_local_state_registry properly placed among various private data members and APIs accessing them.
312 *
313 * If this dtor runs such that there is at least one other thread where this_thread_state_or_null() is not null:
314 * The dtor in the type #Thread_local_state itself must correctly run from *any* thread.
315 * - For many things that's no problem... just normal C++ data and `unique_ptr`s and such.
316 * - For some resources it might be a problem, namely for resources that are thread-local in nature that must
317 * be explicitly freed via API calls.
318 * Example: flushing a memory manager's thread-cache X created for/in thread T might be only possible
319 * in thread T; while also being a quite-natural thing to do in that thread, during thread T cleanup.
320 * From any other thread it might lead to undefined behavior.
321 * - In this case recall this *fact*:
322 * `~Thread_local_state()` shall run *either* from its relevant thread; *or* from
323 * the daddy `~Thread_local_state_registry()`.
324 * - Usually in the latter case, everything is going down anyway -- hence typically it is not necessary to
325 * specifically clean such per-thread resources as thread-caches.
326 * - So it is simple to:
327 * - Save a data member containing, e.g., this_thread_unique_token() in #Thread_local_state.
328 * - (Do *not* use #Thread_id `this_thread::get_id()` or OS-equivalent. A given ID can easily be
329 * recycled after thread-end which creates uncertainty in this context.)
330 * - In its dtor check whether the thread-token at *that* time equals the saved one. If so -- great,
331 * clean the thing. If not -- just don't (it is probably moot as shown above).
332 * - If it is not moot, you'll have to come up with something clever. Unlikely though.
333 *
334 * Reminder: In many cases one shall declare `*this` as `static` or global; in which case threads normally
335 * exit before this dtor would run. Hence then by definition no other thread with non-null TL-state will
336 * exist, and it is not an issue. If not `static` or global, it is often practical to join the relevant
337 * threads before letting a `*this` expire.
338 */
340
341 // Methods.
342
343 /**
344 * Returns pointer to this thread's thread-local object, first constructing it via #m_create_state_func if
345 * it is the first `this->this_thread_state()` call in this thread. In a given thread this shall always return
346 * the same pointer. Reminder (see #m_create_state_func): that guy must not throw; else undefined behavior.
347 *
348 * The pointee shall be destroyed from `*this` dtor or just before this thread's exit, from this thread, whichever
349 * occurs first. You may not call this, or use the returned pointer, after either routine begins executing.
350 *
351 * ### Thread-caching of central canonical state: Interaction with while_locked() ###
352 * The following is irrelevant in the fast-path, wherein this is *not* the first call to this method in the
353 * current thread. It is relevant only in the fast-path, wherein this *is* the first call to this method in the
354 * current thread. In that case we make the following formal guarantee:
355 *
356 * - A ctor of #Thread_local_state is invoked (as you know).
357 * - It is invoked while_locked().
358 *
359 * The most immediate consequence of the latter is simply: Do not call while_locked() inside #Thread_local_state
360 * ctor; it will deadlock. That aside though:
361 *
362 * What's the rationale for this guarantee? Answer: In many cases it does not matter, and other than the last bullet
363 * one would not need to worry about it. It *can* however matter in more complex setups, namely the
364 * pattern "thread-caching of central canonical state." In this pattern:
365 *
366 * - Some kind of *central state* (e.g., *canonical* info being distributed into thread-local caches) must be
367 * - seeded (copied, as a *pull*) into any new #Thread_local_state; and
368 * - updated (copied, as a *push*) into any existing #Thread_local_state, if the canonical state itself is
369 * modified (usually assumed to be rare).
370 * - Suppose one invokes while_locked() whenever modifying the central (canonical) state (perhaps infrequently).
371 * - And we also guarantee it is already in effect inside #Thread_local_state ctor.
372 * - Hence, as is natural, we do the seeding/pulling of the central state inside that ctor.
373 * - In that case while_locked() being active in the call stack (<=> its implied mutex being locked) guarantees
374 * the synchronization of the following state:
375 * - which `Thread_local_state`s exist (in the sense that that they might be returned via
376 * this_thread_state() in the future) in the process;
377 * - the cached copies of the canonical state in all existent (as defined in previous bullet)
378 * `Thread_local_state`s;
379 * - the canonical state (equal to every cached copy!).
380 *
381 * This simplifies thinking about the situation immensely, as to the extent that the central state is distributed
382 * to threads via thread-local `Thread_local_state` objects, the whole thing is monolithic: the state is synchronized
383 * to all relevant threads at all times. That said the following is an important corollary, at least for this
384 * use-case:
385 *
386 * - Assumption: To be useful, the central-state-copy must be accessed by users in relevant threads, probably
387 * via an accessor; so something like: `const auto cool_state_copy = X.this_thread_state()->cool_state()`.
388 * - There are of course variations on this; it could be a method of `Thread_local_state` that uses
389 * the value of the `private` central-state-copy for some computation. We'll use the accessor setup for
390 * exposition purposes.
391 * - Fact: The central-state-copy inside `*X.this_thread_state()` for the current thread can change at any time.
392 * - Therefore: cool_state() accessor, internally, *must* lock/unlock *some* mutex in order to guarantee
393 * synchronization.
394 * - It would be safe for cool_state() to "simply" use while_locked(). However, in any perf-sensitive scenario
395 * (which is essentially guaranteed to be the case: otherwise why setup thread-cached access to the cool-state
396 * in the first place?) this is utterly unacceptable. Now any threads using `->cool_state()` on their
397 * thread-local `Thread_local_state`s will contend for the same central mutex; it defeats the whole purpose.
398 * - Hence the corollary:
399 * - Probably you want to introduce your own additional mutex as a data member of #Thread_local_state. Call
400 * it `m_cool_state_mutex`, say.
401 * - In `->cool_state()` impl, lock it, get the copy of the central-state-copy cool-state, unlock it, return
402 * the copy.
403 * - In the *push* code invoked when the *canonical* central-state is updated -- as noted, this occurs
404 * while_locked() already -- similarly, when pushing to per-thread `Thread_local_state* x`, lock
405 * `x->m_cool_state_mutex`, update the central-state-copy of `*x`, unlock.
406 * - Since there are up to 2 mutexes involved (while_locked() central mutex, `x->m_cool_state_mutex` "little"
407 * mutex), there is some danger of deadlock; but if you are careful then it will be fine:
408 * - Fast-path is in `x->cool_state()`: Only lock `x->m_cool_state_mutex`.
409 * - Slow-path 1 is in the central-state-updating (push) code: `while_locked(F)`; inside `F(state_per_thread)`
410 * lock `x->m_cool_state_mutex` for each `x` in `state_per_thread`.
411 * - Slow-path 2 is in the `*x` ctor: central-state-init (pull) code: `while_locked()` is automatically
412 * in effect inside this_thread_state(); no need to lock `x->m_cool_state_mutex`, since no-place has
413 * access to `*x` until its ctor finishes and hence this_thread_state() returns.
414 * - The bad news: Your impl is no longer lock-free even in the fast-path: `X.this_thread_state()->cool_state()`
415 * does lock and unlock a mutex.
416 * - The good news: This mutex is ~never contended: At most 2 threads can even theoretically vie for it
417 * at a time; and except when *canonical* state must be updated (typically rare), it is only 1 thread.
418 * A regular mutex being locked/unlocked, sans contention, is quite cheap. This should more than defeat
419 * the preceding "bad news" bullet.
420 *
421 * @internal
422 *
423 * @todo Add a Flow unit-test or functional-test for the pattern "thread-caching of central canonical state," as
424 * described in the doc header for Thread_local_state_registry::this_thread_state().
425 *
426 * @endinternal
427 *
428 * @return See above. Never null.
429 */
431
432 /**
433 * Returns pointer to this thread's thread-local object, if it has been created via an earlier this_thread_state()
434 * call; or null if that has not yet occurred.
435 *
436 * @return See above.
437 */
439
440 /**
441 * Locks the non-recursive registry mutex, such that the set of extant per-thread `Thread_local_state`s cannot
442 * change (new threads-with-state cannot appear/`Thread_local_state`s cannot be cted; existing threads-with-state
443 * cannot exit/`Thread_local_state`s cannot be destroyed); executes given task, while giving it the set of
444 * currently extant `Thread_local_state`s; and unlocks said mutex.
445 *
446 * Behavior is undefined (actually: deadlock) if `task()` calls `this->while_locked()` (the mutex is non-recursive).
447 *
448 * ### Interaction with #Thread_local_state ctor ###
449 * See this_thread_state() doc header. To briefly restate, though: #Thread_local_state ctor, when invoked by
450 * this_thread_state() on first call in a given thread, is invoked inside a while_locked(). Therefore do not
451 * call while_locked() from such a ctor, as it will deadlock. From a more positive perspective, informally speaking:
452 * you may rely on while_locked() being active at all points inside a #Thread_local_state ctor.
453 *
454 * The semantics/policies w/r/t `state_per_thread` arg to your `task()`
455 * --------------------------------------------------------------------
456 * This arg is a reference to immutable container holding info for each thread in which this_thread_state() has been
457 * called: the keys are resulting `Thread_local_state*` pointers; the values are potentially interesting thread
458 * info such as thread ID. The container is immutable, as the information it represents --
459 * extant `Thread_local_state`s that exist due to this_thread_state() having been invoked at least once
460 * in their respective threads -- is immutable over the course of `task()`.
461 *
462 * ### What you may do ###
463 * You may access the data structure, including the #Thread_local_state pointees, in read-only mode.
464 *
465 * You may write to each individual #Thread_local_state pointee. Moreover you are guaranteed (see
466 * "Thread safety" below) that no while_locked() user is doing the same simultaneously (by while_locked()
467 * contract).
468 *
469 * If you *do* write to a particular pointee, remember these points:
470 * - Probably (unless you intentionally avoid it) you're writing to it *not* from the thread to which it
471 * belongs (in the sense that this_thread_state() would be called to obtain the same pointer).
472 * - Therefore you must synchronize any such concurrent read/write accesses from this thread and the owner
473 * thread (your own code therein presumably). You can use a mutex, or the datum could be `atomic<>`; etc.
474 * - Generally speaking, one uses thread-local stuff to avoid locking, so think hard before you do this.
475 * That said, locking is only expensive assuming lock contention; and if `state_per_thread` work
476 * from a not-owner thread is rare, this might not matter perf-wise. It *does* matter complexity-wise
477 * though (typically), so informally we'd recommend avoiding it.
478 * - Things like `atomic<bool>` flags are pretty decent in these situations. E.g., one can put into
479 * #Thread_local_state an `atomic<bool> m_do_flush{false}`; set it to `true` (with most-relaxed atomic mode)
480 * via while_locked() blocks when wanting a thread to perform an (e.g.) "flush" action;
481 * and in the owner-thread do checks like:
482 * `if (this_thread_state()->m_do_flush.compare_exchange_strong(true, false, relaxed)) { flush_stuff(); }`.
483 * It is speedy and easy.
484 * - You could also surround any access, from the proper owner thread, to that `Thread_local_state` pointee
485 * with while_locked(). Again, usually one uses thread-local stuff to avoid such central-locking actions;
486 * but it is conceivable to use it judiciously.
487 *
488 * ### Thread safety ###
489 * Informally: Most attempts to use the values in this container outside of `task()` are ill-advised.
490 *
491 * Reasoning: It might seem like it would have been safe to "just" make a copy of this container (while locking
492 * its contents briefly via `while_locked()`) and use that afterwards. In and of itself that's true, and as
493 * long as one never dereferences any `Thread_local_state` pointees, it is safe. (E.g., one could look at the
494 * thread IDs/nicknames in the thus-stored Metadata objects and log them. Not bad.) However dereferencing
495 * such a #Thread_local_state pointee is not safe outside while_locked(): at any moment its rightful-owning
496 * thread might exit and therefore `delete` it.
497 *
498 * @tparam Task
499 * Function object matching signature `void F(const State_per_thread_map&)`.
500 * @param task
501 * This will be invoked as follows: `task(state_per_thread)`. See above.
502 */
503 template<typename Task>
504 void while_locked(const Task& task) const;
505
506 /**
507 * Returns nickname, a brief string suitable for logging. This is included in the output by the `ostream<<`
508 * operator as well. This always returns the same value.
509 *
510 * @return See above.
511 */
512 const std::string& nickname() const;
513
514 /**
515 * Performs thread-safe `Log_context::set_logger(logger_ptr)`; and -- if #S_TL_STATE_HAS_MT_LOG_CONTEXT is `true` --
516 * propagates it to each extant #Thread_local_state via `state->set_logger(logger_ptr)`.
517 *
518 * @see also #m_create_state_func doc header w/r/t the effect of #S_TL_STATE_HAS_MT_LOG_CONTEXT on that by
519 * default.
520 *
521 * ### Thread safety ###
522 * It is safe to call this concurrently with (any thread-first invocation of) this_thread_state() on `*this`.
523 *
524 * @param logger_ptr
525 * Logger to use for logging subsequently. Reminder: can be null.
526 */
527 void set_logger(log::Logger* logger_ptr);
528
529private:
530 // Types.
531
532 /// Short-hand for mutex lock.
534
535 /// Short-hand for mutex type.
536 using Mutex = Lock::mutex_type;
537
538 /**
539 * The entirety of the cross-thread registry state, in a `struct` so as to be able to wrap it in a `shared_ptr`.
540 * See doc header for Registry_ctl::m_state_per_thread for key info.
541 */
543 {
544 // Data.
545
546 /// Protects the Registry_ctl (or `m_state_per_thread`; same difference) and log::Log_context::get_logger().
547 mutable Mutex m_mutex;
548
549 /**
550 * Registry containing each #Thread_local_state, one per distinct thread to have created one via
551 * this_thread_state() and not yet exited (rather, not yet executed the on-thread-exit cleanup
552 * of its #Thread_local_state). In addition the mapped values are informational Metadata.
553 *
554 * ### Creation and cleanup of each `Thread_local_state` (using this member) ###
555 * So, in a given thread T:
556 * - The first (user-invoked) this_thread_state() call shall: lock #m_mutex, insert into #m_state_per_thread,
557 * unlock.
558 * - If `*this` is around when T is exiting, the on-thread-exit cleanup function shall:
559 * obtain `shared_ptr<Registry_ctl>` (via `weak_ptr` observer); then lock, delete from #m_state_per_thread,
560 * unlock; then `delete` the #Thread_local_state itself (outside the locked section -- a formal guarantee;
561 * see "lifecycle" section of class doc header).
562 * - If `*this` is not around when T is exiting (which the user, if desired -- which is often the case --
563 * can prevent via `static` storage or by joining the relevant threads pre-dtor):
564 * - (Please see "Stability note" in Thread_local_state_registry doc header. We shall here assume the
565 * stability problem does not manifest -- which is doable even as of this writing but also hopefully
566 * guaranteed if/when we eliminate its source in `boost::thread_specific_ptr`.)
567 * - The cleanup function will still run (even though the `Thread_local_ptr`/`thread_specific_ptr`
568 * controlling that is gone), at T exit -- which is too late for us. Therefore:
569 * - To free resources in timely fashion, the dtor shall (similarly to cleanup function):
570 * lock, delete from #m_state_per_thread (`.clear()` them all), unlock; then `delete` each
571 * #Thread_local_state itself (again outside the locked section).
572 * - Once cleanup function does run, it will note `shared_ptr<Registry_ctl>`
573 * has been destroyed already (as seen via `weak_ptr` observer); so the `*this` dtor has run already,
574 * so cleanup function will do (almost) nothing and be right to do so.
575 * - As a slight variation on the latter sequence: If `*this` is around when T is exiting, but `*this`
576 * is being destroyed, and `shared_ptr<Registry_ctl>` has been destroyed already... so it'll
577 * similarly do (almost) nothing.
578 */
580 }; // struct Registry_ctl
581
582 /**
583 * The actual user #Thread_local_state stored per thread as lazily-created in this_thread_state(); plus
584 * a small bit of internal book-keeping. What book-keeping, you ask? Why not just a #Thread_local_state, you ask?
585 * Answer:
586 *
587 * ### Rationale w/r/t the `weak_ptr` ###
588 * The essential problem is that in cleanup() (which is called by thread X that earlier issued
589 * `Thread_local_state* x` via this_thread_state() regardless of whether `*this` still exists, and it
590 * might even be concurrently being deleted -- the dtor executing) we do not know whether `*this` dtor
591 * has not yet run (the least chaotic scenario), has run and therefore let Registry_ctl and `x` stay alive
592 * for now (Thread_local_ptr/`thread_specific_ptr` by definition does not cleanup in dtor for other threads).
593 * If everything is still around (least-chaotic), we would lock `m_ctl->m_mutex` and do the cleanup --
594 * deleting `x` and removing `*this` from the registry set/map -- but `*m_ctl` itself might be gone or even
595 * be in the process of being gone concurrently. This is perfect
596 * for `weak_ptr`: we can "just" capture a `weak_ptr` of `shared_ptr` #m_ctl and either grab a co-shared-pointer
597 * of `m_ctl` via `weak_ptr::lock()`; or fail to do so which simply means the dtor will do the cleanup anyway or,
598 * more likely, has already done it.
599 *
600 * Perfect! Only one small problem: Thread_local_ptr/`thread_specific_ptr` does take a cleanup
601 * function... but not a cleanup *function object*. It takes a straight-up func-pointer. Therefore
602 * we cannot "just capture" anything. This might seem like some bizarre
603 * anachronism, where boost.thread guys made an old-school API and never updated it.
604 * This is not the case though. A function pointer is a pointer to code -- which will always exist. A functor
605 * stores captures. So now they have to decide where/how to store that. To store it as regular non-thread-local
606 * data would mean needing a mutex, and in any case it breaks their guiding principle of holding only thread-local
607 * data -- either natively via pthreads/whatever of via `thread_local`. Storing it as thread-local means it's
608 * just more thread-local state that either itself has to be cleaned up -- which means user could just place it
609 * inside the stored type in the first place -- or something that will exist/leak beyond the `thread_specific_ptr`
610 * itself assigned to that thread.
611 *
612 * ### Rationale w/r/t the `weak_ptr` being in the `thread_specific_ptr` itself ###
613 * To summarize then: The member #m_ctl_observer is, simply, the (per-thread) `weak_ptr` to the registry's #m_ctl,
614 * so that `cleanup(X)` can obtain Registry_ctl::m_state_per_thread and delete the `Thread_local_state* X` from
615 * that map (see Registry_ctl::m_state_per_thread doc header). Simple, right? Well....
616 *
617 * If cleanup() runs and finishes before dtor starts, then things are simple enough! Grab `m_ctl` from
618 * `m_ctl_observer`. Delete the Tl_context from Registry_ctl::m_state_per_thread. Delete the `Thread_local_state`
619 * and the Tl_context (passed to cleanup() by `thread_specific_ptr`).
620 *
621 * If dtor runs before a given thread exits, then again: simple enough. Dtor can just do (for each thread's stuff)
622 * what cleanup() would have done; hence for the thread in question it would delete the `Thread_local_state` and
623 * `Tl_context` and delete the entry from Registry_ctl::m_state_per_thread. cleanup() will still run later at
624 * thread exit. (Technically this is unspecified behavior for `thread_specific_ptr`/Thread_local_ptr
625 * but -- if user avoids, or we eliminate the source of, the "Stability note" problem from our top doc header --
626 * in practice that is what happens.) If dtor runs concurrently to thread exit then it's basically the same
627 * flow.
628 *
629 * The problems begin there: Dtor gets to the `m_mutex` first and deletes all the `Tl_context`s as well as
630 * clearing the map; then cleanup() runs later/concurrently... and it needs the `weak_ptr m_ctl_observer` so it
631 * can even try to cooperate with the past-or-concurrent dtor, via `m_ctl_observer.lock()` to begin-with...
632 * except the `Tl_context` was earlier or just-then deleted: crash/etc.
633 *
634 * It's a chicken/egg problem: *the* chicken/egg problem. The `weak_ptr` cannot itself be part of the watched/deleted
635 * state, as it is used to synchronize access to it between dtor and cleanup(), if they run concurrently.
636 * So what do we do? Well... won't lie to you... we leak the `weak_ptr` and `Tl_context` that stores it
637 * (roughly 24-32ish bytes in x86-64), in the case where dtor runs first. This state of affairs continues until
638 * cleanup() executes. Once it does execute it deletes the `Tl_context`. (If cleanup() runs first, meaning
639 * the `*this` outlives a thread, such as if `*this` is being stored `static`ally or globally, then no temp leak.)
640 * It is a tiny and temporary leak, per thread (that outlives a `Thread_local_state_registry` object), per
641 * `Thread_local_state_registry` object.
642 *
643 * Any way to avoid it? Possibly. It is probably not worth it to try, and we have tried. Just to head this
644 * off at the pass: trying to use `thread_local` (while keeping Thread_local_ptr/`thread_specific_ptr`)
645 * will not work out. Cleanup occurs, in Linux at least, during `pthread_` destructor phase which is
646 * after C++ `thread_local` deinit phase, so cleanup function cannot access any helper `thread_local`
647 * (like a map from `Thread_local_state*` to `weak_ptr<Registry_ctl>`, or whatever such scheme).
648 *
649 * It is possibly (probably?) doable to abandon `thread_specific_ptr` and (essentially) reimplement that part
650 * by using `thread_local` directly... but really that is similar to rewriting `thread_specific_ptr` that way.
651 * See notes in impl section of Thread_local_ptr doc header; it discusses why that's doable but not doable
652 * in a way that's sufficiently usable. (In short the cleanup will run too early for some situations, namely
653 * *during* `thread_local` deinit phase, before some of the deinit but after some other. It's a mess; we do not
654 * recommend it!)
655 *
656 * Just, the Tl_context wrapper-with-small-possible-leak-per-thread design is fairly pragmatic without having to
657 * engage in all kinds of masochism. Still it's a bit yucky in an aesthetic sense.
658 */
660 {
661 // Data.
662
663 /// Observer of (existent or non-existent) daddy's #m_ctl. See Tl_context doc header for explanation.
664 boost::weak_ptr<Registry_ctl> m_ctl_observer;
665 /**
666 * The main user state. Never null; but `*m_state` has been freed (`delete`d) if and only if the pointer
667 * `m_state` is no longer in `m_ctl_observer.lock()->m_state_per_thread`, or if `m_ctl_observer.lock() == nullptr`.
668 */
670 };
671
672 /**
673 * Simply wraps a `Thread_local_ptr<Tl_context>`/`boost::thread_specific_ptr<Tl_context>`,
674 * adding absolutely no data or algorithms, purely to work-around a combination of (some?)
675 * clang versions and (some?) GNU STL impls giving a bogus compile error, when one tries
676 * `optional<Thread_local_state_registry>`. The error is
677 * "the parameter for this explicitly-defaulted copy constructor is const, but a member
678 * or base requires it to be non-const" and references impl details
679 * of STL's `optional`. The solution is to wrap the thing in a thing
680 * that is itself already noncopyable in the proper way, unlike `thread_specific_ptr` (which, at least as of
681 * Boost-1.87, still has a copy-forbidding ctor/assigner that takes non-`const` ref).
682 *
683 * Update: The above paragraph and Tsp_wrapper were written based on, in fact, the Boost `t_s_p`.
684 * However since then we've changed to Thread_local_ptr which is semantically identical to
685 * Boost `t_s_p` (and internally wraps it) but provides a significantly faster `get()` (used in our
686 * fast-path: this_thread_state() et al). It also lacks the above problem; so Tsp_wrapper (while harmless,
687 * added lines-of-code aside) is not necessary. We nevertheless decided, for now, to keep it -- just
688 * in case for some tactical reasons (maybe debug/test) we want to switch back to Boost's impl.
689 *
690 * @todo Once the use of Thread_local_ptr (instead of/augmenting `boost::thread_specific_ptr`) inside
691 * Thread_local_state_registry impl has proven out to be entirely permanent, with no desire to switch-back
692 * even for test/debug purposes, eliminate the `Tsp_wrapper` compiler-error-fighting contraption.
693 */
695 {
696 // Data.
697
698 /**
699 * What we wrap and forward-to-and-fro: an object with functional semantics identical to
700 * those of `boost::thread_specific_ptr<Tl_context>`. The latter can, itself, be used here; but
701 * we have now replaced it with Thread_local_ptr<Tl_context> which has much better `.get()` perf.
702 *
703 * @see Thread_local_ptr for details on that.
704 */
706 // boost::thread_specific_ptr<Tl_context> m_tsp; // Alternative/original.
707
708 // Constructors/destructor.
709
710 /**
711 * Constructs payload.
712 * @param ctor_args
713 * Args to #m_tsp ctor.
714 */
715 template<typename... Ctor_args>
716 Tsp_wrapper(Ctor_args&&... ctor_args);
717
718 /**
719 * Forbid copy.
720 * @param src
721 * Yeah.
722 */
723 Tsp_wrapper(const Tsp_wrapper& src) = delete;
724
725 // Methods.
726
727 /**
728 * Forbid copy.
729 * @param src
730 * Yeah.
731 * @return Right.
732 */
733 Tsp_wrapper& operator=(const Tsp_wrapper& src) = delete;
734 }; // struct Tsp_wrapper
735
736 // Methods.
737
738 /**
739 * Called by Thread_local_ptr/`thread_specific_ptr` for a given thread's `m_this_thread_state_or_null.m_tsp.get()`;
740 * can occur before `*this` dtor (which would have destroyed #m_this_thread_state_or_null; this is least chaotic);
741 * or after it, or during it. With proper synchronization:
742 * does `delete ctx->m_state` and `delete ctx` and removes the former from Registry_ctl::m_state_per_thread.
743 * It is possible that the `*this` dtor has run or runs concurrently (if a relevant thread is exiting right around
744 * the time the user chooses to invoke dtor) and manages to `delete ctx->m_state` first; however it will *not*
745 * delete the surrounding `ctx`; so that cleanup() can be sure it can access `*ctx` -- but not necessarily
746 * `*ctx->m_state`.
747 *
748 * @param ctx
749 * Value stored in #m_this_thread_state_or_null; where `->m_state` was returned by at least one
750 * this_thread_state() in this thread. Not null.
751 */
752 static void cleanup(Tl_context* ctx);
753
754 // Data.
755
756 /// See nickname().
757 const std::string m_nickname;
758
759 /**
760 * In a given thread T, `m_this_thread_state_or_null.get()` is null if this_thread_state() has not yet been
761 * called by `*this` user; else (until either `*this` dtor runs, or at-thread-exit cleanup function runs)
762 * pointer to T's thread-local Tl_context object which consists mainly of a pointer to T's
763 * thread-local #Thread_local_state object; plus a bit of book-keeping. (See Tl_context for details on the
764 * latter.)
765 *
766 * ### Cleanup: key discussion ###
767 * People tend to declare Thread_local_ptr/`thread_specific_ptr x` either `static` or global, because in that case:
768 * - Either `delete x.get()` (default) or `cleanup_func(x.get())` (if one defines custom cleanup func)
769 * runs for each thread...
770 * - ...and *after* that during static/global deinit `x` own dtor runs. (It does do `x.reset()` in *that*
771 * thread but only that thread; so at "best" one thread's cleanup occurs during
772 * Thread_local_ptr/`thread_specific_ptr` dtor.)
773 *
774 * We however declare it as a non-`static` data member. That's different. When #m_this_thread_state_or_null
775 * is destroyed (during `*this` destruction), if a given thread T (that is not the thread in which dtor is
776 * executing) has called this_thread_state() -- thus has `m_this_thread_state_or_null.m_tsp.get() != nullptr` -- and
777 * is currently running, then its #Thread_local_state shall leak until thread exit.
778 *
779 * Therefore, in our case, we can make it `static`: but then any cleanup is deferred until thread exit;
780 * and while it is maybe not the end of the world, we strive to be better; a major part of the point of the registry
781 * is to do timely cleanup (but also see "Stability note"). So then instead of that we:
782 * - keep a non-thread-local registry Registry_ctl::m_state_per_thread of each thread's thread-local
783 * #Thread_local_state;
784 * - in dtor iterate through that registry and delete 'em.
785 *
786 * Let `p` stand for `m_this_thread_state_or_null.m_tsp.get()->m_state`: if `p != nullptr`, that alone does not
787 * guarantee that `*p` is valid. It is valid if and only if #m_ctl is a live `shared_ptr` (as determined
788 * via `weak_ptr`), and `p` is in Registry_ctl::m_state_per_thread. If #m_ctl is not live
789 * (`weak_ptr::lock()` is null), then `*this` is destroyed or very soon to be destroyed, and its dtor thus
790 * has `delete`d `p`. If #m_ctl is live, but `p` is not in `m_ctl->m_state_per_thread`, then
791 * the same is true: just we happened to have caught the short time period after the dtor deleting all states
792 * and clearing `m_state_per_thread`, but while the surrounding Registry_ctl still exists.
793 *
794 * So is it safe to access `*p`, when we do access it? Answer: We access it in exactly 2 places:
795 * - When doing `delete p` (in dtor, or in on-thread-exit cleanup function for the relevant thread).
796 * This is safe, because `p` is live if and only if it is not in Registry_ctl::m_state_per_thread
797 * (all this being mutex-synchronized).
798 * - By user code, probably following this_thread_state() to obtain `p`. This is safe, because:
799 * It is illegal for them to access `*this`-owned state after destroying `*this`.
800 *
801 * As for the stuff in `m_this_thread_state_or_null.m_tsp.get()` other than `p` -- the Tl_context surrounding
802 * it -- again: see Tl_context doc header.
803 */
805
806 /// The non-thread-local state. See Registry_ctl docs. `shared_ptr` is used only for `weak_ptr`.
807 boost::shared_ptr<Registry_ctl> m_ctl;
808}; // class Thread_local_state_registry
809
810/**
811 * A single boolean flag, armed via arm_next_poll() (typically from another thread) and both checked and disarmed --
812 * cheaply enough for the hottest of paths -- via poll_armed(). It is the signaling building block of the
813 * `Polled_shared_state` pattern (see Polled_shared_state doc header), wherein one embeds a Poll_flag in each
814 * per-thread state of a Thread_local_state_registry; but it is usable stand-alone, wherever a thread must cheaply,
815 * opportunistically notice a rare request from another thread.
816 */
818 private boost::noncopyable
819{
820public:
821 // Constructors/destructor.
822
823 /// Creates flag in disarmed state: poll_armed() shall return `false` until the first arm_next_poll().
824 Poll_flag();
825
826 // Methods.
827
828 /**
829 * From any thread: causes the next poll_armed() call to return `true` (once). If already armed, it stays
830 * armed (this is not a counter).
831 *
832 * If arming signals a change to some shared data (e.g., `Polled_shared_state::while_locked()`-guarded state),
833 * finish writing that data -- and unlock -- *before* calling this; the memory-ordering internals then
834 * guarantee the poll_armed() caller sees the writes.
835 */
836 void arm_next_poll();
837
838 /**
839 * If armed: disarms and returns `true`; else returns `false`. Cheap enough (one atomic op with near-minimal
840 * ordering constraints; no locking) for the hottest of paths, particularly when it returns `false`.
841 *
842 * @return See above.
843 */
844 bool poll_armed();
845
846private:
847 // Data.
848
849 /// The flag; `true` = armed.
850 std::atomic<bool> m_armed;
851}; // class Poll_flag
852
853/**
854 * Optional-use companion to Thread_local_state_registry that (together with Poll_flag) enables the
855 * `Polled_shared_state` pattern wherein from some arbitrary thread user causes the extant thread-locally-activated
856 * threads opportunistically collaborate on/using locked shared state, with the no-op fast-path being gated by a
857 * high-performance Poll_flag being disarmed.
858 *
859 * This `Polled_shared_state` pattern (I, ygoldfel, made that up... don't know if it's a known thing) is
860 * maybe best explained by example. Suppose we're using Thread_local_state_registry with
861 * `Thread_local_state` type being `T`. Suppose that sometimes some event occurs, in an arbitrary thread (for
862 * simplicity let's say that is not in any thread activated by the `Thread_local_state_registry<T>`) that
863 * requires each state to execute `thread_locally_launch_rocket()`. Lastly, suppose that upon launching the
864 * *last* rocket required, we must report success via `report_success()` from whichever thread did it.
865 *
866 * However there are 2-ish problems at least:
867 * - We're not in any of those threads; we need to inform them somehow they each need to
868 * `thread_locally_launch_rocket()`. There's no way to signal them to do it immediately necessarily;
869 * but we can do it opportunistically to any thread that has already called `this_thread_state()` (been activated).
870 * - Plus there's the non-trivial accounting regarding "last one to launch does a special finishing step" that
871 * requires keeping track of work-load in some shared state.
872 * - Not to mention the fact that the "let's launch missiles!" event might occur before the planned launches
873 * have had a chance to proceed; since then more threads may have become activated and would need to be
874 * added to the list of "planned launches."
875 * - Typically we don't need to launch any rockets; and the *fast-path* is that we in fact don't.
876 * It is important that each activated thread can ask "do we need to launch-rocket?" and get the probable
877 * answer "no" extremely quickly: without locking any mutex, and even more importantly without any contention if
878 * we do. If the answer is "yes," which is assumed to be rare, *then* even lock-contended-locking is okay.
879 *
880 * To handle these challenges the pattern is as follows.
881 * - The #Shared_state template param here is perhaps `set<T*>`: the set of `T`s (each belonging to an
882 * activated thread that has called `Thread_local_state_registry<T>::this_thread_state()`) that should execute,
883 * and have not yet executed, `thread_locally_launch_rocket()`.
884 * - Wherever the `Thread_local_state_registry<T>` is declared/instantiated -- e.g., `static`ally --
885 * also declare `Polled_shared_state<set<T*>>`. Conventionally we recommend the `Polled_stared_state`-first,
886 * `Thread_local_state_registry`-second order; the latter depends on the former.
887 * - Give `T` a Poll_flag data member (say, `Poll_flag m_missile_launch_needed_flag`).
888 * - If the "let's launch missiles" event occurs, in its code do:
889 *
890 * ~~~
891 * registry.while_locked([&](const auto& state_per_thread) // Access across per-thread state is done while_locked().
892 * {
893 * if (state_per_thread.empty()) { return; } // No missiles to launch for sure; forget it.
894 *
895 * // Load the shared state (while_locked()):
896 * missiles_to_launch_polled_shared_state.while_locked([&](set<T*>* threads_that_shall_launch_missiles)
897 * {
898 * // *threads_that_shall_launch_missiles is protected against concurrent change.
899 * for (const auto& state_and_mdt : state_per_thread)
900 * {
901 * T* const thread_lcl_state_ptr = state_and_mdt.first;
902 * threads_that_shall_launch_missiles->insert(thread_lcl_state_ptr);
903 * }
904 * });
905 *
906 * // *AFTER!!!* loading the shared state, arm the poll-flag:
907 * for (const auto& state_and_mdt : state_per_thread)
908 * {
909 * T* const thread_lcl_state_ptr = state_and_mdt.first;
910 * thread_lcl_state_ptr->m_missile_launch_needed_flag.arm_next_poll();
911 * // (We arm every per-thread T; but it is possible and fine to do it only for some.)
912 * // Also note it might already be armed; this would keep it armed; no problem. Before the for()
913 * // the set<> might already have entries (launches planned, now we're adding possibly more to it).
914 * }
915 * });
916 * ~~~
917 *
918 * So that's the setup/arming; and now to consume it:
919 * - In each relevant thread, such that `this_thread_state()` has been called in it (and therefore a `T` exists),
920 * whenever the opportunity arises, check the poll-flag, and in the rare case where it is armed,
921 * do `thread_locally_launch_rocket()`:
922 *
923 * ~~~
924 * void opportunistically_launch_when_triggered() // Assumes: registry.this_thread_state_or_null()) is non-null.
925 * {
926 * T* const this_thread_state = registry.this_thread_state();
927 * if (!this_thread_state->m_missile_launch_needed_flag.poll_armed())
928 * { // Fast-path! Nothing to do re. missile-launching.
929 * return;
930 * }
931 * // else: Slow-path. Examine the shared-state; do what's needed. Note: poll_armed() would now return false.
932 * missiles_to_launch_polled_shared_state.while_locked([&](set<T*>* threads_that_shall_launch_missiles)
933 * {
934 * if (threads_that_shall_launch_missiles->erase(this_thread_state) == 0)
935 * {
936 * // Already-launched? Bug? It depends on your algorithm. But the least brittle thing to do is likely:
937 * return; // Nothing to do (for us) after all.
938 * }
939 * // else: Okay: we need to launch, and we will, and we've marked our progress about it.
940 * thread_locally_launch_rocket();
941 *
942 * if (threads_that_shall_launch_missiles->empty())
943 * {
944 * report_success(); // We launched the last required missile... report success.
945 * }
946 * });
947 * }
948 * ~~~
949 *
950 * Hopefully that explains it. It is a little rigid and a little flexible; the nature of #Shared_state is
951 * arbitrary, and the above is probably the simplest form of it (but typically we suspect it will usually involve
952 * some container(s) tracking some subset of extant `T*`s).
953 *
954 * If there is no shared state to maintain -- the armed flags alone carry all the needed info -- then you need
955 * no Polled_shared_state at all: just embed the `Poll_flag`s. (In the above example that would have been
956 * enough -- if not for the requirement to `report_success()`, when the last missile is launched.)
957 *
958 * ### Lifetime/safety ###
959 * Each poll-flag, being a `T` member, lives exactly as long as its `T`. Hence:
960 * - Arming within `registry.while_locked()` (as shown above) is safe -- even if a targeted thread is concurrently
961 * exiting: every `T` in `state_per_thread`, and therefore its flag, is alive at least until `task()` returns.
962 * (A `T`'s thread-exit cleanup contends for the same lock and un-registers the `T` before destroying it.)
963 * - Polling is safe wherever a `T` is available -- including from `~T()` itself, if it comes to that.
964 *
965 * ### Performance ###
966 * The arming event occurs rarely and thus is not part of any fast-path; the fast-path cost is thread-local logic
967 * detecting `poll_armed() == false` first-thing and doing nothing further -- a single, cheap atomic op
968 * (see Poll_flag doc header).
969 *
970 * ### Design rationale: addendum ###
971 * The two actual API-classes involved in the `Polled_shared_state` pattern -- Polled_shared_state here and
972 * Poll_flag nearby -- are each quite simple, so simple that one could say they are unnecessary. Nevertheless
973 * in our experience it was worthwhile to do this; in particular the sister project Flow-IPC as of this writing
974 * uses the pattern (including both classes) in 3 different places.
975 * - Polled_shared_state itself is really just a simple mutex and a #Shared_state. We provide a bit of
976 * syntactic sugar (while_locked()) and the encapsulation; that's all.
977 * - However: our doc header is a good place to document the pattern. We feel this alone has real value.
978 * - Poll_flag does encapsulate certain `atomic`-logic that, while not exotic or obscure, is still easy
979 * to get wrong in little ways (that will not easily reveal themselves empirically) in terms of correctness
980 * and perf.
981 *
982 * @tparam Shared_state_t
983 * A single object of this type shall be constructed and can be accessed, whether for reading or writing,
984 * using while_locked(). It must be constructible via the ctor signature you choose
985 * to use when constructing `*this` Polled_shared_state ctor (template). The ctor args shall be forwarded
986 * to the `Shared_state_t` ctor.
987 */
988template<typename Shared_state_t>
990 private boost::noncopyable
991{
992 static_assert(!std::is_empty_v<Shared_state_t>,
993 "If there is no shared state to protect -- the poll-flags alone suffice -- then you need "
994 "no Polled_shared_state at all: simply embed Poll_flag(s) in your per-thread state.");
995
996public:
997 // Types.
998
999 /// Short-hand for template parameter type.
1000 using Shared_state = Shared_state_t;
1001
1002 // Constructors/destructor.
1003
1004 /**
1005 * Forwards to the stored object's #Shared_state ctor.
1006 *
1007 * Next: use while_locked() to check/modify #Shared_state contents safely; then arm the relevant per-thread
1008 * `Poll_flag`s. See class doc header for the full pattern.
1009 *
1010 * @tparam Ctor_args
1011 * See above.
1012 * @param shared_state_ctor_args
1013 * See above.
1014 */
1015 template<typename... Ctor_args>
1016 Polled_shared_state(Ctor_args&&... shared_state_ctor_args);
1017
1018 // Methods.
1019
1020 /**
1021 * Locks the non-recursive shared-state mutex, such that no access or modification of the contents
1022 * of the #Shared_state shall concurrently occur; executes given task; and unlocks said mutex.
1023 *
1024 * Behavior is undefined (actually: deadlock) if task() calls `this->while_locked()` (the mutex is non-recursive).
1025 *
1026 * @tparam Task
1027 * Function object matching signature `void F(Shared_state*)`.
1028 * @param task
1029 * This will be invoked as follows: `task(shared_state)`. `shared_state` shall point to the object
1030 * stored in `*this` and constructed in our ctor.
1031 */
1032 template<typename Task>
1033 void while_locked(const Task& task);
1034
1035private:
1036 // Data.
1037
1038 /// Protects #m_shared_state.
1040
1041 /// The managed #Shared_state.
1043}; // class Polled_shared_state
1044
1045// Free functions: in *_fwd.hpp.
1046
1047// Thread_local_state_registry template implementations.
1048
1049template<typename Thread_local_state_t>
1051 (log::Logger* logger_ptr, String_view nickname_str,
1052 decltype(m_create_state_func)&& create_state_func) :
1053
1054 log::Log_context(logger_ptr, Flow_log_component::S_UTIL),
1055
1056 m_create_state_func(std::move(create_state_func)),
1057 m_nickname(nickname_str),
1058 m_this_thread_state_or_null(cleanup),
1059 m_ctl(boost::make_shared<Registry_ctl>())
1060{
1061 FLOW_LOG_INFO("Tl_registry[" << *this << "]: "
1062 "Registry created (watched type has ID [" << typeid(Thread_local_state).name() << "]).");
1063}
1064
1065template<typename Thread_local_state_t>
1068{
1069 const auto ctx = m_this_thread_state_or_null.m_tsp.get();
1070 return ctx ? ctx->m_state : nullptr;
1071}
1072
1073template<typename Thread_local_state_t>
1076{
1077 using log::Logger;
1078
1079 auto ctx = m_this_thread_state_or_null.m_tsp.get();
1080 if (!ctx)
1081 {
1082 // (Slow-path. It is OK to log and do other not-so-fast things.)
1083
1084 /* We shall be accessing (inserting into) m_state_per_thread which understandably requires while_locked().
1085 * So bracket the following with while_locked(). Notice, though, that we do this seemingly earlier than needed:
1086 * Inside, we (1) construct the new Thread_local_state; and only then (2) add it into m_state_per_thread.
1087 * The mutex-lock is only necessary for (2). So why lock it now? Answer: We promised to do so. Why did we?
1088 * Answer: See method doc header for rationale. */
1089
1090 while_locked([&](auto&&...) // Versus this_thread_state()/cleanup(). Among other things locks get_logger()-return.
1091 {
1092 // Time to lazy-init. As advertised:
1093 decltype(m_create_state_func) create_state_func;
1094 if (m_create_state_func.empty())
1095 {
1096 if constexpr(S_TL_STATE_HAS_MT_LOG_CONTEXT && std::is_constructible_v<Thread_local_state, Logger*>)
1097 {
1098 create_state_func = [&]() -> auto { return new Thread_local_state{get_logger()}; };
1099 }
1100 else if constexpr((!S_TL_STATE_HAS_MT_LOG_CONTEXT) && std::is_default_constructible_v<Thread_local_state>)
1101 {
1102 create_state_func = []() -> auto { return new Thread_local_state; };
1103 }
1104 else
1105 {
1106 FLOW_LOG_FATAL("Chose not to supply m_create_state_func at time of needing a new Thread_local_state. "
1107 "In this case you must either have <derived from Log_context_mt and supplied ctor "
1108 "form T_l_s{lgr} (where lgr is a Logger*)> or <*not* derived from Log_context_mt but "
1109 "made T_l_s default-ctible>. Breaks contract; aborting.");
1110 assert(false && "Chose not to supply m_create_state_func at time of needing a new Thread_local_state. "
1111 "In this case you must either have <derived from Log_context_mt and supplied ctor "
1112 "form T_l_s{lgr} (where lgr is a Logger*)> or <*not* derived from Log_context_mt but "
1113 "made T_l_s default-ctible>. Breaks contract.");
1114 std::abort();
1115 }
1116 /* Subtlety: The is_*_constructible_v checks may seem like mere niceties -- why not just let it not-compile
1117 * if they don't provide the expected ctor form given S_TL_STATE_HAS_MT_LOG_CONTEXT being true or false --
1118 * or even actively bad (why not just let it not-compile, so they know the problem at compile-time; or
1119 * why not static_assert() it?). Not so: Suppose they *did* provide m_create_state_func, always, but
1120 * lack the ctor form needed for the case where they hadn't. Then this code path would still try to get
1121 * compiled -- and fail to compile -- even though there's no intention of it even ever executing. That would
1122 * be annoying and unjust. The only downside of the solution is the null-m_create_state_func path can only
1123 * fail at run-time, not compile-time; on balance that is better than the unjust alternative. */
1124 } // if (m_create_state_func.empty())
1125 else // if (!m_create_state_func.empty())
1126 {
1127 create_state_func = m_create_state_func; // We specifically said we'd copy it.
1128 }
1129
1130 ctx = new Tl_context;
1131 ctx->m_ctl_observer = m_ctl;
1132 ctx->m_state = create_state_func();
1133
1134 m_this_thread_state_or_null.m_tsp.reset(ctx);
1135
1136 /* Now to set up the later cleanup, either at thread-exit, or from our ~dtor(), whichever happens first;
1137 * and also to provide access to us via enumeration via while_locked() + `state_per_thread`. */
1138
1139 typename decltype(Registry_ctl::m_state_per_thread)::value_type state_and_mdt{ctx->m_state, Metadata{}};
1140 auto& mdt = state_and_mdt.second;
1141 /* Save thread info (informational). (Note: Logger::set_thread_info() semantics are a bit surprising, out of
1142 * the log-writing context. It outputs nickname if available; else thread ID if not.) */
1143 log::Logger::set_thread_info(&mdt.m_thread_nickname,
1144 &mdt.m_thread_id);
1145 if (mdt.m_thread_id == Thread_id{})
1146 {
1147 mdt.m_thread_id = this_thread::get_id(); // Hence get it ourselves.
1148 }
1149 // else { nickname is blank. Nothing we can do about that though. }
1150
1151 /* flow::log does not output these thread-ID-like unique-tokens (which are never recycled but have
1152 * less-universally accepted value in the context of logging), so flow::log lacks
1153 * APIs for grabbing those, but for our users and/or our own logging it is nevertheless useful; so grab
1154 * it directly. */
1155 mdt.m_thread_token = this_thread_unique_token();
1156
1157 FLOW_LOG_INFO("Tl_registry[" << *this << "]: Adding thread-local-state @[" << ctx->m_state << "] "
1158 "for thread ID [" << mdt.m_thread_id << "]/nickname [" << mdt.m_thread_nickname << "]/"
1159 "unique-token [" << mdt.m_thread_token << "]; "
1160 "watched type has ID [" << typeid(Thread_local_state).name() << "]).");
1161#ifndef NDEBUG
1162 const auto result =
1163#endif
1164 m_ctl->m_state_per_thread.insert(state_and_mdt);
1165 assert(result.second && "How did `state` ptr value get `new`ed, if another thread has not cleaned up same yet?");
1166 }); // while_locked()
1167 } // if (!ctx)
1168 // else if (ctx) { Fast path: state already init-ed. Do not log or do anything unnecessary. }
1169
1170 return ctx->m_state;
1171} // Thread_local_state_registry::this_thread_state()
1172
1173template<typename Thread_local_state_t>
1175{
1176 using std::vector;
1177
1178 vector<Thread_local_state*> states_to_delete;
1179 while_locked([&](auto&&...) // Versus cleanup() (possibly even 2+ of them).
1180 {
1181 FLOW_LOG_INFO("Tl_registry[" << *this << "]: "
1182 "Registry shutting down (watched type has ID [" << typeid(Thread_local_state).name() << "]). "
1183 "Will now delete thread-local-state for each thread that has not exited before this point.");
1184 /* ^-- As we are in dtor, that log-call (which accesses get_logger()) is safe to place pre-while_locked().
1185 * Keeping it in here anyway, arguably for maintainability (conceivable it is moved out of dtor or something).
1186 * Perf impact negligible. */
1187
1188 for (const auto& state_and_mdt : m_ctl->m_state_per_thread)
1189 {
1190 const auto state = state_and_mdt.first;
1191 const auto& mdt = state_and_mdt.second;
1192 FLOW_LOG_INFO("Tl_registry[" << *this << "]: Deleting thread-local-state @[" << state << "] "
1193 "for thread ID [" << mdt.m_thread_id << "]/nickname [" << mdt.m_thread_nickname << "]/"
1194 "unique-token [" << mdt.m_thread_token << "].");
1195
1196 // Let's not `delete state` while locked, if only to match cleanup() avoiding it.
1197 states_to_delete.push_back(state);
1198 }
1199 m_ctl->m_state_per_thread.clear();
1200 }); // while_locked()
1201
1202 for (const auto state : states_to_delete)
1203 {
1204 delete state;
1205 /* Careful! We delete `state` (the Thread_local_state) but *not* the Tl_context (we didn't even store
1206 * it in the map) that is actually stored in the thread_specific_ptr m_this_thread_state_or_null.m_tsp.
1207 * See Tl_context doc header for explanation. In short by leaving it alive we leave cleanup() able to
1208 * run concurrently with ourselves -- unlikely but possible -- or well after ourselves. */
1209 }
1210
1211 /* Subtlety: When m_this_thread_state_or_null is auto-destroyed shortly, it will auto-execute
1212 * m_this_thread_state_or_null.m_tsp.reset() -- in *this* thread only. If in fact this_thread_state() has been
1213 * called in this thread, then it'll try to do cleanup(m_this_thread_state_or_null.m_tsp.get()); nothing good
1214 * can come of that really. We could try to prevent it by doing m_this_thread_state_or_null.m_tsp.reset()... but
1215 * same result. Instead we do the following which simply replaces the stored (now useless) Tl_context* with null, and
1216 * that's it. We already deleted its ->m_state, so that's perfect.
1217 *
1218 * Plus, we can `delete` the Tl_context (again: just this thread's); might as well not leak it, since we do not
1219 * have to. */
1220 delete m_this_thread_state_or_null.m_tsp.release();
1221
1222 // After the }, m_ctl is nullified, and lastly m_this_thread_state_or_null is destroyed (a no-op in our context).
1223} // Thread_local_state_registry::~Thread_local_state_registry()
1224
1225template<typename Thread_local_state_t>
1227{
1228 /* If the relevant *this has been destroyed, we are still called at thread exit (do see "Stability note" though).
1229 * It is also/similarly possible that our thread T is exiting, and just then user in another thread chose to
1230 * invoke *this dtor. Therefore we must carefully use locking and weak_ptr (as you'll see) to contend
1231 * with these possibilities; it might be worthwhile to read cleanup() and the dtor in parallel.
1232 *
1233 * By the way: Among other things, the relevant *this's Log_context_mt might be around at one point but not another;
1234 * and by contract same with the underlying Logger. So we cannot grab the Logger/log using that necessarily; we
1235 * will just have to be quiet; that's life. */
1236
1237 auto& weak_ptr_to_ctl = ctx->m_ctl_observer;
1238 const auto shared_ptr_to_ctl = weak_ptr_to_ctl.lock();
1239 if (!shared_ptr_to_ctl)
1240 {
1241 /* Relevant Thread_local_state_registry dtor was called in the past, or concurrently but early enough.
1242 * Either way it managed to grab the m_ctl and delete it (the m_ctl_observer target).
1243 * So -- we need not worry about cleanup after all; it has been taken care of...
1244 * ...except the *ctx wrapper itself. So clean that up (not actual ctx->m_state payload!) -- as
1245 * T_l_s_r dtor specifically never does -- and GTFO. */
1246 delete ctx;
1247 return;
1248 }
1249 // else
1250
1251 /* Either the relevant Thread_local_state_registry dtor has not at all run yet, or perhaps it has started to run --
1252 * but we were able to grab the m_ctl fast enough. So now either they'll grab m_ctl->m_mutex first, or we will. */
1253 bool do_delete;
1254 {
1255 Lock lock{shared_ptr_to_ctl->m_mutex}; // Versus this_thread_state()/dtor/other thread's/threads' cleanup()(s).
1256 do_delete = (shared_ptr_to_ctl->m_state_per_thread.erase(ctx->m_state) == 1);
1257 } // shared_ptr_to_ctl->m_mutex unlocks here.
1258
1259 /* We don't want to `delete ctx->m_state` inside the locked section; it might not be necessarily always criminal --
1260 * but in some exotic but real situations the Thread_local_state dtor might launch a new, presumably detached, thread
1261 * that would itself call this_thread_state() which would deadlock trying to lock the same mutex, if the
1262 * dtor call doesn't finish fast enough. */
1263 if (do_delete)
1264 {
1265 delete ctx->m_state; // We got the mutex first. Their ~Thread_local_state() dtor runs here. Least-chaotic outcome.
1266 }
1267 /* else { Guess the concurrently-running dtor got there first! It `delete`d ctx->m_state and
1268 * m_state_per_thread.clear()ed instead of us. } */
1269
1270 delete ctx; // Either way we can free the little Tl_context; dtor never does that (known/justified temp leak by dtor).
1271} // Thread_local_state_registry::cleanup() // Static.
1272
1273template<typename Thread_local_state_t>
1274template<typename Task>
1276{
1277 Lock lock{m_ctl->m_mutex};
1278 task(std::as_const(m_ctl->m_state_per_thread));
1279}
1280
1281template<typename Thread_local_state_t>
1283{
1284 return m_nickname;
1285}
1286
1287template<typename Thread_local_state_t>
1289{
1290 using log::Log_context;
1291
1292 while_locked([&](auto&&...)
1293 {
1294 // Versus our own slow-path logging elsewhere, this needs to be inside while_locked().
1295 Log_context::set_logger(logger_ptr);
1296
1297 if constexpr(S_TL_STATE_HAS_MT_LOG_CONTEXT)
1298 {
1299 for (const auto& state_and_mdt : m_ctl->m_state_per_thread)
1300 {
1301 const auto state = state_and_mdt.first;
1302
1303 state->set_logger(logger_ptr);
1304 }
1305 }
1306 }); // while_locked()
1307} // Thread_local_state_registry::set_logger()
1308
1309template<typename Thread_local_state_t>
1310template<typename... Ctor_args>
1312 m_tsp(std::forward<Ctor_args>(ctor_args)...)
1313{
1314 // Yeah.
1315}
1316
1317template<typename Thread_local_state_t>
1318std::ostream& operator<<(std::ostream& os, const Thread_local_state_registry<Thread_local_state_t>& val)
1319{
1320 return os << '[' << val.nickname() << "]@" << &val;
1321}
1322
1323// Poll_flag implementations.
1324
1326 m_armed(false)
1327{
1328 // Yep.
1329}
1330
1332{
1333 m_armed.store(true, std::memory_order_release);
1334
1335 /* Explanation of memory_order_release here + memory_order_acquire when consuming it in poll_armed():
1336 *
1337 * Our goal is to signal another thread to -- when it next has a chance (opportunistic piggy-backing) --
1338 * do stuff. Yet since it is opportunistic piggy-backing, the fast-path (where nothing needs to be done)
1339 * needs to be lightning-fast. So we "just" set that bool flag. However typically
1340 * we also need to tell it some more info and/or even have it update it, namely whatever
1341 * shared data (e.g., Polled_shared_state-held state) the user should have set-up before calling this
1342 * arm_next_poll(). So we do it in the order:
1343 * -1- user updates shared data under lock (e.g., Polled_shared_state::while_locked())
1344 * -2- set flag = true
1345 * and in the polling thread later
1346 * -A- check flag (poll_armed()), and if that returns true
1347 * -B- user reads/updates shared data under lock
1348 * The danger is that, to the other thread, our steps -1-2- will be reordered to -2-1-, and it will
1349 * see flag=true with the shared data not-ready/empty/whatever (disaster). However by using RELEASE for -2- and
1350 * ACQUIRE for -A-, together with the presence of mutex-locking around -1- and -B- (plus the if-relationship
1351 * between -A-B-), we guarantee the ordering -1-2-A-B- as required.
1352 *
1353 * Regarding perf: probably even the strict memory_order_seq_cst in both here and poll_armed() would've been
1354 * reasonably quick; but we did better than that, approaching the minimally-strict memory_order_relaxed (but
1355 * not quite). */
1356} // Poll_flag::arm_next_poll()
1357
1359{
1360 /* Replace true (armed) with false (no longer armed); return true (was armed)...
1361 * ...but if was already false (not armed), do nothing ("replace" it with false); return false (was not armed).
1362 * memory_order_acquire: See explanation in arm_next_poll(). */
1363 return m_armed.exchange(false, std::memory_order_acquire);
1364
1365 /* (I (ygoldfel) initially wrote it as:
1366 * bool exp = true; return ...compare_exchange_strong(exp, false, ...acquire);
1367 * because it "felt" somehow more robust to "formally" do-nothing, if it is already `false`... but it is clearly
1368 * slower/weirder to most eyes.) */
1369}
1370
1371// Polled_shared_state template implementations.
1372
1373template<typename Shared_state_t>
1374template<typename... Ctor_args>
1375Polled_shared_state<Shared_state_t>::Polled_shared_state(Ctor_args&&... shared_state_ctor_args) :
1376 m_shared_state(std::forward<Ctor_args>(shared_state_ctor_args)...)
1377{
1378 // Yep.
1379}
1380
1381template<typename Shared_state_t>
1382template<typename Task>
1384{
1385 flow::util::Lock_guard<decltype(m_shared_state_mutex)> lock{m_shared_state_mutex};
1386 task(&m_shared_state);
1387}
1388
1389} // namespace flow::util
Similar mission to Log_context but enables one to safely log under the possibility of a concurrent se...
Definition: log.hpp:1954
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns...
Definition: log.hpp:1786
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1392
static void set_thread_info(Fixed_string *call_thread_nickname, util::Thread_id *call_thread_id)
Same as set_thread_info_in_msg_metadata() but targets the given two variables as opposed to a Msg_met...
Definition: log.cpp:137
A single boolean flag, armed via arm_next_poll() (typically from another thread) and both checked and...
Definition: thread_lcl.hpp:819
std::atomic< bool > m_armed
The flag; true = armed.
Definition: thread_lcl.hpp:850
Poll_flag()
Creates flag in disarmed state: poll_armed() shall return false until the first arm_next_poll().
bool poll_armed()
If armed: disarms and returns true; else returns false.
void arm_next_poll()
From any thread: causes the next poll_armed() call to return true (once).
Optional-use companion to Thread_local_state_registry that (together with Poll_flag) enables the Poll...
Definition: thread_lcl.hpp:991
Shared_state_t Shared_state
Short-hand for template parameter type.
void while_locked(const Task &task)
Locks the non-recursive shared-state mutex, such that no access or modification of the contents of th...
Shared_state m_shared_state
The managed Shared_state.
Mutex_non_recursive m_shared_state_mutex
Protects m_shared_state.
Polled_shared_state(Ctor_args &&... shared_state_ctor_args)
Forwards to the stored object's Shared_state ctor.
boost::thread_specific_ptr replacement (augmentation) whose fast-path .get() touches only thread_loca...
Similar to Thread_local_ptr<T> (a perf-upgraded boost::thread_specific_ptr<T>) but allows iteration t...
Definition: thread_lcl.hpp:225
Thread_local_state * this_thread_state()
Returns pointer to this thread's thread-local object, first constructing it via m_create_state_func i...
Thread_local_state_registry(log::Logger *logger_ptr, String_view nickname_str, decltype(m_create_state_func)&&create_state_func={})
Create empty registry.
~Thread_local_state_registry()
Deletes each Thread_local_state to have been created so far by calls to this_thread_state() from vari...
std::ostream & operator<<(std::ostream &os, const Thread_local_state_registry< Thread_local_state_t > &val)
Serializes a Thread_local_state_registry to a standard output stream.
Function< Thread_local_state *()> m_create_state_func
this_thread_state(), when needing to create a thread's local new Thread_local_state to return,...
Definition: thread_lcl.hpp:286
void set_logger(log::Logger *logger_ptr)
Performs thread-safe Log_context::set_logger(logger_ptr); and – if S_TL_STATE_HAS_MT_LOG_CONTEXT is t...
Lock::mutex_type Mutex
Short-hand for mutex type.
Definition: thread_lcl.hpp:536
boost::unordered_map< Thread_local_state *, Metadata > State_per_thread_map
Type of state_per_thread to task() to while_locked(): extant Thread_local_states and info about their...
Definition: thread_lcl.hpp:249
const std::string & nickname() const
Returns nickname, a brief string suitable for logging.
void while_locked(const Task &task) const
Locks the non-recursive registry mutex, such that the set of extant per-thread Thread_local_states ca...
Thread_local_state * this_thread_state_or_null()
Returns pointer to this thread's thread-local object, if it has been created via an earlier this_thre...
Tsp_wrapper m_this_thread_state_or_null
In a given thread T, m_this_thread_state_or_null.get() is null if this_thread_state() has not yet bee...
Definition: thread_lcl.hpp:804
static void cleanup(Tl_context *ctx)
Called by Thread_local_ptr/thread_specific_ptr for a given thread's m_this_thread_state_or_null....
boost::shared_ptr< Registry_ctl > m_ctl
The non-thread-local state. See Registry_ctl docs. shared_ptr is used only for weak_ptr.
Definition: thread_lcl.hpp:807
static constexpr bool S_TL_STATE_HAS_MT_LOG_CONTEXT
true if and only if Thread_local_state is a public sub-class of log::Log_context_mt which has implica...
Definition: thread_lcl.hpp:257
Thread_local_state_t Thread_local_state
Short-hand for template parameter type. See our class doc header for requirements.
Definition: thread_lcl.hpp:230
Lock_guard< Mutex_non_recursive > Lock
Short-hand for mutex lock.
Definition: thread_lcl.hpp:533
const std::string m_nickname
See nickname().
Definition: thread_lcl.hpp:757
#define FLOW_LOG_INFO(ARG_stream_fragment)
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:200
#define FLOW_LOG_FATAL(ARG_stream_fragment)
Logs a FATAL message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:170
Function< void()> Task
Short-hand for a task that can be posted for execution by a Concurrent_task_loop or flow::util::Task_...
Definition: async_fwd.hpp:96
Flow module containing miscellaneous general-use facilities that don't fit into any other Flow module...
Thread::id Thread_id
Short-hand for an OS-provided ID of a util::Thread.
Definition: util_fwd.hpp:123
boost::unique_lock< Mutex > Lock_guard
Short-hand for advanced-capability RAII lock guard for any mutex, ensuring exclusive ownership of tha...
Definition: util_fwd.hpp:298
Thread_token this_thread_unique_token()
Returns a value conceptually similar to this_thread::get_id() – a thread ID – but that is never recyc...
boost::mutex Mutex_non_recursive
Short-hand for non-reentrant, exclusive mutex. ("Reentrant" = one can lock an already-locked-in-that-...
Definition: util_fwd.hpp:248
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:627
General info (as of this writing for logging only) about a given entry (thread/object) in State_per_t...
Definition: thread_lcl.hpp:234
std::string m_thread_nickname
Thread nickname as per log::Logger::set_thread_info().
Definition: thread_lcl.hpp:238
Thread_token m_thread_token
Unique thread token. See this_thread_unique_token() doc header for details; in short: it is fully uni...
Definition: thread_lcl.hpp:242
Thread_id m_thread_id
Thread ID. See Thread_id doc header for important warning about how these can be recycled (not fully ...
Definition: thread_lcl.hpp:240
The entirety of the cross-thread registry state, in a struct so as to be able to wrap it in a shared_...
Definition: thread_lcl.hpp:543
Mutex m_mutex
Protects the Registry_ctl (or m_state_per_thread; same difference) and log::Log_context::get_logger()...
Definition: thread_lcl.hpp:547
State_per_thread_map m_state_per_thread
Registry containing each Thread_local_state, one per distinct thread to have created one via this_thr...
Definition: thread_lcl.hpp:579
The actual user Thread_local_state stored per thread as lazily-created in this_thread_state(); plus a...
Definition: thread_lcl.hpp:660
boost::weak_ptr< Registry_ctl > m_ctl_observer
Observer of (existent or non-existent) daddy's m_ctl. See Tl_context doc header for explanation.
Definition: thread_lcl.hpp:664
Thread_local_state * m_state
The main user state.
Definition: thread_lcl.hpp:669
Simply wraps a Thread_local_ptr<Tl_context>/boost::thread_specific_ptr<Tl_context>,...
Definition: thread_lcl.hpp:695
Tsp_wrapper(const Tsp_wrapper &src)=delete
Forbid copy.
Thread_local_ptr< Tl_context > m_tsp
What we wrap and forward-to-and-fro: an object with functional semantics identical to those of boost:...
Definition: thread_lcl.hpp:705
Tsp_wrapper & operator=(const Tsp_wrapper &src)=delete
Forbid copy.
Tsp_wrapper(Ctor_args &&... ctor_args)
Constructs payload.
An integer-like, type-safe wrapper for the thread-token concept – see this_thread_unique_token(),...