Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
timer_ev_emitter.cpp
Go to the documentation of this file.
1/* Flow-IPC: Core
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
22#include <flow/error/error.hpp>
23#include <boost/move/make_unique.hpp>
24#include <boost/asio/connect_pipe.hpp>
25#include <cassert>
26#include <cstdlib>
27#include <ostream>
28#include <utility>
29
30namespace ipc::util::sync_io
31{
32
33// Implementations.
34
35Timer_event_emitter::Timer_event_emitter(flow::log::Logger* logger_ptr, String_view nickname_str) :
36 flow::log::Log_context(logger_ptr, Log_component::S_UTIL),
37 m_nickname(nickname_str),
38 m_worker_started(false),
39 m_worker(get_logger(),
40 /* (Linux) OS thread name will truncate m_nickname to 15-5=10 chars here; high chance that'll include
41 * something decently useful; probably not everything though; depends on nickname. It's a decent attempt. */
42 std::string("TEvE-") + m_nickname)
43{
44 FLOW_LOG_TRACE("Timer_event_emitter [" << *this << "]: Created; worker thread not yet started (lazily started "
45 "by first timer async-wait, if any).");
46}
47
49{
50 /* Stop/join thread here, so past this statement no handlers of ours can run, and therefore the boost.asio
51 * pipe-guys can be destroyed in peace without something trying to access them -- while *(m_worker.task_engine())
52 * (which they reference, including potentially in their dtors) -- still exists due to our member decl order. */
53 m_worker.stop();
54}
55
57{
58 using flow::util::Timer;
59
60 return Timer{*(m_worker.task_engine())};
61}
62
64{
65 using boost::asio::writable_pipe;
66 using boost::asio::connect_pipe;
67 using boost::movelib::make_unique;
68
69 auto& task_engine = *(m_worker.task_engine());
70 auto read_end = make_unique<Timer_fired_read_end>(task_engine);
71 auto write_end = make_unique<writable_pipe>(task_engine);
72 Error_code sys_err_code;
73 connect_pipe(*read_end, *write_end, sys_err_code);
74
75 if (sys_err_code)
76 {
77 // @todo Someday we should report this as an error, etc. Low-priority, as at this stage everything is kaput.
78 FLOW_LOG_FATAL("connect_pipe() failed; this should never happen. Details follow.");
79 FLOW_ERROR_SYS_ERROR_LOG_FATAL();
80 assert(false && "connect_pipe() failed; this should never happen."); std::abort();
81 return nullptr;
82 }
83 // else
84
85 auto& read_end_ref = m_signal_pipe_readers.emplace_back(std::move(read_end));
86
87#ifndef NDEBUG
88 const auto result =
89#endif
90 m_signal_pipe_writers.emplace(read_end_ref.get(), std::move(write_end));
91 assert(result.second);
92
93 FLOW_LOG_TRACE("Timer_event_emitter [" << *this << "]: Pipe created; read-end ptr = [" << read_end_ref.get() << "].");
94
95 return read_end_ref.get();
96} // Timer_event_emitter::create_timer_signal_pipe()
97
98void Timer_event_emitter::timer_async_wait(flow::util::Timer* timer, Timer_fired_read_end* read_end)
99{
100 FLOW_LOG_TRACE("Timer_event_emitter [" << *this << "]: Starting timer async-wait; when/if it fires, "
101 "we will write to write-end corresponding to read-end ptr = [" << read_end << "].");
102
103 if (!m_worker_started)
104 {
105 m_worker_started = true;
106
107 FLOW_LOG_TRACE("Timer_event_emitter [" << *this << "]: First async-wait => lazily starting thread.");
108
109 m_worker.start(flow::async::reset_this_thread_pinning);
110 // Don't inherit any strange core-affinity! ^-- Worker must float free.
111 }
112
113 /* Careful: this is a user thread, but the handler below is in our worker thread.
114 * Accessing m_signal_pipe_writers inside there would be not thread-safe, as if some timer happens to fire,
115 * while they (say) create_timer_signal_pipe() for another future timer's purposes, things could explode.
116 * Save the value now; it is accurate and guaranteed valid until `*this` dies (which can only happen
117 * after thread is joined, meaning handler couldn't possibly be executing). */
118 const auto it = m_signal_pipe_writers.find(read_end);
119 assert((it != m_signal_pipe_writers.end()) && "timer_async_wait() invoked on read_end from another *this?");
120 const auto write_end = it->second.get();
121
122 timer->async_wait([this, write_end, read_end](const Error_code& async_err_code)
123 {
124 // We are in the worker thread m_worker.
125
126 auto sys_err_code = async_err_code;
127
128 if (sys_err_code == boost::asio::error::operation_aborted)
129 {
130 // As promised we only report actual firings. Stuff is shutting down, or timer canceled. GTFO.
131 return;
132 }
133 // else
134
135 if (sys_err_code)
136 {
137 /* This decision (to simply pretend it was a success code) is borrowed from flow::async::schedule_*().
138 * assert() is another option, but use Flow wisdom. */
139 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
140 FLOW_LOG_WARNING("Timer_event_emitter [" << *this << "]: "
141 "Timer system error; just logged; totally unexpected; pretending it fired normally.");
142 }
143
144 FLOW_LOG_TRACE("Timer_event_emitter [" << *this << "]: Timer fired. Pushing byte to pipe; "
145 "read_end ptr = [" << read_end << "]. User code should detect event soon and cause byte "
146 "to be popped.");
147
148 util::pipe_produce(get_logger(), write_end);
149 }); // timer->async_wait()
150} // Timer_event_emitter::timer_async_wait()
151
153{
154 FLOW_LOG_TRACE("Timer_event_emitter [" << *this << "]: Timer presumably fired and pushed byte to pipe; "
155 "we now pop it.");
156
157 util::pipe_consume(get_logger(), read_end);
158} // Timer_event_emitter::consume_timer_firing_signal()
159
160std::ostream& operator<<(std::ostream& os, const Timer_event_emitter& val)
161{
162 return
163 os << '[' << val.m_nickname << "]@" << static_cast<const void*>(&val);
164}
165
166} // namespace ipc::util::sync_io
An object of this type, used internally to implement sync_io-pattern objects that require timer event...
bool m_worker_started
Whether m_worker.start() has already been lazily called (first timer_async_wait()); initially false.
boost::asio::readable_pipe Timer_fired_read_end
Object representing the read end of IPC mechanism, where readable status indicates the associated tim...
boost::unordered_flat_map< Timer_fired_read_end *, boost::movelib::unique_ptr< boost::asio::writable_pipe > > m_signal_pipe_writers
Stores the write-ends of the pipes created in create_timer_signal_pipe(), indexed by pointer to their...
const std::string m_nickname
Nickname as passed to ctor.
void consume_timer_firing_signal(Timer_fired_read_end *read_end)
Must be called after a timer_async_wait()-triggered timer fired, before invoking that method for the ...
void timer_async_wait(flow::util::Timer *timer, Timer_fired_read_end *read_end)
To be used on a timer T returned by create_timer(), this is the replacement for the usual T....
~Timer_event_emitter()
Ends background thread, if any started; hence no timer-firings will execute after.
Timer_event_emitter(flow::log::Logger *logger_ptr, String_view nickname_str)
Constructs emitter.
flow::util::Timer create_timer()
Creates idle timer for use with timer_async_wait() subsequently.
flow::async::Single_thread_task_loop m_worker
The thread where (only) timer-firing events (from create_timer()-created Timers) execute.
Timer_fired_read_end * create_timer_signal_pipe()
Creates, and internally stores, an IPC mechanism instance intended for use with a given create_timer(...
std::vector< boost::movelib::unique_ptr< Timer_fired_read_end > > m_signal_pipe_readers
The readers (never null) returned by create_timer_signal_pipe().
Contains common code, as well as important explanatory documentation in the following text,...
Definition: util_fwd.hpp:217
std::ostream & operator<<(std::ostream &os, const Timer_event_emitter &val)
Prints string representation of the given Timer_event_emitter to the given ostream.
void pipe_produce(flow::log::Logger *logger_ptr, Pipe_writer *pipe)
Writes a byte to the given pipe writer.
Definition: util.cpp:60
void pipe_consume(flow::log::Logger *logger_ptr, Pipe_reader *pipe)
Reads a byte via the given pipe reader.
Definition: util.cpp:89
flow::util::String_view String_view
Short-hand for Flow's String_view.
Definition: util_fwd.hpp:112
Log_component
The flow::log::Component payload enumeration containing various log components used by Flow-IPC inter...
Definition: common.hpp:338
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313