Flow 3.0.0
Flow project: Full implementation reference.
thread_lcl_str_appender.cpp
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
21#include <boost/move/make_unique.hpp>
22
23namespace flow::log
24{
25
26// Implementations.
27
28Thread_local_string_appender*
30{
32
33 /* It's just like an on-demand singleton -- but per thread. Precisely because it is per thread, this code is
34 * thread-safe unlike a regular on-demand singleton. Then, after the thread lookup, another lookup obtains the
35 * particular source object's dedicated appender (for the current thread). (Reminder: source_obj_id is unique ID
36 * of an object that wants to use appenders. In theory even a mix of different classes can
37 * co-exist in this system, since IDs are unique across all objects of all types.)
38 *
39 * The crux of it is that the TL-access into *this_thread_appender_ptrs yields a different addr depending on the
40 * current thread; followed by a presumably quick map lookup. If the performance of TL-access is
41 * higher than the performance of creating a new string, inserter around string, and stream around inserter,
42 * then this whole thing is worth it. Otherwise it is not.
43 *
44 * Update: Now it might still be worth it, because the fact that each ostream thus stored maintains a
45 * persistent yet independent-from-others formatter state (with the save_formatting_state_and_restore_prev() feature
46 * further helping to manage formatting) guarantees convenient, predictable behavior for users of ostream formatters.
47 * That is to say, this allows user to (say) set chrono::symbol_format formatter for this thread's logging of
48 * durations; yet without concurrently affecting other threads' output of durations; yet also without having re-apply
49 * that formatter repeatedly just to keep duration output looking a certain way. So, desiring persistent formatting
50 * for a given stream-writing entity OVER TIME means new-string/inserter-around-string/stream-around-insert being
51 * done for each little stream-writing code snippet is insufficient, even if it were computationally as cheap
52 * as the following lookup. */
53
54 /* Despite the weird look, this is basically almost equivalent to accessing a `static thread_local` member.
55 * See class doc header for a brief introduction to what's happening here. In short: This contraption allows
56 * us to detect the return-null case. */
57 const auto this_thread_appender_ptrs
58 = Thread_local_obj_deinit_safe<Source_obj_to_appender_map, Thread_local_string_appender>
59 ::this_thread_obj_or_null();
60 if (!this_thread_appender_ptrs)
61 {
62 return nullptr; // As advertised.
63 }
64 // else
65
66 auto& appender_ptrs_map = *this_thread_appender_ptrs;
67
68 const auto& id = source_obj_id.unique_id();
69 auto& appender_ptr_ref = appender_ptrs_map[id]; // (Reference to) smart pointer directly in the map.
70 if (!appender_ptr_ref)
71 {
72 /* Uncommon code path. appender_ptr (inside the map) was just initialized to null smart pointer. Un-null it.
73 * Because it's a smart pointer, not a raw one, deleting the map will delete the object allocated here.
74 *
75 * (Note about cleanup: *this_thread_appender_ptrs dtor will be executed when thread exits.
76 * Therefore any per-thread memory is not leaked past the thread's lifetime. However, for a given thread,
77 * per-object-ID memory IS leaked even when the corresponding object disappears; we provide no mechanism
78 * to free this. See class doc header for discussion. */
79 appender_ptr_ref.reset(new Thread_local_string_appender);
80 }
81
82 /* You may be rightly alarmed that .get() is used and even returned outside the current block.
83 * In this case, it is definitely safe: The underlying pointer is deleted only on .reset() or destruction
84 * of this pointer; .reset() is called only once and was just done above; and destruction occurs only
85 * when the thread exits. Since the returned object is to be, by definition (it's in the name!) used only
86 * in the current thread, any attempt to use it in another thread is a blatant mis-use and need not be considered.
87 * (Why not return a smart pointer anyway? Well, why impose on outside caller the associated syntactic complexities,
88 * when it would be done due to internal implementation details in the first place? There are other reasons, but
89 * that one is enough, even if they didn't exist.) */
90 return appender_ptr_ref.get();
91} // Thread_local_string_appender::this_thread_string_appender()
92
94 m_target_appender_ostream_prev_os_state
95 (boost::movelib::make_unique<boost::io::ios_all_saver>(m_target_appender_ostream.os()))
96{
97 // Nothing else.
98}
99
101{
103 return appender_ostream();
104}
105
107{
108 // @todo Maybe we should also expose ostream& instead of pointer, if only for consistency.
109 return &(m_target_appender_ostream.os());
110}
111
113{
114 using boost::io::ios_all_saver;
115
116 /* Due to smart pointer mechanics and function call expression evaluation order semantics, this does the following:
117 * - Inside reset() parentheses, new ios_all_saver *X is created.
118 * - X->ios_all_saver() runs: the current state of the ostream is saved in *X.
119 * - Inside reset() method, the unique_ptr<>'s internal saved pointer is replaced with X.
120 * - Since the previous saved pointer Y is replaced, *Y is destroyed.
121 * - Y->~ios_all_saver() runs: the stated saved at *Y's construction is restored to the ostream.
122 *
123 * The key insight is that X saved now will be the Y at the present method's next invocation. Thus the inductive
124 * chain is built. The induction base is that *X is the clean just-constructed ostream's formatting. */
125 m_target_appender_ostream_prev_os_state = boost::movelib::make_unique<ios_all_saver>(m_target_appender_ostream.os());
126}
127
129{
131}
132
133} // namespace flow::log
Internal flow::log class that facilitates a more efficient way to get util::ostream_op_to_string() be...
void save_formatting_state_and_restore_prev()
Saves the formatting state of the ostream returned by appender_ostream() and sets that same ostream t...
const std::string & target_contents() const
Read-only accessor for the contents of the string, as written to it since the last fresh_appender_ost...
std::ostream * appender_ostream()
Same as fresh_appender_ostream() but does not clear the result string, enabling piecemeal writing to ...
util::String_ostream m_target_appender_ostream
The target string wrapped by an ostream. Emptied at construction and in fresh_appender_ostream() only...
static Thread_local_string_appender * this_thread_string_appender(const util::Unique_id_holder &source_obj_id)
Returns a pointer to the exactly one Thread_local_string_appender object that is accessible from the ...
Thread_local_string_appender()
Initializes object with an empty string and the streams machinery available to write to that string.
boost::movelib::unique_ptr< boost::io::ios_all_saver > m_target_appender_ostream_prev_os_state
Stores the ostream formatter state from construction time or time of last save_formatting_state_and_r...
std::ostream * fresh_appender_ostream()
Clears the internally stored string (accessible for reading via target_contents()),...
void str_clear()
Performs std::string::clear() on the object returned by str().
std::ostream & os()
Access to stream that will write to owned string.
const std::string & str() const
Read-only access to the string being wrapped.
A thread_local object of type Obj, in effect, but with death-aware access: this_thread_obj_or_null() ...
Each object of this class stores (at construction) and returns (on demand) a numeric ID unique from a...
id_t unique_id() const
Raw unique ID identifying this object as well as any object of a derived type.
Flow module providing logging functionality.