Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
channel_base.hpp
Go to the documentation of this file.
1/* Flow-IPC: Structured Transport
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
26#pragma once
27
30#include "ipc/util/util_fwd.hpp"
31
33{
34
35// Types.
36
37/// `Channel` base that contains non-parameterized items such as tag types and constants.
39{
40public:
41 // Types.
42
43 /**
44 * Clarifying short-hand for outgoing-message IDs. This is public for logging/reporting use only
45 * (see set_remote_unexpected_response_handler()) as of this writing.
46 */
48
49 /**
50 * Tag type for ctor selection: Backing memory for serialization comes from fixed-size segment allocation(s) from
51 * regular heap (`malloc()`); and similarly for deserialization. Therefore the serialization is copied
52 * into the Channel::Owned_channel transport in the Channel::send() invoker process;
53 * then copied out of there by the opposing (receiving) process. I.e., this mode is *not* zero-copy, in
54 * both directions.
55 *
56 * More specifically: Heap_fixed_builder and Heap_reader are the serialization engines used.
57 *
58 * For concision, and to compile successfully, use the struc::Channel alias class template:
59 * #Channel_via_heap.
60 *
61 * ### Relative benefits/limitations (versus SHM-based `Serialize_via_*_shm`s) ###
62 * These are discussed in some detail in Heap_fixed_builder doc header. Short version:
63 * Pros:
64 * - It's simple internally and externally, and cleanup is 100% guaranteed (no kernel-persistent resources
65 * are involved in serialization of structured messages).
66 * - In terms of simplicity specifically: it is not required to operate within ipc::session at all;
67 * whereas `Serializa_via_*_shm` are so required.
68 * - Subtlety: One *can* operate the SHM-based builders without ipc::session. To do so you'll have to
69 * use the struc::Channel ctor form wherein you provide your own builder and reader engines
70 * which you'll need to first create manually. One cannot do so with Serialize_via_heap ctor form,
71 * however, which is intended for easiest possible struc::Channel setup.
72 *
73 * Cons:
74 * - It's not zero-copy. A linear-time copy is involved on each side of a transmitted message's processing.
75 * - If a leaf is too large to fit into the underlying `Owned_channel` pipe, the transmission will fail,
76 * and the struc::Channel will be hosed. I.e., it places limits on what can be transmitted, and the
77 * exact nature of these limits is not 100% exact/predictable.
78 */
80
81 /**
82 * Tag type for ctor selection: Backing RAM for serialization comes from a given session::Session's SHM arena
83 * (of per-session scope) via exponential-growing-size segment allocation(s). Subsequent deserialization in the
84 * opposing process occurs from directly within the same backing RAM area(s). Therefore the serialization
85 * is *never* copied, period; only a short handle is internally copied into and out of the `Owned_channel` transport.
86 * Conversely, the opposing process does the same as the sender, and we do the converse as the receiver.
87 * I.e., this mode *is* zero-copy, in both directions. Serialize_via_session_shm is usable only within the
88 * ipc::session framework; if you are operating outside it then use the struc::Channel ctor form wherein the
89 * builder/reader engines are supplied as args.
90 *
91 * More specifically, as of this writing: If the `Session` type in tag-form struc::Channel ctor is
92 * session::shm::classic::Server_session or session::shm::classic::Client_session,
93 * then shm::classic::Builder and shm::classic::Reader are the [de]serialization engines used. If the
94 * `Session` type in tag-form struc::Channel ctor is session::shm::arena_lend::jemalloc::Server_session
95 * or session::shm::arena_lend::jemalloc::Client_session, then shm::arena_lend::jemalloc::Builder and
96 * shm::arena_lend::jemalloc::Reader are the [de]serialization engines used. The source arena for our serialization
97 * (message building) comes from the `Session` impl supplied nearby in the ctor call; specifically its
98 * `.session_shm()` arena.
99 *
100 * For concision, and to compile successfully, use the struc::Channel alias class template:
101 * shm::classic::Channel or shm::arena_lend::jemalloc::Channel.
102 *
103 * Relative benefits/limitations
104 * -----------------------------
105 * ### As compared to Serialize_via_heap ###
106 * Essentially they are the inverse of those of Serialize_via_heap above.
107 *
108 * ### shm::classic::Channel versus shm::arena_lend::jemalloc::Channel ###
109 * The relative merits of the two SHM-providers (SHM-classic versus SHM-jemalloc) in ipc::shm are discussed within
110 * that namespace's docs. Short version:
111 * - SHM-jemalloc is intended for safety (due to its asymmetrical internally-IPC-using nature) and efficiency of
112 * allocation (jemalloc is an industry-strength `malloc()` impl, which we've adapted to SHM use; whereas
113 * SHM-classic uses boost.ipc's relatively rudimentary default allocation algorithm (e.g., it does not do thread
114 * caching)).
115 */
117
118 /**
119 * Similar to Serialize_via_session_shm but assumes per-app-scope SHM-arena (as opposed to
120 * per-session-scope) at compile time. More specifically: The source arena for our serialization comes from the
121 * `Session` impl supplied nearby in the ctor call; specifically its `.app_shm()` arena. As for deserialization
122 * on our end: we will be able to handle any objects sent from the other side, period.
123 *
124 * `Session` must be specifically *not* of the `"ipc::session::shm::arena_lend::*::Server_session"` variety;
125 * `"ipc::session::shm::arena_lend::*::Client_session"` cannot be used (any attempt
126 * will not compile). This is because the latter lacks `.app_shm()` due to the nature of arena-lending
127 * SHM-providers, where each process allocates from an arena it maintains for itself (and from which
128 * the other process can only borrow objects). Some more notes on this are below in this doc header.
129 *
130 * For concision, and to compile successfully, use the struc::Channel alias class template:
131 * shm::classic::Channel or shm::arena_lend::jemalloc::Channel.
132 *
133 * Relative benefits/limitations
134 * -----------------------------
135 * ### As compared to Serialize_via_heap ###
136 * Essentially they are the inverse of those of Serialize_via_heap above.
137 *
138 * ### shm::classic::Channel versus shm::arena_lend::jemalloc::Channel ###
139 * See Serialize_via_session_shm.
140 *
141 * In addition there is a *possibly* crucial difference in capabilities, depending on one's app needs
142 * (it is mentioned above in this doc header):
143 * - With SHM-jemalloc (via shm::arena_lend::jemalloc::Channel), it is *not* possible
144 * to use this per-app-scope mode on the client side; only per-session scope is available (this is
145 * a consequence of its asymmetrical, safety-oriented design as of this writing).
146 * On the `Server_session` end it is possible to create out-messages in either scope however.
147 * - An attempt to use this per-app-scope mode with a `session::shm::arena_lend::jemalloc::Client_session`
148 * will be caught at compile time (it... won't compile).
149 * - Specifically, then, with SHM-jemalloc, in session-server use either Serialize_via_app_shm or
150 * Serialize_via_session_shm; in the opposing session-client use the latter only. Deserialization will work
151 * on both sides; but serialization (building/sending) will work only for session-scope resources
152 * (again, attempt to do otherwise will not compile).
153 */
155
156 // Constants.
157
158 /// The sole value of the tag type Serialize_via_heap.
160
161 /// The sole value of the tag type Serialize_via_session_shm.
163
164 /// The sole value of the tag type Serialize_via_app_shm.
166
167 /**
168 * Minimum protocol version supported internally by struc::sync_io::Channel and derivative(s).
169 * Public for logging/reporting only; really it is an internally used only value.
170 */
172
173 /**
174 * Maximum protocol version supported internally by struc::sync_io::Channel and derivative(s).
175 * Public for logging/reporting only; really it is an internally used only value.
176 *
177 * It is currently the same as #S_PROTOCOL_MIN_VERSION, as so far we've kept it intentionally simple to not
178 * deal with backward-compatibility.
179 *
180 * @internal
181 * See sync_io::Channel doc header impl section "Protocol negotiation" for our strategy around this.
182 */
184
185 /**
186 * For struc::Channel::sync_request() (and any other `sync_*()` APIs, that is potentially-blocking ops),
187 * how often the op shall, while awaiting completion or channel error, internally check that the opposing process
188 * still exists (and if not, exit and emit transport::error::Code::S_PEER_PROCESS_NO_LONGER_EXISTS).
189 *
190 * Public for logging/reporting only; really it is an internally used only value.
191 *
192 * ### Rationale / Explanation ###
193 * The mechanism tuned by this knob is a back-stop/backup mechanism employed by each `struc::Channel::sync_...()`
194 * operation; every effort is made to not need to use it and to return an appropriate channel-hosed error
195 * (such as error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE) ~instantly upon the hosing of the channel by the
196 * peer process.
197 *
198 * To wit:
199 * - If you use a with-timeout overload of the `sync_...()` op in question, and that timeout is exceeded,
200 * the `sync_...()` shall always return appropriately. The present mechanism is in that situation irrelevant.
201 * From this point on, let's assume either one used a no-timeout overload; or a long-enough timeout to where
202 * it is desirable to return from `sync_...()` closer chronologically to a problem occurring in the opposing
203 * process than simply awaiting said long timeout.
204 * - A properly written (with Channel::async_end_sending() and Channel dtor both used per their docs)
205 * use of `Channel` on the opposing side, without the opposing process crashing (aborting),
206 * will make the present mechanism irrelevant. error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE shall be emitted
207 * responsively.
208 * - Even without `async_end_sending()`, if the opposing process does not crash (abort), the present
209 * mechanism will still be irrelevant. `boost::asio::error::eof` or equivalent shall be emitted responsively.
210 * - In a properly functioning system, if the opposing side does crash, at least for some Channel::Owned_channel
211 * types -- including ones with at least a Native_socket_stream pipe -- an error shall ~instantly propagate
212 * to our process; thus the present mechanism will again be irrelevant. `eof` or equivalent shall again be
213 * emitted responsively.
214 *
215 * Unfortunately:
216 * - Sometimes an opposing process shall indeed crash (abort).
217 * - When this happens, and there is no Native_socket_stream in the relevant Channel::Owned_channel, and
218 * there is no conceptually similar pipe -- one such that the opposing crash would responsively propagate
219 * an error along the now-defunct `Owned_channel` -- available in `Owned_channel`...
220 * - ...then the `sync_...()` op will hang, either indefinitely or until the (earlier-assumed undesirable large)
221 * timeout given as arg to `sync_...()`.
222 * - We have also observed, in the field in production, that certain mysterious (as of this writing) circumstances
223 * can cause even `Native_socket_stream` to not detect an error, if the opposing process crashes. An `eof`
224 * should be emitted responsively, but we have seen this not happen at times, much to our frustration...
225 * - ...in which case, again, `sync_...()` will hang indefinitely or too-long.
226 *
227 * For that reason, we internally use this back-stop/backup mechanism to still detect the death (crash) of
228 * the opposing process. Specifically:
229 * - Instead of, internally, issuing an indefinite-or-up-to-long-user-specified-timeout wait...
230 * - ...we break it up into successive waits up to the period #S_SYNC_OP_PEER_PROCESS_LIVENESS_CHECK_PERIOD.
231 * - This period shall be sufficiently long so as to make the periodic wakeups negligible in terms of compute
232 * used.
233 * - It will be sufficiently short for a human to perceive the detection of opposing-peer-death as
234 * reasonably responsive. So, it would be a fraction of a second.
235 * - Therefore, after the first and each successive such wait, we check whether the opposing process is alive
236 * according to the OS. (As of this writing the mechanism used for this is util::process_running().) If not,
237 * `sync_...()` shall immediately emit transport::error::Code::S_PEER_PROCESS_NO_LONGER_EXISTS (hence return or
238 * throw).
239 *
240 * This mechanism is not perfect:
241 * - It is not maximally responsive; a value such as 500msec is quick-enough but not sub-millisecond as with
242 * proper OS-driven error propagation.
243 * - It will not (as of this writing) detect a zombified or otherwise stalled opposing process
244 * (unlike official zombification combined with OS-driven error propagation). util::process_running() does not
245 * detect those cases.
246 * - Upon detecting opposing process death, the emitted error -- unlike the OS-propagated ones -- does not
247 * "stick" to the `Channel` object. One *should* stop using that `Channel`; but if one does not, further attempts
248 * to use it may not obviously and quickly fail.
249 * - (That said, in our experience, ipc::session will always report any opposing process death/zombification/stall
250 * appropriately quickly. The problem, typically, is to get out of the hanging `sync_...()` first so as to
251 * be able to react to such a report. The present mechanism will do that.)
252 *
253 * However, it can help significantly when needed; and when not needed, it does no harm (basically a periodic
254 * internal no-op inside the given `sync_...()`).
255 */
256 static constexpr util::Fine_duration S_SYNC_OP_PEER_PROCESS_LIVENESS_CHECK_PERIOD = boost::chrono::milliseconds(500);
257}; // class Channel_base
258
259} // namespace ipc::transport::struc
int16_t proto_ver_t
Type sufficient to store a protocol version; positive values identify newer versions of a protocol; w...
Channel base that contains non-parameterized items such as tag types and constants.
static constexpr util::Fine_duration S_SYNC_OP_PEER_PROCESS_LIVENESS_CHECK_PERIOD
For struc::Channel::sync_request() (and any other sync_*() APIs, that is potentially-blocking ops),...
static constexpr Serialize_via_session_shm S_SERIALIZE_VIA_SESSION_SHM
The sole value of the tag type Serialize_via_session_shm.
static constexpr Serialize_via_heap S_SERIALIZE_VIA_HEAP
The sole value of the tag type Serialize_via_heap.
static constexpr Protocol_negotiator::proto_ver_t S_PROTOCOL_MAX_VERSION
Maximum protocol version supported internally by struc::sync_io::Channel and derivative(s).
static constexpr Protocol_negotiator::proto_ver_t S_PROTOCOL_MIN_VERSION
Minimum protocol version supported internally by struc::sync_io::Channel and derivative(s).
msg_id_t msg_id_out_t
Clarifying short-hand for outgoing-message IDs.
static constexpr Serialize_via_app_shm S_SERIALIZE_VIA_APP_SHM
The sole value of the tag type Serialize_via_app_shm.
Sub-module of Flow-IPC module ipc::transport providing transmission of structured messages specifical...
Definition: channel.cpp:31
uint64_t msg_id_t
Message ID uniquely identifying outgoing message (Msg_out, among all other Msg_outs),...
Definition: struc_fwd.hpp:215
flow::Fine_duration Fine_duration
Short-hand for Flow's Fine_duration.
Definition: util_fwd.hpp:114
Similar to Serialize_via_session_shm but assumes per-app-scope SHM-arena (as opposed to per-session-s...
Tag type for ctor selection: Backing memory for serialization comes from fixed-size segment allocatio...
Tag type for ctor selection: Backing RAM for serialization comes from a given session::Session's SHM ...