Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
bipc_mq_handle.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
22#include <flow/error/error.hpp>
23#include <flow/common.hpp>
24#include <boost/interprocess/ipc/message_queue.hpp>
25#include <boost/version.hpp>
26#include <boost/move/make_unique.hpp>
27#include <boost/io/ios_state.hpp>
28#include <cassert>
29#include <cerrno>
30#include <ostream>
31#include <type_traits>
32#include <utility>
33
34namespace ipc::transport
35{
36
37// Initializers.
38
40
41// Implementations.
42
44 m_interrupting_snd(false), // (Not algorithmically needed as of this writing, but avoid some sanitizer complaints.)
45 m_interrupting_rcv(false) // (Ditto. E.g., swap() through clang's UBSAN => complains it's uninitialized.)
46{
47 // Right.
48}
49
50template<typename Mode_tag>
51Bipc_mq_handle::Bipc_mq_handle(Mode_tag mode_tag, flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
52 size_t max_n_msg, size_t max_msg_sz,
53 const util::Permissions& perms,
54 Error_code* err_code) :
55 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT),
56 m_absolute_name(absolute_name_arg),
57 m_interrupting_snd(false),
58 m_interrupting_rcv(false)
59{
60 using flow::log::Sev;
61 using boost::io::ios_all_saver;
62 using boost::movelib::make_unique;
63 using bipc::message_queue;
64
65 assert(max_n_msg >= 1);
66 assert(max_msg_sz >= 1);
67
68 static_assert(std::is_same_v<Mode_tag, util::Create_only> || std::is_same_v<Mode_tag, util::Open_or_create>,
69 "Can only delegate to this ctor with Mode_tag = Create_only or Open_or_create.");
70 constexpr char const * MODE_STR = std::is_same_v<Mode_tag, util::Create_only>
71 ? "create-only" : "open-or-create";
72
73 if (logger_ptr && logger_ptr->should_log(Sev::S_TRACE, get_log_component()))
74 {
75 ios_all_saver saver{*(logger_ptr->this_thread_ostream())}; // Revert std::oct/etc. soon.
76 FLOW_LOG_TRACE_WITHOUT_CHECKING
77 ("Bipc_mq_handle [" << *this << "]: Constructing MQ handle to MQ at name [" << absolute_name() << "] in "
78 "[" << MODE_STR << "] mode; max msg size [" << max_msg_sz << "] x [" << max_n_msg << "] msgs; "
79 "perms = [" << std::setfill('0') << std::setw(4) << std::oct << perms.get_permissions() << "].");
80 }
81
82 /* m_mq is null. Try to create/create-or-open it; it may throw exception; this will do the right thing including
83 * leaving m_mq at null, as promised, on any error. Note we might throw exception because of this call. */
84 op_with_possible_bipc_mq_exception(err_code, "Bipc_mq_handle(): bipc::message_queue()",
85 [&]()
86 {
87 m_mq = make_unique<message_queue>(mode_tag, absolute_name().native_str(), max_n_msg, max_msg_sz, perms);
88 /* Bonus: All bipc CREATE/OPEN_OR_CREATE guys take care to ensure permissions are set regardless of umask,
89 * so no need for us to set_resource_permissions() here. */
90 });
91} // Bipc_mq_handle::Bipc_mq_handle(Mode_tag)
92
93Bipc_mq_handle::Bipc_mq_handle(flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
94 util::Create_only, size_t max_n_msg, size_t max_msg_sz,
95 const util::Permissions& perms, Error_code* err_code) :
96 Bipc_mq_handle(util::CREATE_ONLY, logger_ptr, absolute_name_arg, max_n_msg, max_msg_sz, perms, err_code)
97{
98 // Cool.
99}
100
101Bipc_mq_handle::Bipc_mq_handle(flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
102 util::Open_or_create, size_t max_n_msg, size_t max_msg_sz,
103 const util::Permissions& perms, Error_code* err_code) :
104 Bipc_mq_handle(util::OPEN_OR_CREATE, logger_ptr, absolute_name_arg, max_n_msg, max_msg_sz, perms, err_code)
105{
106 // Cool.
107}
108
109Bipc_mq_handle::Bipc_mq_handle(flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
110 util::Open_only, Error_code* err_code) :
111 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT),
112 m_absolute_name(absolute_name_arg),
113 m_interrupting_snd(false),
114 m_interrupting_rcv(false)
115{
116 using boost::movelib::make_unique;
117 using bipc::message_queue;
118
119 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Constructing MQ handle to MQ at name "
120 "[" << absolute_name() << "] in open-only mode.");
121
122 /* m_mq is null. Try to create it; it may throw exception; this will do the right thing including
123 * leaving m_mq at null, as promised, on any error. Note we might throw exception because of this call. */
124 op_with_possible_bipc_mq_exception(err_code, "Bipc_mq_handle(): bipc::message_queue(OPEN_ONLY)",
125 [&]()
126 {
127 m_mq = make_unique<message_queue>(util::OPEN_ONLY, absolute_name().native_str());
128 });
129} // Bipc_mq_handle::Bipc_mq_handle(Open_only)
130
131// Just move-construct m_mq, m_absolute_name, and Log_holder.
133
135{
136 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Closing MQ handle (already null? = [" << (!m_mq) << "]).");
137}
138
139// Just do m_mq = std::move(src.m_mq) and same with m_absolute_name and Log_holder.
141
143{
144 using flow::log::Log_context;
145 using std::swap;
146
147 // This is a bit faster than un-specialized std::swap() which would require a move ction + 2 move assignments.
148
149 swap(static_cast<Log_context&>(val1), static_cast<Log_context&>(val2));
150 swap(val1.m_mq, val2.m_mq);
154}
155
157{
158 assert(m_mq && "As advertised: max_msg_size() => undefined behavior if not successfully cted or was moved-from.");
159 return m_mq->get_max_msg_size();
160} // Bipc_mq_handle::max_msg_size()
161
163{
164 assert(m_mq && "As advertised: max_n_msgs() => undefined behavior if not successfully cted or was moved-from.");
165 return m_mq->get_max_msg();
166} // Bipc_mq_handle::max_n_msgs()
167
169{
170 using flow::util::buffers_dump_string;
171
172 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, try_send, blob, _1);
173 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
174
175 assert(m_mq && "As advertised: try_send() => undefined behavior if not successfully cted or was moved-from.");
176
177 bool not_blocked;
178 op_with_possible_bipc_mq_exception(err_code, "Bipc_mq_handle::try_send(): bipc::message_queue::try_send()",
179 [&]()
180 {
181 auto blob_data = blob.data();
182 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
183 "size [" << blob.size() << "].");
184 if (blob.size() == 0)
185 {
186 /* bipc::message_queue::try_send() invokes memcpy(X, nullptr, N), even when N == 0;
187 * which is (1) empirically speaking harmless but (2) technically in violation of arg 2's non-null decl
188 * hence (3) causes a clang UBSAN sanitizer error. So waste a couple cycles by feeding it this dummy
189 * non-null value. */
190 blob_data = static_cast<const void*>(&blob_data);
191 }
192 else // if (blob.size() != 0)
193 {
194 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(blob, " ") << "].");
195 }
196
197 not_blocked
198 = m_mq->try_send(blob_data, blob.size(), 0); // Throws <=> error wrapper sets truthy *err_code.
199 if (!not_blocked)
200 {
201 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
202 "size [" << blob.size() << "]: would-block.");
203 }
204 }); // op_with_possible_bipc_mq_exception()
205 if (*err_code) // It logged if truthy.
206 {
207 return false; // not_blocked is garbage.
208 }
209 // else
210
211 return not_blocked; // Logged about it already.
212} // Bipc_mq_handle::try_send()
213
215{
216 using flow::util::buffers_dump_string;
217
218 if (flow::error::exec_void_and_throw_on_error
219 ([&](Error_code* actual_err_code) { send(blob, actual_err_code); },
220 err_code, "Bipc_mq_handle::send()"))
221 {
222 return;
223 }
224 // else
225 err_code->clear();
226
227 assert(m_mq && "As advertised: send() => undefined behavior if not successfully cted or was moved-from.");
228
229 auto blob_data = blob.data();
230 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-push of blob @[" << blob_data << "], "
231 "size [" << blob.size() << "]. Trying nb-push first; if it succeeds -- great. "
232 "Else will wait/retry/wait/retry/....");
233 if (blob.size() == 0)
234 {
235 // See similarly-placed comment in try_send() which explains this.
236 blob_data = static_cast<const void*>(&blob_data);
237 }
238 else // if (blob.size() != 0)
239 {
240 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(blob, " ") << "].");
241 }
242
243 /* We could just invoke blocking m_mq->send(), but to get the promised logging we go a tiny bit fancier as follows.
244 * Update: Now that we have to be interrupt_*()ible, also reuse wait_*() instead of using native
245 * m_mq->*(). */
246
247 bool ok;
248 while (true)
249 {
250 op_with_possible_bipc_mq_exception(err_code, "Bipc_mq_handle::send(): bipc::message_queue::try_send()",
251 [&]()
252 {
253 ok = m_mq->try_send(blob_data, blob.size(), 0); // Throws <=> error wrapper sets truthy *err_code.
254 });
255 if (*err_code || ok)
256 {
257 // Threw => true error => *err_code set; get out. Didn't throw and returned true => success; get out.
258 return;
259 }
260 // else if (would-block): TRACE-logs.
261
262 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
263 "size [" << blob.size() << "]: would-block. Executing blocking-wait.");
264
265 wait_sendable(err_code);
266 if (*err_code)
267 {
268 // Whether interrupted or true error, we're done.
269 return;
270 }
271 // else
272 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
273 } // while (true)
274} // Bipc_mq_handle::send()
275
277 Error_code* err_code)
278{
279 using flow::util::time_since_posix_epoch;
280 using flow::util::buffers_dump_string;
281 using flow::Fine_clock;
282 using boost::chrono::round;
283 using boost::chrono::microseconds;
284
285 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, timed_send, blob, timeout_from_now, _1);
286 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
287
288 // Similar to a combo of (blocking) send() and (non-blocking) try_send() -- keeping comments light where redundant.
289
290 assert(m_mq && "As advertised: timed_send() => undefined behavior if not successfully cted or was moved-from.");
291
292 auto blob_data = blob.data();
293 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-timed-push of blob @[" << blob_data << "], "
294 "size [" << blob.size() << "]; timeout ~[" << round<microseconds>(timeout_from_now) << "]. "
295 "Trying nb-push first; if it succeeds -- great. Else will wait/retry/wait/retry/....");
296 if (blob.size() == 0)
297 {
298 // See similarly-placed comment in try_send() which explains this.
299 blob_data = static_cast<const void*>(&blob_data);
300 }
301 else // if (blob.size() != 0)
302 {
303 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(blob, " ") << "].");
304 }
305
306 bool ok;
307 auto before = Fine_clock::now();
308
309 while (true)
310 {
311 op_with_possible_bipc_mq_exception(err_code, "Bipc_mq_handle::timed_send(): bipc::message_queue::try_send()",
312 [&]()
313 {
314 ok = m_mq->try_send(blob_data, blob.size(), 0); // Throws <=> error wrapper sets truthy *err_code.
315 });
316 if (*err_code)
317 {
318 // Threw => true error => *err_code set; get out.
319 return false;
320 }
321 // else if (would-block or success):
322 if (ok)
323 {
324 break; // Instant success.
325 }
326 // else if (would-block): TRACE-logs.
327
328 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
329 "size [" << blob.size() << "]: would-block. Executing blocking-wait.");
330
331 const bool ready = timed_wait_sendable(timeout_from_now, err_code);
332 if (*err_code)
333 {
334 // Whether interrupted or true error, we're done.
335 return false;
336 }
337 // else:
338
339 if (!ready) // I.e., if (timed out).
340 {
341 FLOW_LOG_TRACE("Did not finish before timeout.");
342 return false;
343 }
344 // else: successful wait for transmissibility. Try nb-transmitting again.
345 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
346
347 const auto after = Fine_clock::now();
348 timeout_from_now -= (after - before); // Reduce time left by the duration of the last attempt.
349 before = after;
350 } // while (true)
351
352 return true;
353} // Bipc_mq_handle::timed_send()
354
356{
357 using flow::util::buffers_dump_string;
358 using util::Blob_mutable;
359
360 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, try_receive, blob, _1);
361 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
362
363 assert(m_mq && "As advertised: try_receive() => undefined behavior if not successfully cted or was moved-from.");
364
365 bool not_blocked;
366 op_with_possible_bipc_mq_exception(err_code, "Bipc_mq_handle::try_receive(): bipc::message_queue::try_receive()",
367 [&]()
368 {
369 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
370 "max-size [" << blob->size() << "].");
371
372 size_t n_rcvd = {};
373 /* (^-- Initializer not needed algorithmically, but gcc-13 gives maybe-uninitialized warning;
374 * while at least various modern clangs and gcc-9 are fine. It's OK; we can afford it.) */
375 unsigned int pri_ignored;
376 not_blocked // Throws <=> error wrapper sets truthy *err_code. --v
377 = m_mq->try_receive(blob->data(), blob->size(), n_rcvd, pri_ignored);
378 if (not_blocked)
379 {
380 *blob = Blob_mutable{blob->data(), n_rcvd};
381 FLOW_LOG_TRACE("Received message sized [" << n_rcvd << "].");
382 if (blob->size() != 0)
383 {
384 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(*blob, " ") << "].");
385 }
386 }
387 else // if (!not_blocked)
388 {
389 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
390 "max-size [" << blob->size() << "]: would-block.");
391 }
392 }); // op_with_possible_bipc_mq_exception()
393 if (*err_code) // It logged if truthy.
394 {
395 return false; // not_blocked is garbage; *blob is untouched.
396 }
397 // else
398
399 return not_blocked; // Logged about it already.
400} // Bipc_mq_handle::try_receive()
401
403{
404 using flow::util::buffers_dump_string;
405 using util::Blob_mutable;
406
407 if (flow::error::exec_void_and_throw_on_error
408 ([&](Error_code* actual_err_code) { receive(blob, actual_err_code); },
409 err_code, "Bipc_mq_handle::receive()"))
410 {
411 return;
412 }
413 // else
414 err_code->clear();
415
416 assert(m_mq && "As advertised: receive() => undefined behavior if not successfully cted or was moved-from.");
417
418 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-pop to blob @[" << blob->data() << "], "
419 "max-size [" << blob->size() << "]. Trying nb-pop first; if it succeeds -- great. "
420 "Else will wait/retry/wait/retry/....");
421
422 /* We could just invoke blocking m_mq->receive(), but to get the promised logging we go a tiny bit fancier as follows.
423 * Update: Now that we have to be interrupt_*()ible, also reuse wait_*() instead of using native
424 * m_mq->*(). */
425
426 size_t n_rcvd = {}; // (Why initialize? See comment near first such initializer for explanation.)
427 unsigned int pri_ignored;
428 bool ok;
429
430 while (true)
431 {
432 op_with_possible_bipc_mq_exception(err_code, "Bipc_mq_handle::receive(): bipc::message_queue::try_receive()",
433 [&]()
434 {
435 ok = m_mq->try_receive(blob->data(), blob->size(), // Throws <=> error wrapper sets truthy *err_code.
436 n_rcvd, pri_ignored);
437 });
438 if (*err_code)
439 {
440 // Threw => true error => *err_code set; get out.
441 return;
442 }
443 // else if (would-block or success):
444 if (ok)
445 {
446 *blob = Blob_mutable{blob->data(), n_rcvd};
447 FLOW_LOG_TRACE("Received message sized [" << n_rcvd << "].");
448 if (blob->size() != 0)
449 {
450 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(*blob, " ") << "].");
451 }
452 return; // Instant success.
453 }
454 // else if (would-block): TRACE-logs.
455
456 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
457 "max-size [" << blob->size() << "]: would-block. Executing blocking-pop.");
458
459 wait_receivable(err_code);
460 if (*err_code)
461 {
462 // Whether interrupted or true error, we're done.
463 return;
464 }
465 // else
466 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
467 } // while (true)
468} // Bipc_mq_handle::receive()
469
471{
472 using util::Blob_mutable;
473 using flow::util::time_since_posix_epoch;
474 using flow::util::buffers_dump_string;
475 using flow::Fine_clock;
476 using boost::chrono::round;
477 using boost::chrono::microseconds;
478
479 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, timed_receive, blob, timeout_from_now, _1);
480 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
481
482 // Similar to a combo of (blocking) receive() and (non-blocking) try_receive() -- keeping comments light.
483
484 assert(m_mq && "As advertised: timed_receive() => undefined behavior if not successfully cted or was moved-from.");
485
486 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-timed-pop to blob @[" << blob->data() << "], "
487 "max-size [" << blob->size() << "]; timeout ~[" << round<microseconds>(timeout_from_now) << "]. "
488 "Trying nb-pop first; if it succeeds -- great. Else will wait/retry/wait/retry/....");
489
490 size_t n_rcvd = {}; // (Why initialize? See comment near first such initializer for explanation.)
491 unsigned int pri_ignored;
492
493 bool ok;
494 auto before = Fine_clock::now();
495
496 while (true)
497 {
499 "Bipc_mq_handle::timed_receive(): bipc::message_queue::try_receive()",
500 [&]()
501 {
502 ok = m_mq->try_receive(blob->data(), blob->size(), // Throws <=> error wrapper sets truthy *err_code.
503 n_rcvd, pri_ignored);
504 });
505 if (*err_code)
506 {
507 // Threw => true error => *err_code set; get out.
508 return false;
509 }
510 // else if (would-block or success):
511 if (ok)
512 {
513 *blob = Blob_mutable{blob->data(), n_rcvd};
514 FLOW_LOG_TRACE("Received message sized [" << n_rcvd << "].");
515 if (blob->size() != 0)
516 {
517 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(*blob, " ") << "].");
518 }
519 break; // Instant success.
520 }
521 // else if (would-block): TRACE-logs.
522
523 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
524 "max-size [" << blob->size() << "]: would-block. Executing blocking-wait.");
525
526 const bool ready = timed_wait_receivable(timeout_from_now, err_code);
527 if (*err_code)
528 {
529 // Whether interrupted or true error, we're done.
530 return false;
531 }
532 // else:
533
534 if (!ready) // I.e., if (timed out).
535 {
536 FLOW_LOG_TRACE("Did not finish before timeout.");
537 return false;
538 }
539 // else: successful wait for transmissibility. Try nb-transmitting again.
540 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
541
542 const auto after = Fine_clock::now();
543 timeout_from_now -= (after - before); // Reduce time left by the duration of the last attempt.
544 before = after;
545 } // while (true)
546
547 return true;
548} // Bipc_mq_handle::timed_receive()
549
550template<bool SND_ELSE_RCV, bool ON_ELSE_OFF>
552{
553 using Classic_shm_area = bipc::ipcdetail::managed_open_or_create_impl<bipc::shared_memory_object, 0, true, false>;
554 using Bipc_mq = bipc::message_queue;
555 using Bipc_mq_hdr = bipc::ipcdetail::mq_hdr_t<Bipc_mq::void_pointer>;
556 using Bipc_mq_mtx = bipc::interprocess_mutex;
557 using Bipc_mq_lock = bipc::scoped_lock<Bipc_mq_mtx>;
558
559 assert(m_mq
560 && "As advertised: interrupt_allow_impl() => undefined behavior if not successfully cted or was moved-from.");
561
562 /* First see m_interrupting_snd doc header. Then check out wait_impl() carefully. Then the below will
563 * probably make sense. */
564
565 bool* interrupting_ptr;
566 auto& shm_area = reinterpret_cast<Classic_shm_area&>(*m_mq);
567 auto* const mq_hdr = static_cast<Bipc_mq_hdr*>(shm_area.get_user_address());
568 decltype(mq_hdr->m_cond_recv)* cond_ptr;
569 if constexpr(SND_ELSE_RCV)
570 {
571 interrupting_ptr = &m_interrupting_snd;
572 cond_ptr = &mq_hdr->m_cond_send;
573 }
574 else
575 {
576 interrupting_ptr = &m_interrupting_rcv;
577 cond_ptr = &mq_hdr->m_cond_recv;
578 }
579 auto& interrupting = *interrupting_ptr;
580 auto& cond = *cond_ptr;
581
582 {
583 Bipc_mq_lock lock{mq_hdr->m_mutex};
584
585 if (interrupting == ON_ELSE_OFF)
586 {
587 FLOW_LOG_WARNING("Bipc_mq_handle [" << *this << "]: Interrupt mode already set for "
588 "snd_else_rcv [" << SND_ELSE_RCV << "], on_else_off [" << ON_ELSE_OFF << "]. Ignoring.");
589 return false;
590 }
591 // else
592
593 interrupting = ON_ELSE_OFF;
594 FLOW_LOG_INFO("Bipc_mq_handle [" << *this << "]: Interrupt mode set for "
595 "snd_else_rcv [" << SND_ELSE_RCV << "], on_else_off [" << ON_ELSE_OFF << "]. If on -- we "
596 "shall now ping the associated condition variable to wake up any ongoing waits.");
597
598 if constexpr(ON_ELSE_OFF)
599 {
600 /* *All* ongoing waits shall be woken up. Each one will auto-re-lock mq_hdr->m_mutex and query
601 * their local m_interrupting_*. (Note: If `*this` wakes up, it'll see it's `true`. If another
602 * one wakes up, it will probably see `false` and re-enter the wait. If by some coincidence that non-`*this`
603 * had just set *that* m_interrupting_*, then -- well, cool -- presumably we won a race against
604 * their own interrupt_allow_impl() doing the same thing.) */
605 cond.notify_all(); // By the docs, and even by the source code as of Boost-1.81, this does not throw.
606
607 /* (bipc::message_queue code does .notify_one() in send/receive impl, which makes sense since one
608 * message/one empty space would be used by at most one blocked dude. It also does it outside the mutex lock,
609 * citing performance at the cost of occasional spurious wakeups. Neither thing applies to us, so let's
610 * .notify_all() and keep it simple inside the locked section. */
611 } // if constexpr(ON_ELSE_OFF)
612 /* else if constexpr(!ON_ELSE_OFF)
613 * { We're good! No need to wake anyone up to tell them... not to stop. } */
614 } // Bipc_mq_lock lock(mq_hdr->m_mutex);
615
616 return true;
617} // Bipc_mq_handle::interrupt_allow_impl()
618
620{
621 return interrupt_allow_impl<true, true>();
622}
623
625{
626 return interrupt_allow_impl<true, false>();
627}
628
630{
631 return interrupt_allow_impl<false, true>();
632}
633
635{
636 return interrupt_allow_impl<false, false>();
637}
638
639template<Bipc_mq_handle::Wait_type WAIT_TYPE, bool SND_ELSE_RCV>
640bool Bipc_mq_handle::wait_impl([[maybe_unused]] util::Fine_duration timeout_from_now, Error_code* err_code)
641{
642 using util::Fine_time_pt;
643 using flow::util::time_since_posix_epoch;
644 using boost::chrono::round;
645 using boost::chrono::microseconds;
646 using Classic_shm_area = bipc::ipcdetail::managed_open_or_create_impl<bipc::shared_memory_object, 0, true, false>;
647 using Bipc_mq = bipc::message_queue;
648 using Bipc_mq_hdr = bipc::ipcdetail::mq_hdr_t<Bipc_mq::void_pointer>;
649 using Bipc_mq_mtx = bipc::interprocess_mutex;
650 using Bipc_mq_lock = bipc::scoped_lock<Bipc_mq_mtx>;
651
652 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, (wait_impl<WAIT_TYPE, SND_ELSE_RCV>), timeout_from_now, _1);
653 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
654
655 assert(m_mq
656 && "As advertised: wait_impl() => undefined behavior if not successfully cted or was moved-from.");
657
658 [[maybe_unused]] Fine_time_pt timeout_since_epoch;
659 if constexpr(WAIT_TYPE == Wait_type::S_TIMED_WAIT)
660 {
661 timeout_since_epoch = Fine_time_pt{time_since_posix_epoch() + timeout_from_now};
662 }
663
664 /* Most likely the below code will elicit a "WTF." The background is that bipc::message_queue lacks anything like
665 * wait_impl(). It seems to mimic the mq_*() API. However mq_*() does have it, in Linux, because mq_t is an FD,
666 * and it can be used with epoll_*(), so that is what Posix_mq_handle uses. To get the same thing here I had
667 * to hack it as seen below. What it does is it mimics *send() and *receive() internal Boost source code, but
668 * it stops short of actually performing the read or write once the queue becomes pushable/poppable.
669 * To get it working I (ygoldfel) operate directly on their internal data structures, same as those methods do.
670 * Normally this would be beyond the pale; however in this case a couple of points make it reasonable-enough.
671 *
672 * Firstly, we can even do it in the first place, because the data structures -- all of which are directly in
673 * SHM -- `public`ly expose their data members (though, interestingly, not the methods; however the methods
674 * are very basic wrappers around public things like condition variables and mutexes). This fact is suggestive
675 * of this being intentionally possible.
676 *
677 * Secondly, and this is the main reason I consider this reasonably maintainable, is the fact that the code
678 * itself -- including a comment above the BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX define -- says that
679 * their goal was to make version A of bipc interoperable (this is IPC after all) with version B>A of bipc.
680 * That means that if they *do* change this stuff, it will be protected by #define(s) like
681 * BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX, so as to make any change possible to roll-back via a compile
682 * flag. They *could* rename a data member without changing the physical structures, in which case this
683 * would stop building, but that seems unlikely, as Boost updates are not made willy-nilly.
684 *
685 * Is there risk of this breaking with a newer Boost version? Yes; but the above evidence shows the risk is
686 * manageably low. Note that this wait_impl() feature, particularly since I've made it interruptible, is quite
687 * useful. Without it one must used timed_*(), and even with that -- suppose we want to stop work on
688 * a queue from another thread -- we have to put up with a deinit time equal to the fine-grainedness of the timeout
689 * one would have to use. Plus even that aside, it is annoying to have to break up an operation into smaller
690 * ones. */
691
692 if constexpr(WAIT_TYPE == Wait_type::S_POLL)
693 {
694 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Poll-unstarved for snd_else_rcv [" << SND_ELSE_RCV << "].");
695 }
696 else if constexpr(WAIT_TYPE == Wait_type::S_TIMED_WAIT)
697 {
698 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-timed-await-unstarved for "
699 "snd_else_rcv [" << SND_ELSE_RCV << "]; "
700 "timeout ~[" << round<microseconds>(timeout_from_now) << "].");
701 }
702 else
703 {
704 static_assert(WAIT_TYPE == Wait_type::S_WAIT, "What!");
705 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-await-unstarved for snd_else_rcv "
706 "[" << SND_ELSE_RCV << "].");
707 }
708
709 auto& shm_area = reinterpret_cast<Classic_shm_area&>(*m_mq);
710 auto* const mq_hdr = static_cast<Bipc_mq_hdr*>(shm_area.get_user_address());
711 size_t* blocked_dudes_ptr;
712 bool* interrupting_ptr;
713 decltype(mq_hdr->m_cond_recv)* cond_ptr;
714 if constexpr(SND_ELSE_RCV)
715 {
716 blocked_dudes_ptr = &mq_hdr->m_blocked_senders;
717 cond_ptr = &mq_hdr->m_cond_send;
718 interrupting_ptr = &m_interrupting_snd;
719 }
720 else
721 {
722 blocked_dudes_ptr = &mq_hdr->m_blocked_receivers;
723 cond_ptr = &mq_hdr->m_cond_recv;
724 interrupting_ptr = &m_interrupting_rcv;
725 }
726 auto& blocked_dudes = *blocked_dudes_ptr;
727 auto& cond = *cond_ptr;
728 bool& interrupting = *interrupting_ptr;
729
730 const auto is_starved_func = [&]() -> bool
731 {
732 if constexpr(SND_ELSE_RCV)
733 {
734 return mq_hdr->m_cur_num_msg == mq_hdr->m_max_num_msg;
735 }
736 else
737 {
738 return mq_hdr->m_cur_num_msg == 0;
739 }
740 }; // const auto is_starved_func =
741
742 bool interrupted = false;
743 bool not_starved;
744 /* Technically [timed_]wait() can throw, I guess if bool(mutex) is false. Their internal code has a try{}
745 * so why not. */
747 "Bipc_mq_handle::wait_impl(): "
748 "bipc::interprocess_condition::[timed_]wait()",
749 [&]()
750 {
751#if (BOOST_VERSION < 109100) && !defined(BOOST_INTERPROCESS_MSG_QUEUE_CIRCULAR_INDEX)
752 /* Through Boost 1.90 bipc offers 2 MQ algorithms; the macro selects the circular-index one, which bipc
753 * comments show is the default as of many Boost versions ago, unless unset which would decrease performance;
754 * and we do not do that. Our code for simplicity assumes it and does not support the lower-perf algorithm.
755 * As of Boost 1.91 the macro is gone: the circular-index algorithm is the only one (mq_hdr_t layout unchanged,
756 * m_cur_first_msg included); so there is nothing to check. */
757 static_assert(false,
758 "bipc comments show this shall be true as of many Boost versions ago, unless unset which "
759 "would decrease performance; and we do not do that. Our code for simplicity assumes "
760 "it and does not support the lower-perf bipc MQ algorithm.");
761#endif
762
763 {
764 Bipc_mq_lock lock{mq_hdr->m_mutex};
765
766 // See interrupt_allow_impl() to understand this check (also below on cond.*wait()).
767 if (interrupting)
768 {
769 FLOW_LOG_TRACE("Interrupted before wait/poll started (preemptively).");
770 interrupted = true;
771 return;
772 }
773 // else
774
775 if (is_starved_func())
776 {
777 // timed_receive() would INFO-log here, but let's not slow things down while the bipc MQ mutex is locked.
778
779 if constexpr(WAIT_TYPE == Wait_type::S_POLL)
780 {
781 FLOW_LOG_TRACE("Not immediately unstarved. Poll = done.");
782 not_starved = false;
783 return;
784 }
785 else // if constexpr(WAIT_TYPE == Wait_type::S_[TIMED_]WAIT)
786 {
787 FLOW_LOG_TRACE("Not immediately unstarved. Awaiting unstarvedness or timeout.");
788
789 ++blocked_dudes;
790 try
791 {
792 do
793 {
794 if constexpr(WAIT_TYPE == Wait_type::S_WAIT)
795 {
796 cond.wait(lock);
797
798 // See interrupt_allow_impl() to understand this check (also above).
799 if (interrupting)
800 {
801 FLOW_LOG_TRACE("Interruption detected upon waking up from wait (interrupted concurrently).");
802 --blocked_dudes;
803 interrupted = true;
804 return;
805 }
806 // else: Another loop iteration.
807 }
808 else // if (WAIT_TYPE==TIMED_WAIT)
809 {
810 static_assert(WAIT_TYPE == Wait_type::S_TIMED_WAIT, "The hell?");
811
812 const bool wait_result = cond.timed_wait(lock, timeout_since_epoch); // Lock unlocked throughout wait.
813
814 // See interrupt_allow_impl() to understand this check (also above).
815 if (interrupting)
816 {
817 FLOW_LOG_TRACE("Interruption detected upon waking up from wait (interrupted concurrently).");
818 --blocked_dudes;
819 interrupted = true;
820 return;
821 }
822 // else
823
824 if (!wait_result)
825 {
826 // Timeout reached.
827 if (is_starved_func())
828 {
829 // Timeout reached; still starved. Done (exit algo).
830 --blocked_dudes;
831 not_starved = false;
832 return;
833 }
834 // else: Timeout reached; *is* unstarved. Done (exit loop -- no need to recheck is_starved_func()).
835 break;
836 } // if (!wait_result)
837 // else: Timeout not reached; probably *is* unstarved; but check it as loop exit condition.
838 } // else if (WAIT_TYPE==TIMED_WAIT)
839 }
840 while (is_starved_func());
841 } // try
842 catch (...)
843 {
844 --blocked_dudes;
845 throw;
846 }
847 --blocked_dudes;
848 } // else if constexpr(WAIT_TYPE == Wait_type::S_[TIMED_]WAIT)
849
850 // Will INFO-log shortly (outside lock).
851 not_starved = true;
852 } // if (is_starved_func())
853 else
854 {
855 FLOW_LOG_TRACE("Immediately unstarved.");
856 not_starved = true;
857 }
858 } // Bipc_mq_lock lock{mq_hdr->m_mutex};
859 }); // op_with_possible_bipc_mq_exception()
860
861 if ((!*err_code) && interrupted)
862 {
863 FLOW_LOG_INFO("Bipc_mq_handle [" << *this << "]: Poll/wait/timed-wait-unstarved for "
864 "snd_else_rcv [" << SND_ELSE_RCV << "]: interrupted (TRACE message -- if visible -- "
865 "indicates whether preemptively or concurrently).");
866 *err_code = error::Code::S_INTERRUPTED;
867 }
868
869 if (*err_code) // It logged if truthy.
870 {
871 return false; // not_starved is garbage.
872 }
873 // else: as promised, INFOx1 log.
874
875 if constexpr(WAIT_TYPE == Wait_type::S_POLL)
876 {
877 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Poll-unstarved for snd_else_rcv [" << SND_ELSE_RCV << "]: "
878 "succeeded? = [" << not_starved << "].");
879 }
880 else if constexpr(WAIT_TYPE == Wait_type::S_TIMED_WAIT)
881 {
882 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-timed-await-unstarved for "
883 "snd_else_rcv [" << SND_ELSE_RCV << "]; timeout ~[" << round<microseconds>(timeout_from_now) << "]: "
884 "succeeded? = [" << not_starved << "]. "
885 "Either was immediately unstarved, or was not but waited until success or timeout+failure. "
886 "If success: TRACE message (if visible) above indicates which occurred.");
887 }
888 else
889 {
890 assert(not_starved);
891 FLOW_LOG_TRACE("Bipc_mq_handle [" << *this << "]: Blocking-await-unstarved for "
892 "snd_else_rcv [" << SND_ELSE_RCV << "]: succeeded eventually. "
893 "Either was immediately unstarved, or was not but waited it out. "
894 "TRACE message (if visible) above indicates which occurred.");
895 }
896
897 return not_starved; // Logged about it already.
898} // Bipc_mq_handle::wait_impl()
899
901{
902 return wait_impl<Wait_type::S_POLL, true>(util::Fine_duration{}, err_code);
903}
904
906{
907 wait_impl<Wait_type::S_WAIT, true>(util::Fine_duration{}, err_code);
908}
909
911{
912 return wait_impl<Wait_type::S_TIMED_WAIT, true>(timeout_from_now, err_code);
913}
914
916{
917 return wait_impl<Wait_type::S_POLL, false>(util::Fine_duration{}, err_code);
918}
919
921{
922 wait_impl<Wait_type::S_WAIT, false>(util::Fine_duration{}, err_code);
923}
924
926{
927 return wait_impl<Wait_type::S_TIMED_WAIT, false>(timeout_from_now, err_code);
928}
929
930void Bipc_mq_handle::remove_persistent(flow::log::Logger* logger_ptr, // Static.
931 const Shared_name& absolute_name, Error_code* err_code)
932{
933 using bipc::message_queue;
934 using boost::system::system_category;
935
936 if (flow::error::exec_void_and_throw_on_error
937 ([&](Error_code* actual_err_code)
938 { remove_persistent(logger_ptr, absolute_name, actual_err_code); },
939 err_code, "Bipc_mq_handle::remove_persistent()"))
940 {
941 return;
942 }
943 // else
944
945 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
946
947 FLOW_LOG_INFO("Bipc_mq @ Shared_name[" << absolute_name << "]: Removing persistent MQ if possible.");
948 const bool ok = message_queue::remove(absolute_name.native_str()); // Does not throw.
949
950 if (ok)
951 {
952 err_code->clear();
953 return;
954 }
955 /* message_queue::remove() is strangely gimped -- though I believe so are the other kernel-persistent remove()s
956 * throughout bipc -- it does not throw and merely returns true or false and no code. Odd, since there can
957 * be at least a couple of reasons one would fail to delete.... However, in POSIX, the Boost 1.78 source code
958 * shows that mq::remove() calls shared_memory_object::remove() which calls some internal
959 * ipcdetail::delete_file() which calls... drumroll... freakin' ::unlink(const char*). Hence we haxor: */
960#ifndef FLOW_OS_LINUX // @todo Should maybe check Boost version or something too?
961 static_assert(false,
962 "Code in Bipc_mq_handle::remove_persistent() relies on Boost invoking Linux unlink() with errno.");
963#endif
964 const auto& sys_err_code = *err_code = {errno, system_category()};
965 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
966} // Bipc_mq_handle::remove_persistent()
967
968template<typename Func>
970 const Func& func)
971{
972 using flow::error::Runtime_error;
973 using bipc::interprocess_exception;
974 using boost::system::system_category;
975
976 if (flow::error::exec_void_and_throw_on_error
977 ([&](Error_code* actual_err_code) { op_with_possible_bipc_mq_exception(actual_err_code, context, func); },
978 err_code, context))
979 {
980 return;
981 }
982 // else
983
984 try
985 {
986 func();
987 }
988 catch (const interprocess_exception& exc)
989 {
990 /* They appear to always throw this guy with some interesting semantics. We want to yield our consistent
991 * Flow-style semantics, namely produce a truthy *err_code (which, if actual original err_code was null will
992 * be instead thrown after being wrapped in a flow::Runtime_error, which is really a boost::system::system_error
993 * with a cleaner what() message). More details inline below.
994 *
995 * So interprocess_exception is not a system_error even but rather an exception with a particular custom API.
996 * To ensure the information is logged *somewhere* for sure and not lost do log all of this.
997 * After that normalize to the *err_code semantics, even if some information is lost -- though we'll try
998 * to lose nothing even so. */
999 const auto native_code_raw = exc.get_native_error();
1000 const auto bipc_err_code_enum = exc.get_error_code();
1001 const bool is_size_error = bipc_err_code_enum == bipc::size_error;
1002 FLOW_LOG_WARNING("bipc threw interprocess_exception; will emit some hopefully suitable Flow-IPC Error_code; "
1003 "but here are all the details of the original exception: native code int "
1004 "[" << native_code_raw << "]; bipc error_code_t enum->int "
1005 "[" << int(bipc_err_code_enum) << "]; latter==size_error? = [" << is_size_error << "]; "
1006 "message = [" << exc.what() << "]; context = [" << context << "].");
1007
1008 /* Special case: size_error is thrown by message_queue on a receive with underflow or send with overflow.
1009 * Our API contract (Persistent_mq_handle concept doc header) is to emit this particular code then. */
1010 if (is_size_error)
1011 {
1012 // Sufficient. (Check of Boost 1.78 source code confirms there's no native code in this case anyway.)
1014 return;
1015 }
1016 // else
1017 if (native_code_raw != 0)
1018 {
1019 /* At least in POSIX, interprocess_exception only does the following in this case:
1020 * - strerror(native_code_raw) => the message. But that's standard boost.system errno handling already;
1021 * so if we just emit a system_category() Error_code, all will be equally well message-wise in what().
1022 * - They have a table that maps certain native_code_raw values to one of a few (not super-many but not
1023 * a handful) bipc_err_code_enum enum values. Technically the following will lose that information;
1024 * but (1) it is logged above; and (2) so what? */
1025 const auto& sys_err_code = *err_code = {native_code_raw, system_category()};
1026 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
1027 return;
1028 }
1029 // else
1030
1031 /* All we have is bipc_err_code_enum. We've already handled the known likely issue, size_error. Beyond that
1032 * there does not seem to be much we can do; really it's a matter of either emitting one catch-all code or
1033 * having our own equivalents of their enum. For now, at least, the former is OK. @todo Revisit. */
1034 *err_code = error::Code::S_MQ_BIPC_MISC_LIBRARY_ERROR; // The earlier WARNING is good enough.
1035 return;
1036 } // catch ()
1037 // Got here: all good.
1038 err_code->clear();
1039} // Bipc_mq_handle::op_with_possible_bipc_mq_exception()
1040
1042{
1043 return m_absolute_name;
1044}
1045
1046std::ostream& operator<<(std::ostream& os, const Bipc_mq_handle& val)
1047{
1048 return os << '@' << &val << ": sh_name[" << val.absolute_name() << ']';
1049}
1050
1051} // namespace ipc::transport
Implements the Persistent_mq_handle concept by thinly wrapping bipc::message_queue,...
Bipc_mq_handle()
Implements Persistent_mq_handle API: Construct null handle.
bool allow_receives()
Implements Persistent_mq_handle API: Turn off preemptive/concurrent interruption of blocking-receives...
boost::movelib::unique_ptr< bipc::message_queue > m_mq
Underlying MQ handle.
void receive(util::Blob_mutable *blob, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Blocking receive: pops copy of message from queue into buffer; i...
Bipc_mq_handle & operator=(Bipc_mq_handle &&src)
Implements Persistent_mq_handle API: Replaces handle with the source handle while making the latter i...
bool is_sendable(Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like try_send() but without the actual pushing of a message.
bool timed_send(const util::Blob_const &blob, util::Fine_duration timeout_from_now, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Blocking timed send: pushes copy of message to queue; if queue i...
static void remove_persistent(flow::log::Logger *logger_ptr, const Shared_name &absolute_name, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Removes the named persistent MQ.
bool m_interrupting_rcv
Other-direction counterpart to m_interrupting_snd.
bool interrupt_sends()
Implements Persistent_mq_handle API: Turn on preemptive/concurrent interruption of blocking-sends and...
bool is_receivable(Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like try_receive() but without the actual popping of a message.
bool timed_wait_receivable(util::Fine_duration timeout_from_now, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like timed_receive() but without the actual popping of a message...
const Shared_name & absolute_name() const
Implements Persistent_mq_handle API: Returns name equal to absolute_name passed to ctor.
static const Shared_name S_RESOURCE_TYPE_ID
Implements concept API.
bool allow_sends()
Implements Persistent_mq_handle API: Turn off preemptive/concurrent interruption of blocking-sends an...
@ S_TIMED_WAIT
Timed-wait-type (blocking until timeout).
@ S_WAIT
Wait-type (blocking indefinitely).
@ S_POLL
Poll-type (non-blocking).
bool interrupt_receives()
Implements Persistent_mq_handle API: Turn on preemptive/concurrent interruption of blocking-receives ...
bool m_interrupting_snd
Starting at false, this is made true via interrupt_sends(), and back by allow_sends(); when true wait...
bool timed_wait_sendable(util::Fine_duration timeout_from_now, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like timed_send() but without the actual pushing of a message.
void op_with_possible_bipc_mq_exception(Error_code *err_code, util::String_view context, const Func &func)
Error helper: Run func() which will perform a bipc::message_queue API that might throw; if it does em...
size_t max_n_msgs() const
Implements Persistent_mq_handle API: Returns the max message count of the underlying queue.
void send(const util::Blob_const &blob, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Blocking send: pushes copy of message to queue; if queue is full...
bool try_send(const util::Blob_const &blob, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Non-blocking send: pushes copy of message to queue and returns t...
void wait_receivable(Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like receive() but without the actual popping of a message.
~Bipc_mq_handle()
Implements Persistent_mq_handle API: Destroys this handle (or no-op if no handle was successfully con...
bool interrupt_allow_impl()
Impl body for interrupt_*() and allow_*().
bool timed_receive(util::Blob_mutable *blob, util::Fine_duration timeout_from_now, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Blocking timed receive: pops copy of message from queue into buf...
size_t max_msg_size() const
Implements Persistent_mq_handle API: Returns the max message size of the underlying queue.
bool wait_impl(util::Fine_duration timeout_from_now, Error_code *err_code)
Impl body for *_sendable() and *_receivable().
Shared_name m_absolute_name
See absolute_name().
void wait_sendable(Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like send() but without the actual pushing of a message.
bool try_receive(util::Blob_mutable *blob, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Non-blocking receive: pops copy of message from queue into buffe...
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
const char * native_str() const
Returns (sans copying) pointer to NUL-terminated wrapped name string, suitable to pass into sys calls...
Definition: shared_name.cpp:94
static Shared_name ct(const Source &src)
Copy-constructs from a char-sequence container such as string or util::String_view.
@ S_INTERRUPTED
A blocking operation was intentionally interrupted or preemptively canceled.
@ S_MQ_BIPC_MISC_LIBRARY_ERROR
Low-level message queue: boost.interprocess emitted miscellaneous library exception sans a system cod...
@ S_MQ_MESSAGE_SIZE_OVER_OR_UNDERFLOW
Low-level message queue send-op buffer overflow (> max size) or receive-op buffer underflow (< max si...
Flow-IPC module providing transmission of structured messages and/or low-level blobs (and more) betwe...
std::ostream & operator<<(std::ostream &os, const Generic_msg_batch_in< Msg_resource_t, NO_HNDLS > &val)
Prints string representation of the given Generic_msg_batch_in to the given ostream.
Definition: batch.hpp:431
util::Shared_name Shared_name
Convenience alias for the commonly used type util::Shared_name.
void swap(Bipc_mq_handle &val1, Bipc_mq_handle &val2)
Implements Persistent_mq_handle related concept: Swaps two objects.
bipc::permissions Permissions
Short-hand for Unix (POSIX) permissions class.
Definition: util_fwd.hpp:158
const uint8_t * blob_data(const Blob_const &blob)
Syntactic-sugary helper that returns pointer to first byte in an immutable buffer,...
Definition: util.cpp:181
bipc::open_only_t Open_only
Tag type indicating an ideally-atomic open-if-exists-else-fail operation.
Definition: util_fwd.hpp:152
const Open_or_create OPEN_OR_CREATE
Tag value indicating an open-if-exists-else-create operation.
Definition: util.cpp:36
const Open_only OPEN_ONLY
Tag value indicating an atomic open-if-exists-else-fail operation.
Definition: util.cpp:37
boost::asio::mutable_buffer Blob_mutable
Short-hand for a mutable blob somewhere in memory, stored as exactly a void* and a size_t.
Definition: util_fwd.hpp:137
bipc::open_or_create_t Open_or_create
Tag type indicating an atomic open-if-exists-else-create operation.
Definition: util_fwd.hpp:149
bipc::create_only_t Create_only
Tag type indicating a create-unless-exists-else-fail operation.
Definition: util_fwd.hpp:155
flow::Fine_time_pt Fine_time_pt
Short-hand for Flow's Fine_time_pt.
Definition: util_fwd.hpp:116
flow::Fine_duration Fine_duration
Short-hand for Flow's Fine_duration.
Definition: util_fwd.hpp:114
const Create_only CREATE_ONLY
Tag value indicating an atomic create-unless-exists-else-fail operation.
Definition: util.cpp:38
boost::asio::const_buffer Blob_const
Short-hand for an immutable blob somewhere in memory, stored as exactly a void const * and a size_t.
Definition: util_fwd.hpp:131
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