Flow 1.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
20#include <boost/move/make_unique.hpp>
21
22namespace flow::log
23{
24
25// Static initializations.
26
27boost::thread_specific_ptr<Thread_local_string_appender::Source_obj_to_appender_map>
29
30// Implementations.
31
32Thread_local_string_appender*
34{
35 /* It's just like an on-demand singleton -- but per thread. Precisely because it is per thread, this code is
36 * thread-safe unlike a regular on-demand singleton. Then, after the thread lookup, another lookup obtains the
37 * particular source object's dedicated appender (for the current thread). (Reminder: source_obj_id is unique ID
38 * of an object that wants to use appenders. In theory even a mix of different classes can
39 * co-exist in this system, since IDs are unique across all objects of all types.)
40 *
41 * The crux of it is that the .get() call just below returns a different pointer depending on the current thread;
42 * followed by a presumably quick map lookup. If the performance of .get() is higher than the performance of creating
43 * a new string, inserter around string, and stream around inserter, then this whole thing is worth it. Otherwise it
44 * is not.
45 *
46 * Update: Now it might still be worth it, because the fact that each ostream thus stored maintains a
47 * persistent yet independent-from-others formatter state (with the save_formatting_state_and_restore_prev() feature
48 * further helping to manage formatting) guarantees convenient, predictable behavior for users of ostream formatters.
49 * That is to say, this allows user to (say) set chrono::symbol_format formatter for this thread's logging of
50 * durations; yet without concurrently affecting other threads' output of durations; yet also without having re-apply
51 * that formatter repeatedly just to keep duration output looking a certain way. So, desiring persistent formatting
52 * for a given stream-writing entity OVER TIME means new-string/inserter-around-string/stream-around-insert being
53 * done for each little stream-writing code snippet is insufficient, even if it were computationally as cheap
54 * as the following lookup. */
55
57 if (!appender_ptrs)
58 {
59 // Uncommon code path.
60
61 appender_ptrs = new Source_obj_to_appender_map;
62 s_this_thread_appender_ptrs.reset(appender_ptrs);
63
64 assert(appender_ptrs == s_this_thread_appender_ptrs.get());
65
66 /* Note about cleanup: `delete appender_ptrs;` will be executed by thread_specific_ptr when thread exits.
67 * Therefore any per-thread memory is not leaked past the thread's lifetime. However, for a given thread,
68 * per-object-ID memory IS leaked even when the corresponding object disappears; we provide no mechanism
69 * to free this. See class doc header for discussion. */
70 }
71
72 Source_obj_to_appender_map& appender_ptrs_map = *appender_ptrs;
73
74 const auto& id = source_obj_id.unique_id();
75 auto& appender_ptr_ref = appender_ptrs_map[id]; // (Reference to) smart pointer directly in the map.
76 if (!appender_ptr_ref)
77 {
78 /* Uncommon code path. appender_ptr (inside the map) was just initialized to null smart pointer. Un-null it.
79 * Because it's a smart pointer, not a raw one, deleting the map will delete the object allocated here. */
80 appender_ptr_ref.reset(new Thread_local_string_appender);
81 }
82
83 /* You may be rightly alarmed that .get() is used and even returned outside the current block.
84 * In this case, it is definitely safe: The underlying pointer is deleted only on .reset() or destruction
85 * of this pointer; .reset() is called only once and was just done above; and destruction occurs only
86 * when the thread exits. Since the returned object is to be, by definition (it's in the name!) used only
87 * in the current thread, any attempt to use it in another thread is a blatant mis-use and need not be considered.
88 * (Why not return a smart pointer anyway? Well, why impose on outside caller the associated syntactic complexities,
89 * when it would be done due to internal implementation details in the first place? There are other reasons, but
90 * that one is enough, even if they didn't exist.) */
91 return appender_ptr_ref.get();
92}
93
95 m_target_appender_ostream_prev_os_state
96 (boost::movelib::make_unique<boost::io::ios_all_saver>(m_target_appender_ostream.os()))
97{
98 // Nothing else.
99}
100
102{
104 return appender_ostream();
105}
106
108{
109 // @todo Maybe we should also expose ostream& instead of pointer, if only for consistency.
110 return &(m_target_appender_ostream.os());
111}
112
114{
115 using boost::io::ios_all_saver;
116
117 /* Due to smart pointer mechanics and function call expression evaluation order semantics, this does the following:
118 * - Inside reset() parentheses, new ios_all_saver *X is created.
119 * - X->ios_all_saver() runs: the current state of the ostream is saved in *X.
120 * - Inside reset() method, the unique_ptr<>'s internal saved pointer is replaced with X.
121 * - Since the previous saved pointer Y is replaced, *Y is destroyed.
122 * - Y->~ios_all_saver() runs: the stated saved at *Y's construction is restored to the ostream.
123 *
124 * The key insight is that X saved now will be the Y at the present method's next invocation. Thus the inductive
125 * chain is built. The induction base is that *X is the clean just-constructed ostream's formatting. */
126 m_target_appender_ostream_prev_os_state = boost::movelib::make_unique<ios_all_saver>(m_target_appender_ostream.os());
127}
128
130{
132}
133
134} // 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...
static boost::thread_specific_ptr< Source_obj_to_appender_map > s_this_thread_appender_ptrs
Thread-local storage for each thread's map storing objects of this class (lazily set to non-null on 1...
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...
boost::unordered_map< util::Unique_id_holder::id_t, boost::movelib::unique_ptr< Thread_local_string_appender > > Source_obj_to_appender_map
Short-hand for map of a given thread's appender objects indexed by the IDs of their respective source...
static Thread_local_string_appender * get_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 ...
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...
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.
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.