Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
posix_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/util/util.hpp>
24#include <boost/chrono/floor.hpp>
25#include <boost/array.hpp>
26#include <boost/asio/connect_pipe.hpp>
27#include <boost/io/ios_state.hpp>
28#include <cassert>
29#include <cerrno>
30#include <cstdlib>
31#include <ostream>
32#include <string>
33#include <type_traits>
34#include <utility>
35
36namespace ipc::transport
37{
38
39namespace
40{
41
42/**
43 * File-local helper: takes Shared_name; returns name suitable for `mq_open()` and buddies.
44 * @param name
45 * Shared_name.
46 * @return See above.
47 */
48std::string shared_name_to_mq_name(const Shared_name& name);
49
50} // namespace (anon)
51
52// Initializers.
53
55
56// Implementations.
57
59 m_interrupting_snd(false), // (Not algorithmically needed as of this writing, but avoid some sanitizer complaints.)
60 m_interrupting_rcv(false), // (Ditto. E.g., swap() through clang's UBSAN => complains it's uninitialized.)
61 m_interrupter_snd(m_nb_task_engine),
62 m_interrupt_detector_snd(m_nb_task_engine),
63 m_interrupter_rcv(m_nb_task_engine),
64 m_interrupt_detector_rcv(m_nb_task_engine)
65{
66 // Done.
67}
68
69template<typename Mode_tag>
70Posix_mq_handle::Posix_mq_handle(Mode_tag, flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
71 size_t max_n_msg, size_t max_msg_sz,
72 const util::Permissions& perms, Error_code* err_code) :
73 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT),
74 m_absolute_name(absolute_name_arg),
75 m_interrupting_snd(false),
76 m_interrupting_rcv(false),
77 m_interrupter_snd(m_nb_task_engine),
78 m_interrupt_detector_snd(m_nb_task_engine),
79 m_interrupter_rcv(m_nb_task_engine),
80 m_interrupt_detector_rcv(m_nb_task_engine)
81{
83 using flow::error::Runtime_error;
84 using flow::log::Sev;
85 using boost::io::ios_all_saver;
86 using boost::system::system_category;
87 using ::mq_open;
88 using ::mq_close;
89 using ::mq_unlink;
90 using ::mq_attr;
91 using ::close;
92 // using ::O_RDWR; // A macro apparently.
93 // using ::O_CREAT; // A macro apparently.
94 // using ::O_EXCL; // A macro apparently.
95
96 assert(max_n_msg >= 1);
97 assert(max_msg_sz >= 1);
98
99 static_assert(std::is_same_v<Mode_tag, util::Create_only> || std::is_same_v<Mode_tag, util::Open_or_create>,
100 "Can only delegate to this ctor with Mode_tag = Create_only or Open_or_create.");
101 constexpr bool CREATE_ONLY_ELSE_MAYBE = std::is_same_v<Mode_tag, util::Create_only>;
102 constexpr char const * MODE_STR = CREATE_ONLY_ELSE_MAYBE ? "create-only" : "open-or-create";
103
104 if (logger_ptr && logger_ptr->should_log(Sev::S_TRACE, get_log_component()))
105 {
106 ios_all_saver saver{*(logger_ptr->this_thread_ostream())}; // Revert std::oct/etc. soon.
107
108 FLOW_LOG_TRACE_WITHOUT_CHECKING
109 ("Posix_mq_handle [" << *this << "]: Constructing MQ handle to MQ at name [" << absolute_name() << "] in "
110 "[" << MODE_STR << "] mode; max msg size [" << max_msg_sz << "] x [" << max_n_msg << "] msgs; "
111 "perms = [" << std::setfill('0') << std::setw(4) << std::oct << perms.get_permissions() << "].");
112 }
113
114 const auto mq_name = shared_name_to_mq_name(absolute_name());
115 bool created = false; // If we end up failing overall, and this is true, we shall unlink the MQ we created.
116 /* Whether we end up failing overall = sys_err_code falsiness. The fail-points are all determined
117 * by sys_err_code, and some logging + possible throwing at the end will use its value too. */
118 Error_code sys_err_code;
119
120 ([&]() // Error? Just return; we will cleanup before ctor exits.
121 {
122 /* Naively: Use mq_open() to both create-or-open/exclusively-create and to set mode `perms`.
123 * In reality though there are two subtleties that make it tougher than that. Fortunately this has been
124 * adjudicated out there, and internal boost.interprocess code very nicely confirms that adjudication/sets
125 * a sufficiently-authoritative precedent.
126 *
127 * Subtlety 1 is that mq_open(), like all POSIX/Linux ::open()-y calls, doesn't listen to `perms` verbatim
128 * but rather makes it subject to the process umask. The proper way (citation omitted) to deal with it is
129 * to follow it up with ::[f]chmod() or equivalent, with the resource descriptor if available (or path if not).
130 * (We do have the descriptor -- m_mq -- and we have a util::set_resource_permissions() overload for this.)
131 *
132 * Subtlety 2 is that we should only do that last thing if, indeed, we are *creating* the resource (MQ);
133 * if CREATE_ONLY_ELSE_MAYBE==false, and it already exists, then we mustn't do any such thing; we've succeeded.
134 * Sadly there's no (atomic/direct) way to find out whether mq_open() (in ==false case) did in fact create
135 * the thing. Therefore, we have to do things in a kludgy way -- that is, nevertheless, reasonable in practice
136 * and indeed is validated by bipc's use of it internally (search headers for `case ipcdetail::DoOpenOrCreate:`
137 * followed by shm_open(), fchmod(), etc.). We explain it here for peace of mind.
138 *
139 * In the CREATE_ONLY_ELSE_MAYBE=true case, we can just do what we'd do anyway -- shm_open() in create-only
140 * mode; then set_resource_permissions(); and that's that. Otherwise:
141 *
142 * We can no longer use the built-in atomic create-or-open mode (O_CREAT sans O_EXCL). Instead, we logically
143 * split it into two parts: try to create-only (O_CREAT|O_EXCL); if it succeeds, done (can
144 * set_resource_permissions()); if it fails with file-exists error, then mq_open() again, this time in open-only
145 * mode (and no need for set_resource_permissions()). Great! The problem, of course, is that this no longer uses a
146 * guaranteed-atomic open-or-create OS-call semantic. Namely it means set_resource_permissions() could fail due to
147 * file-not-found, because during the non-atomic gap the MQ got removed by someone. Then we spin-lock (in a way)
148 * through it: just try the whole procedure again. Sooner or later -- assuming no other errors of course --
149 * either the first mq_open() will succeed, or it'll "fail" with file-exists, yet the 2nd mq_open() will succeed.
150 * Pegging processor is not a serious concern in this context. */
151
152 // Get pipe stuff out of the way, as it does not need m_mq.
153 if ((sys_err_code = pipe_setup()))
154 {
155 return;
156 }
157 // else
158
159 // Setup m_mq. (Open handle, which may or may not create name in file system; in latter case set perms as well.)
160
161 const auto do_mq_open_func = [&](bool create_else_open)
162 {
163 /* Note: O_NONBLOCK is something we are forced to set/unset depending on the transmission API being called.
164 * So just don't worry about it here. */
165 ::mqd_t raw;
166 if (create_else_open)
167 {
168 mq_attr attr;
169 attr.mq_maxmsg = max_n_msg;
170 attr.mq_msgsize = max_msg_sz;
171
172 raw = mq_open(mq_name.c_str(),
173 O_RDWR | O_CREAT | O_EXCL, // Create-only always: per above. Can't use create-or-open.
174 perms.get_permissions(),
175 &attr);
176 }
177 else
178 {
179 raw = mq_open(mq_name.c_str(), O_RDWR);
180 }
181 if (raw != -1)
182 {
183 sys_err_code.clear();
184 m_mq = Native_handle{raw};
185 create_else_open && (created = true);
186 }
187 else
188 {
189 sys_err_code = {errno, system_category()};
190 }
191 }; // do_mq_open_func =
192
193 if (CREATE_ONLY_ELSE_MAYBE)
194 {
195 // Simple case.
196
197 if ((do_mq_open_func(true), sys_err_code)
198 || (set_resource_permissions(get_logger(), m_mq, perms, &sys_err_code),
199 sys_err_code))
200 {
201 return;
202 }
203 }
204 else // if (!CREATE_ONLY_ELSE_MAYBE)
205 {
206 // Complicated case.
207
208 bool success = false; // While this remains false, sys_err_code indicates whether it's false b/c of fatal error.
209 do
210 {
211 if (do_mq_open_func(true), !sys_err_code)
212 {
213 // Created! Only situation where we must indeed set_resource_permissions().
214 if (set_resource_permissions(get_logger(), m_mq, perms, &sys_err_code),
215 sys_err_code)
216 {
217 return; // Real error. GTFO.
218 }
219 // else
220
221 success = true;
222 continue; // `break`, really.
223 }
224 // else if (do_mq_open_func() failed):
225 if (sys_err_code != boost::system::errc::file_exists)
226 {
227 return; // Real error. GTFO.
228 }
229 // else if (file_exists): Create failed, because it already exists. Open the existing guy!
230
231 if (do_mq_open_func(false), !sys_err_code)
232 {
233 success = true; // Opened!
234 continue; // `break`, really.
235 }
236 // else if (do_mq_open_func() failed):
237 if (sys_err_code != boost::system::errc::no_such_file_or_directory)
238 {
239 return; // Real error. GTFO.
240 }
241 // else if (no_such_file_or_directory): Open failed, because MQ *just* got unlinked. Try again!
242
243 // This is fun and rare enough to warrant an INFO message.
244 if (logger_ptr && logger_ptr->should_log(Sev::S_INFO, get_log_component()))
245 {
246 ios_all_saver saver{*(logger_ptr->this_thread_ostream())}; // Revert std::oct/etc. soon.
247 FLOW_LOG_INFO_WITHOUT_CHECKING
248 ("Posix_mq_handle [" << *this << "]: Create-or-open algorithm encountered the rare concurrency: "
249 "MQ at name [" << absolute_name() << "] existed during the create-only mq_open() but disappeared "
250 "before we were able to complete open-only mq_open(). Retrying in spin-lock fashion. "
251 "Details: max msg size [" << max_msg_sz << "] x [" << max_n_msg << "] msgs; "
252 "perms = [" << std::setfill('0') << std::setw(4) << std::oct << perms.get_permissions() << "].");
253 }
254
255 // success remains false, and that wasn't a fatal error; so we did not return, and loop continues.
256 }
257 while (!success);
258
259 // Now fall through.
260 } // else if (!CREATE_ONLY_ELSE_MAYBE)
261
262 // We never use blocking transmission -- always using *wait_*able() so that interrupt_*() works. Non-blocking 4eva.
263 if (!set_non_blocking(true, &sys_err_code)) // (On error it'd print Error_code redundantly. It's fine.)
264 {
265 assert(sys_err_code);
266 return;
267 }
268 // else
269
270 sys_err_code = epoll_setup(); // Lastly setup the watcher mechanism.
271 })(); // The returning-on-error gauntlet.
272
273 if (sys_err_code)
274 {
275 Error_code sink;
276
277 m_interrupt_detector_rcv.close(sink);
278 m_interrupter_rcv.close(sink);
279 m_interrupt_detector_snd.close(sink);
280 m_interrupter_snd.close(sink);
281 /* (As of this writing could use m_epoll_hndl_snd.close() to replace prev 3 lines; also below and in several
282 * places in this class, where ::close() is used. Since the whole class is based on the epoll() API, though,
283 * it seemed more honest and even, perversely, maintainable to use the proper native API for
284 * epoll() FD-closing). */
287 mq_close(m_mq.m_native_handle); m_mq = {};
288
289 if (created)
290 {
291 /* Roll back the creation: else a healthy-looking -- but, e.g., possibly wrongly-permissioned -- MQ would
292 * remain at `absolute_name` despite our reporting failure. Best-effort: disregard any error. */
293 mq_unlink(mq_name.c_str());
294 }
295
296 if (logger_ptr && logger_ptr->should_log(Sev::S_WARNING, get_log_component()))
297 {
298 ios_all_saver saver{*(logger_ptr->this_thread_ostream())}; // Revert std::oct/etc. soon.
299
300 FLOW_LOG_WARNING_WITHOUT_CHECKING
301 ("Posix_mq_handle [" << *this << "]: Setup error (additional details may be above) while "
302 "constructing MQ handle to MQ at name [" << absolute_name() << "] in "
303 "[" << MODE_STR << "] mode; max msg size [" << max_msg_sz << "] x [" << max_n_msg << "] msgs; "
304 "perms = [" << std::setfill('0') << std::setw(4) << std::oct << perms.get_permissions() << "].");
305 }
306 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
307
308 if (err_code)
309 {
310 *err_code = sys_err_code;
311 }
312 else
313 {
314 throw Runtime_error{sys_err_code, FLOW_UTIL_WHERE_AM_I_STR()};
315 }
316 } // if (sys_err_code)
317 else if (err_code)
318 {
319 *err_code = sys_err_code; // Cool!
320 }
321 // else { Cool! Just do not throw. }
322} // Posix_mq_handle::Posix_mq_handle(Mode_tag)
323
324Posix_mq_handle::Posix_mq_handle(flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
325 util::Create_only, size_t max_n_msg, size_t max_msg_sz,
326 const util::Permissions& perms, Error_code* err_code) :
327 Posix_mq_handle(util::CREATE_ONLY, logger_ptr, absolute_name_arg, max_n_msg, max_msg_sz, perms, err_code)
328{
329 // Cool.
330}
331
332Posix_mq_handle::Posix_mq_handle(flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
333 util::Open_or_create, size_t max_n_msg, size_t max_msg_sz,
334 const util::Permissions& perms, Error_code* err_code) :
335 Posix_mq_handle(util::OPEN_OR_CREATE, logger_ptr, absolute_name_arg, max_n_msg, max_msg_sz, perms, err_code)
336{
337 // Cool.
338}
339
340Posix_mq_handle::Posix_mq_handle(flow::log::Logger* logger_ptr, const Shared_name& absolute_name_arg,
341 util::Open_only, Error_code* err_code) :
342 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT),
343 m_absolute_name(absolute_name_arg),
344 m_interrupting_snd(false),
345 m_interrupting_rcv(false),
346 m_interrupter_snd(m_nb_task_engine),
347 m_interrupt_detector_snd(m_nb_task_engine),
348 m_interrupter_rcv(m_nb_task_engine),
349 m_interrupt_detector_rcv(m_nb_task_engine)
350{
351 using flow::log::Sev;
352 using flow::error::Runtime_error;
353 using boost::system::system_category;
354 using ::mq_open;
355 using ::mq_close;
356 using ::close;
357 // using ::O_RDWR; // A macro apparently.
358
359 FLOW_LOG_TRACE
360 ("Posix_mq_handle [" << *this << "]: Constructing MQ handle to MQ at name [" << absolute_name() << "] in "
361 "open-only mode.");
362
363 // Similar setup to the Mode_tag ctor; just somewhat simpler (no `created` possibility; less stuff to log).
364 Error_code sys_err_code;
365
366 ([&]()
367 {
368 // Get pipe stuff out of the way, as it does not need m_mq.
369 if ((sys_err_code = pipe_setup()))
370 {
371 return;
372 }
373 // else
374
375 /* Note: O_NONBLOCK is something we are forced to set/unset depending on the transmission API being called.
376 * So just don't worry about it here. */
377 const auto raw = mq_open(shared_name_to_mq_name(absolute_name()).c_str(), O_RDWR);
378 if (raw == -1)
379 {
380 sys_err_code = {errno, system_category()};
381 return;
382 }
383 // else
384
385 m_mq = Native_handle{raw};
386
387 // We never use blocking transmission -- always using *wait_*able() => interrupt_*() works. Non-blocking 4eva.
388 if (!set_non_blocking(true, &sys_err_code)) // (On error it'd print Error_code redundantly. It's fine.)
389 {
390 assert(sys_err_code);
391 return;
392 }
393 // else
394
395 sys_err_code = epoll_setup(); // Lastly setup the watcher mechanism.
396 })();
397
398 if (sys_err_code)
399 {
400 Error_code sink;
401
402 m_interrupt_detector_rcv.close(sink); // @todo Code reuse with other cleanup sequences.
403 m_interrupter_rcv.close(sink);
404 m_interrupt_detector_snd.close(sink);
405 m_interrupter_snd.close(sink);
408 mq_close(m_mq.m_native_handle); m_mq = {};
409
410 FLOW_LOG_WARNING
411 ("Posix_mq_handle [" << *this << "]: Setup error (additional details may be above) while "
412 "constructing MQ handle to MQ at name [" << absolute_name() << "] in open-only mode.");
413 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
414
415 if (err_code)
416 {
417 *err_code = sys_err_code;
418 }
419 else
420 {
421 throw Runtime_error{sys_err_code, FLOW_UTIL_WHERE_AM_I_STR()};
422 }
423 }
424 else if (err_code)
425 {
426 *err_code = sys_err_code; // Cool!
427 }
428 // else { Cool! Just do not throw. }
429} // Posix_mq_handle::Posix_mq_handle(Open_only)
430
432{
433 using boost::asio::connect_pipe;
434
435 // Reminder: Caller will cleanup on error, so we can just return.
436
437 Error_code sys_err_code;
438 connect_pipe(m_interrupt_detector_snd, m_interrupter_snd, sys_err_code);
439 if (!sys_err_code)
440 {
441 connect_pipe(m_interrupt_detector_rcv, m_interrupter_rcv, sys_err_code);
442 }
443
444 if (sys_err_code)
445 {
446 FLOW_LOG_WARNING
447 ("Posix_mq_handle [" << *this << "]: Constructing MQ handle to MQ at name [" << absolute_name() << "]: "
448 "connect-pipe failed. Details follow."); // Caller logs about sys_err_code.
449 }
450
451 return sys_err_code;
452} // Posix_mq_handle::pipe_setup()
453
455{
456 using boost::system::system_category;
457 using ::epoll_create1;
458 using ::epoll_ctl;
459 using Epoll_event = ::epoll_event;
460 // using ::EPOLL_CTL_ADD; // A macro apparently.
461 // using ::EPOLLIN; // A macro apparently.
462
463 /* m_mq is good to go.
464 *
465 * Set up one epoll handle for checking for writability; another for readability.
466 * Into each one add an interrupter FD (readability of read end of an anonymous pipe).
467 *
468 * Reminder: Caller will cleanup on error, so we can just return. */
469 Error_code sys_err_code;
470
471 const auto setup = [&](Native_handle* epoll_hndl_ptr, bool snd_else_rcv)
472 {
473 auto& epoll_hndl = *epoll_hndl_ptr;
474 auto& interrupt_detector = snd_else_rcv ? m_interrupt_detector_snd : m_interrupt_detector_rcv;
475
476 epoll_hndl = Native_handle{epoll_create1(0)};
477 if (epoll_hndl.m_native_handle == -1)
478 {
479 epoll_hndl = {}; // No-op as of this writing, but just to keep it maintainable do it anyway.
480
481 sys_err_code = {errno, system_category()};
482 FLOW_LOG_WARNING("Posix_mq_handle [" << *this << "]: Created MQ handle fine, but epoll_create1() failed; "
483 "details follow."); // Caller logs about sys_err_code.
484 return;
485 }
486 // else if (epoll_hndl.m_native_handle != -1)
487
488 // Each epoll_wait() will listen for transmissibility of the queue itself + readability of interrupt-pipe.
489 Epoll_event event_of_interest1;
490 event_of_interest1.events = snd_else_rcv ? EPOLLOUT : EPOLLIN;
491 event_of_interest1.data.fd = m_mq.m_native_handle;
492 Epoll_event event_of_interest2;
493 event_of_interest2.events = EPOLLIN;
494 event_of_interest2.data.fd = interrupt_detector.native_handle();
495 if ((epoll_ctl(epoll_hndl.m_native_handle, EPOLL_CTL_ADD, event_of_interest1.data.fd, &event_of_interest1) == -1)
496 ||
497 (epoll_ctl(epoll_hndl.m_native_handle, EPOLL_CTL_ADD, event_of_interest2.data.fd, &event_of_interest2) == -1))
498 {
499 sys_err_code = {errno, system_category()};
500 FLOW_LOG_WARNING("Posix_mq_handle [" << *this << "]: Created MQ handle fine, but an epoll_ctl() failed; "
501 "snd_else_rcv = [" << snd_else_rcv << "]; details follow.");
502 return;
503 }
504 }; // const auto setup =
505
506 if (setup(&m_epoll_hndl_snd, true), !sys_err_code) // Do setup1. If it succeeds...
507 {
508 setup(&m_epoll_hndl_rcv, false); // ...do setup2.
509 }
510
511 return sys_err_code;
512} // Posix_mq_handle::epoll_setup()
513
516{
517 operator=(std::move(src));
518}
519
521{
522 using ::mq_close;
523 using ::close;
524
525 if (m_mq.null())
526 {
527 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: No MQ handle to close in destructor.");
528 assert(m_epoll_hndl_snd.null());
529 assert(m_epoll_hndl_rcv.null());
530 }
531 else
532 {
533 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Closing MQ handle (and epoll set).");
534 // Disregard any error. In Linux, by the way, only EBADF is possible apparently; we should be fine.
535 mq_close(m_mq.m_native_handle);
536
537 assert(!m_epoll_hndl_snd.null());
538 assert(!m_epoll_hndl_rcv.null());
539 close(m_epoll_hndl_snd.m_native_handle); // Ditto regarding errors.
541 }
542} // Posix_mq_handle::~Posix_mq_handle()
543
545{
546 using std::swap;
547 using ::mq_close;
548 using ::close;
549
550 if (&src != this)
551 {
552 if (!m_mq.null())
553 {
554 mq_close(m_mq.m_native_handle);
555 }
556 m_mq = {};
557
559
560 if (!m_epoll_hndl_snd.null())
561 {
563 }
564 m_epoll_hndl_snd = {};
565 if (!m_epoll_hndl_rcv.null())
566 {
568 }
569 m_epoll_hndl_rcv = {};
570
575
576 swap(*this, src);
577 }
578 return *this;
579}
580
582{
583 using util::Pipe_writer;
584 using util::Pipe_reader;
585 using flow::log::Log_context;
586 using std::swap;
587 using boost::array;
588
589 // This is a bit faster than un-specialized std::swap() which would require a move ction + 3 move assignments.
590
591 swap(static_cast<Log_context&>(val1), static_cast<Log_context&>(val2));
592 swap(val1.m_mq, val2.m_mq);
598
599 /* This is annoying! Maybe we should just wrap all these in unique_ptr<>s to avoid this nonsense.
600 * As it is, it might be unsafe to swap asio objects apart from their Task_engines, so doing this blech. */
601 try
602 {
603 array<Native_handle, 4> fds1;
604 array<Native_handle, 4> fds2;
605 const auto unload = [&](Posix_mq_handle& val, auto& fds)
606 {
607 if (val.m_interrupter_snd.is_open())
608 {
609 fds[0].m_native_handle = val.m_interrupter_snd.release();
610 }
611 if (val.m_interrupter_rcv.is_open())
612 {
613 fds[1].m_native_handle = val.m_interrupter_rcv.release();
614 }
615 if (val.m_interrupt_detector_snd.is_open())
616 {
617 fds[2].m_native_handle = val.m_interrupt_detector_snd.release();
618 }
619 if (val.m_interrupt_detector_rcv.is_open())
620 {
621 fds[3].m_native_handle = val.m_interrupt_detector_rcv.release();
622 }
623 };
624 unload(val1, fds1);
625 unload(val2, fds2);
626
627 const auto reload = [&](Posix_mq_handle& val, auto& fds)
628 {
629 // Leave their stupid task engines in-place. Do need to reassociate them with the swapped FDs though.
631 = fds[0].null() ? Pipe_writer{val.m_nb_task_engine}
632 : Pipe_writer{val.m_nb_task_engine, fds[0].m_native_handle};
634 = fds[1].null() ? Pipe_writer{val.m_nb_task_engine}
635 : Pipe_writer{val.m_nb_task_engine, fds[1].m_native_handle};
637 = fds[2].null() ? Pipe_reader{val.m_nb_task_engine}
638 : Pipe_reader{val.m_nb_task_engine, fds[2].m_native_handle};
640 = fds[3].null() ? Pipe_reader{val.m_nb_task_engine}
641 : Pipe_reader{val.m_nb_task_engine, fds[3].m_native_handle};
642 };
643 reload(val1, fds2); // Swap 'em.
644 reload(val2, fds1);
645 }
646 catch (...) // .release() and ctor can throw, and they really shouldn't at all.
647 {
648 assert(false && "Horrible exception."); std::abort();
649 }
650} // swap()
651
653{
654 using ::mq_attr;
655 using ::mq_getattr;
656
657 assert((!m_mq.null())
658 && "As advertised: max_msg_size() => undefined behavior if not successfully cted or was moved-from.");
659
660 mq_attr attr;
661 if (mq_getattr(m_mq.m_native_handle, &attr) != 0)
662 {
663 /* We could handle error gracefully, as we do elsewhere, but EBADF is the only possibility, and there's zero
664 * reason it should happen unless m_mq is null at this point. It'd be fine to handle it, but then we'd have
665 * to emit an Error_code, and the API would change, and the user would have more to worry about; I (ygoldfel)
666 * decided it's overkill. So if it does happen, log and assert(). @todo Maybe reconsider. */
667 Error_code sink;
668 handle_mq_api_result(-1, &sink, "Posix_mq_handle::max_msg_size(): mq_getattr()");
669 assert(false && "mq_getattr() failed (details logged); this is too bizarre.");
670 return 0;
671 }
672 // else
673
674 return size_t(attr.mq_msgsize);
675} // Posix_mq_handle::max_msg_size()
676
678{
679 // Very similar to max_msg_size(). @todo Maybe code reuse. Though line count might be even greater then so....
680
681 using ::mq_attr;
682 using ::mq_getattr;
683 assert((!m_mq.null())
684 && "As advertised: max_n_msgs() => undefined behavior if not successfully cted or was moved-from.");
685 mq_attr attr;
686 if (mq_getattr(m_mq.m_native_handle, &attr) != 0)
687 {
688 Error_code sink;
689 handle_mq_api_result(-1, &sink, "Posix_mq_handle::max_n_msgs(): mq_getattr()");
690 assert(false && "mq_getattr() failed (details logged); this is too bizarre.");
691 return 0;
692 }
693 return size_t(attr.mq_maxmsg);
694} // Posix_mq_handle::max_n_msgs()
695
697{
698 using boost::system::system_category;
699 using ::mq_attr;
700 using ::mq_setattr;
701 // using ::O_NONBLOCK; // A macro apparently.
702
703 assert(err_code);
704
705 /* By the way -- no, this cannot be done in mq_attr that goes into mq_open(). It is ignored there according to docs.
706 * Would've been nice actually.... */
707
708 mq_attr attr;
709 attr.mq_flags = nb ? O_NONBLOCK : 0;
710 return handle_mq_api_result(mq_setattr(m_mq.m_native_handle, &attr, nullptr),
711 err_code, "Posix_mq_handle::set_non_blocking(): mq_setattr()");
712} // Posix_mq_handle::set_non_blocking()
713
715{
716 using flow::util::buffers_dump_string;
717 using ::mq_send;
718 // using ::EAGAIN; // A macro apparently.
719
720 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, try_send, blob, _1);
721 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
722
723 assert((!m_mq.null())
724 && "As advertised: try_send() => undefined behavior if not successfully cted or was moved-from.");
725
726 auto blob_data = blob.data();
727 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
728 "size [" << blob.size() << "].");
729 if (blob.size() == 0)
730 {
731 /* ::mq_send(..., nullptr, N), even when N == 0, is (1) empirically speaking harmless but (2) technically
732 * in violation of arg 2's non-null decl (even though `N == 0` is explicitly allowed per `man` page) hence
733 * (3) causes a clang UBSAN sanitizer error. So waste a couple cycles by feeding it this dummy
734 * non-null value. */
735 blob_data = static_cast<const void*>(&blob_data);
736 }
737 else // if (blob.size() != 0)
738 {
739 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(blob, " ") << "].");
740 }
741
742 if (mq_send(m_mq.m_native_handle,
743 static_cast<const char*>(blob_data),
744 blob.size(), 0) == 0)
745 {
746 err_code->clear();
747 return true; // Instant success.
748 }
749 // else
750 if (errno == EAGAIN)
751 {
752 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
753 "size [" << blob.size() << "]: would-block.");
754 err_code->clear();
755 return false; // Queue full.
756 }
757 // else
758
759 handle_mq_api_result(-1, err_code, "Posix_mq_handle::try_send(): mq_send(nb)");
760 assert(*err_code);
761 return false;
762} // Posix_mq_handle::try_send()
763
765{
766 using flow::util::buffers_dump_string;
767 using ::mq_send;
768 // using ::EAGAIN; // A macro apparently.
769
770 if (flow::error::exec_void_and_throw_on_error
771 ([&](Error_code* actual_err_code) { send(blob, actual_err_code); },
772 err_code, "Posix_mq_handle::send()"))
773 {
774 return;
775 }
776 // else
777
778 assert((!m_mq.null())
779 && "As advertised: send() => undefined behavior if not successfully cted or was moved-from.");
780
781 auto blob_data = blob.data();
782 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Blocking-push of blob @[" << blob_data << "], "
783 "size [" << blob.size() << "]. Trying nb-push first; if it succeeds -- great. "
784 "Else will wait/retry/wait/retry/....");
785 if (blob.size() == 0)
786 {
787 // See similarly-placed comment in try_send() which explains this.
788 blob_data = static_cast<const void*>(&blob_data);
789 }
790 else // if (blob.size() != 0)
791 {
792 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(blob, " ") << "].");
793 }
794
795 /* We could just invoke blocking mq_send(m_mq), but to get the promised logging we go a tiny bit fancier as follows.
796 * Update: Now that we have to be interrupt_*()ible, also reuse wait_*() instead of using native
797 * blocking mq_*(). */
798
799 while (true)
800 {
801 if (mq_send(m_mq.m_native_handle,
802 static_cast<const char*>(blob_data),
803 blob.size(), 0) == 0)
804 {
805 err_code->clear();
806 return; // Instant success.
807 }
808 // else
809 if (errno != EAGAIN)
810 {
811 handle_mq_api_result(-1, err_code, "Posix_mq_handle::send(): mq_send(nb)");
812 assert(*err_code);
813 return;
814 }
815 // else if (would-block): TRACE-logs.
816
817 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
818 "size [" << blob.size() << "]: would-block. Executing blocking-wait.");
819
820 wait_sendable(err_code);
821 if (*err_code)
822 {
823 // Whether interrupted or true error, we're done.
824 return;
825 }
826 // else
827
828 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
829 } // while (true)
830} // Posix_mq_handle::send()
831
833 Error_code* err_code)
834{
835 using flow::util::time_since_posix_epoch;
836 using flow::util::buffers_dump_string;
837 using flow::Fine_clock;
838 using boost::chrono::floor;
839 using boost::chrono::round;
840 using boost::chrono::seconds;
841 using boost::chrono::microseconds;
842 using boost::chrono::nanoseconds;
843 using ::mq_send;
844 // using ::EAGAIN; // A macro apparently.
845
846 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, timed_send, blob, timeout_from_now, _1);
847 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
848
849 // Similar to a combo of (blocking) send() and (non-blocking) try_send() -- keeping comments light where redundant.
850
851 assert((!m_mq.null())
852 && "As advertised: timed_send() => undefined behavior if not successfully cted or was moved-from.");
853
854 auto blob_data = blob.data();
855 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Blocking-timed-push of blob @[" << blob_data << "], "
856 "size [" << blob.size() << "]; timeout ~[" << round<microseconds>(timeout_from_now) << "]. "
857 "Trying nb-push first; if it succeeds -- great. Else will wait/retry/wait/retry/....");
858 if (blob.size() == 0)
859 {
860 // See similarly-placed comment in try_send() which explains this.
861 blob_data = static_cast<const void*>(&blob_data);
862 }
863 else // if (blob.size() != 0)
864 {
865 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(blob, " ") << "].");
866 }
867
868 auto before = Fine_clock::now();
869
870 while (true)
871 {
872 if (mq_send(m_mq.m_native_handle,
873 static_cast<const char*>(blob_data),
874 blob.size(), 0) == 0)
875 {
876 err_code->clear();
877 break; // Instant success.
878 }
879 // else
880 if (errno != EAGAIN)
881 {
882 handle_mq_api_result(-1, err_code, "Posix_mq_handle::timed_send(): mq_send(nb)");
883 assert(*err_code);
884 return false;
885 }
886 // else if (would-block): TRACE-logs.
887
888 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-push of blob @[" << blob_data << "], "
889 "size [" << blob.size() << "]: would-block. Executing blocking-wait.");
890
891 const bool ready = timed_wait_sendable(timeout_from_now, err_code);
892 if (*err_code)
893 {
894 // Whether interrupted or true error, we're done.
895 return false;
896 }
897 // else:
898
899 if (!ready) // I.e., if (timed out).
900 {
901 FLOW_LOG_TRACE("Did not finish before timeout.");
902 return false;
903 }
904 // else: successful wait for transmissibility. Try nb-transmitting again.
905 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
906
907 const auto after = Fine_clock::now();
908 timeout_from_now -= (after - before); // Reduce time left by the duration of the last attempt.
909 before = after;
910 } // while (true)
911
912 return true;
913} // Posix_mq_handle::timed_send()
914
916{
917 using util::Blob_mutable;
918 using flow::util::buffers_dump_string;
919 using ::mq_receive;
920 // using ::EAGAIN; // A macro apparently.
921
922 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, try_receive, blob, _1);
923 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
924
925 assert((!m_mq.null())
926 && "As advertised: try_receive() => undefined behavior if not successfully cted or was moved-from.");
927
928 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
929 "max-size [" << blob->size() << "].");
930
931 ssize_t n_rcvd;
932 unsigned int pri_ignored;
933 if ((n_rcvd = mq_receive(m_mq.m_native_handle,
934 static_cast<char*>(blob->data()),
935 blob->size(), &pri_ignored)) >= 0)
936 {
937 *blob = Blob_mutable{blob->data(), size_t(n_rcvd)};
938 FLOW_LOG_TRACE("Received message sized [" << n_rcvd << "].");
939 if (blob->size() != 0)
940 {
941 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(*blob, " ") << "].");
942 }
943 err_code->clear();
944 return true; // Instant success.
945 }
946 // else
947 if (errno == EAGAIN)
948 {
949 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
950 "max-size [" << blob->size() << "]: would-block.");
951 err_code->clear();
952 return false; // Queue empty.
953 }
954 // else
955
956 handle_mq_api_result(-1, err_code, "Posix_mq_handle::try_receive(): mq_receive(nb)");
957 assert(*err_code);
958 return false;
959} // Posix_mq_handle::try_receive()
960
962{
963 using util::Blob_mutable;
964 using flow::util::buffers_dump_string;
965 using ::mq_receive;
966 // using ::EAGAIN; // A macro apparently.
967
968 if (flow::error::exec_void_and_throw_on_error
969 ([&](Error_code* actual_err_code) { receive(blob, actual_err_code); },
970 err_code, "Posix_mq_handle::receive()"))
971 {
972 return;
973 }
974 // else
975
976 assert((!m_mq.null())
977 && "As advertised: receive() => undefined behavior if not successfully cted or was moved-from.");
978
979 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Blocking-pop to blob @[" << blob->data() << "], "
980 "max-size [" << blob->size() << "]. Trying nb-pop first; if it succeeds -- great. "
981 "Else will wait/retry/wait/retry/....");
982
983 /* We could just invoke blocking mq_receive(m_mq), but to get the promised logging we go tiny bit fancier as follows.
984 * Update: Now that we have to be interrupt_*()ible, also reuse wait_*() instead of using native
985 * blocking mq_*(). */
986
987 ssize_t n_rcvd;
988 unsigned int pri_ignored;
989
990 while (true)
991 {
992 if ((n_rcvd = mq_receive(m_mq.m_native_handle,
993 static_cast<char*>(blob->data()),
994 blob->size(), &pri_ignored)) >= 0)
995 {
996 *blob = Blob_mutable{blob->data(), size_t(n_rcvd)};
997 FLOW_LOG_TRACE("Received message sized [" << n_rcvd << "].");
998 if (blob->size() != 0)
999 {
1000 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(*blob, " ") << "].");
1001 }
1002 err_code->clear();
1003 return; // Instant success.
1004 }
1005 // else
1006 if (errno != EAGAIN)
1007 {
1008 handle_mq_api_result(-1, err_code, "Posix_mq_handle::receive(): mq_receive(nb)");
1009 assert(*err_code);
1010 return;
1011 }
1012 // else if (would-block): TRACE-logs.
1013
1014 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
1015 "max-size [" << blob->size() << "]: would-block. Executing blocking-wait.");
1016
1017 wait_receivable(err_code);
1018 if (*err_code)
1019 {
1020 // Whether interrupted or true error, we're done.
1021 return;
1022 }
1023 // else
1024
1025 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
1026 } // while (true)
1027} // Posix_mq_handle::receive()
1028
1030 Error_code* err_code)
1031{
1032 using util::Blob_mutable;
1033 using flow::Fine_clock;
1034 using flow::util::time_since_posix_epoch;
1035 using flow::util::buffers_dump_string;
1036 using boost::chrono::floor;
1037 using boost::chrono::round;
1038 using boost::chrono::seconds;
1039 using boost::chrono::microseconds;
1040 using boost::chrono::nanoseconds;
1041 using ::mq_receive;
1042 // using ::EAGAIN; // A macro apparently.
1043
1044 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, timed_receive, blob, timeout_from_now, _1);
1045 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
1046
1047 // Similar to a combo of (blocking) receive() and (non-blocking) try_receive() -- keeping comments light if redundant.
1048
1049 assert((!m_mq.null())
1050 && "As advertised: timed_receive() => undefined behavior if not successfully cted or was moved-from.");
1051
1052 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Blocking-timed-pop to blob @[" << blob->data() << "], "
1053 "max-size [" << blob->size() << "]; timeout ~[" << round<microseconds>(timeout_from_now) << "]. "
1054 "Trying nb-pop first; if it succeeds -- great. Else will wait/retry/wait/retry/....");
1055
1056 ssize_t n_rcvd;
1057 unsigned int pri_ignored;
1058
1059 auto before = Fine_clock::now();
1060
1061 while (true)
1062 {
1063 if ((n_rcvd = mq_receive(m_mq.m_native_handle,
1064 static_cast<char*>(blob->data()),
1065 blob->size(), &pri_ignored)) >= 0)
1066 {
1067 *blob = Blob_mutable{blob->data(), size_t(n_rcvd)};
1068 FLOW_LOG_TRACE("Received message sized [" << n_rcvd << "].");
1069 if (blob->size() != 0)
1070 {
1071 FLOW_LOG_DATA("Blob contents: [\n" << buffers_dump_string(*blob, " ") << "].");
1072 }
1073 err_code->clear();
1074 break; // Instant success.
1075 }
1076 // else
1077 if (errno != EAGAIN)
1078 {
1079 handle_mq_api_result(-1, err_code, "Posix_mq_handle::timed_receive(): mq_receive(nb)");
1080 assert(*err_code);
1081 return false;
1082 }
1083 // else if (would-block): TRACE-logs.
1084
1085 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Nb-pop to blob @[" << blob->data() << "], "
1086 "max-size [" << blob->size() << "]: would-block. Executing blocking-wait.");
1087
1088 const bool ready = timed_wait_receivable(timeout_from_now, err_code);
1089 if (*err_code)
1090 {
1091 // Whether interrupted or true error, we're done.
1092 return false;
1093 }
1094 // else:
1095
1096 if (!ready) // I.e., if (timed out).
1097 {
1098 FLOW_LOG_TRACE("Did not finish before timeout.");
1099 return false;
1100 }
1101 // else: successful wait for transmissibility. Try nb-transmitting again.
1102 FLOW_LOG_TRACE("Blocking-wait reported transmissibility. Retrying.");
1103
1104 const auto after = Fine_clock::now();
1105 timeout_from_now -= (after - before); // Reduce time left by the duration of the last attempt.
1106 before = after;
1107 } // while (true)
1108
1109 return true;
1110} // Posix_mq_handle::timed_receive()
1111
1112template<bool SND_ELSE_RCV>
1114{
1115 using util::Blob_const;
1116
1117 assert((!m_mq.null())
1118 && "As advertised: interrupt_impl() => undefined behavior if not successfully cted or was moved-from.");
1119
1120 Pipe_writer* interrupter_ptr;
1121 bool* interrupting_ptr;
1122 if constexpr(SND_ELSE_RCV)
1123 {
1124 interrupter_ptr = &m_interrupter_snd; interrupting_ptr = &m_interrupting_snd;
1125 }
1126 else
1127 {
1128 interrupter_ptr = &m_interrupter_rcv; interrupting_ptr = &m_interrupting_rcv;
1129 }
1130 auto& interrupter = *interrupter_ptr;
1131 auto& interrupting = *interrupting_ptr;
1132
1133 if (interrupting)
1134 {
1135 FLOW_LOG_WARNING("Posix_mq_handle [" << *this << "]: Interrupt mode already ON for "
1136 "snd_else_rcv [" << SND_ELSE_RCV << "]. Ignoring.");
1137 return false;
1138 }
1139 // else
1140
1141 interrupting = true; // Reminder: This member is for the above guard *only*. wait_impl() relies on FD state.
1142 FLOW_LOG_INFO("Posix_mq_handle [" << *this << "]: Interrupt mode turning ON for "
1143 "snd_else_rcv [" << SND_ELSE_RCV << "].");
1144
1145 util::pipe_produce(get_logger(), &interrupter);
1146
1147 // Now any level-triggered poll-wait will detect that this mode is on.
1148 return true;
1149} // Posix_mq_handle::interrupt_impl()
1150
1151template<bool SND_ELSE_RCV>
1153{
1154 using util::Blob_mutable;
1155
1156 assert((!m_mq.null())
1157 && "As advertised: allow_impl() => undefined behavior if not successfully cted or was moved-from.");
1158
1159 // Inverse of interrupt_impl(). Keeping comments light.
1160
1161 Pipe_reader* interrupt_detector_ptr;
1162 bool* interrupting_ptr;
1163 if constexpr(SND_ELSE_RCV)
1164 {
1165 interrupt_detector_ptr = &m_interrupt_detector_snd; interrupting_ptr = &m_interrupting_snd;
1166 }
1167 else
1168 {
1169 interrupt_detector_ptr = &m_interrupt_detector_rcv; interrupting_ptr = &m_interrupting_rcv;
1170 }
1171 auto& interrupt_detector = *interrupt_detector_ptr;
1172 auto& interrupting = *interrupting_ptr;
1173
1174 if (!interrupting)
1175 {
1176 FLOW_LOG_WARNING("Posix_mq_handle [" << *this << "]: Interrupt mode already OFF for "
1177 "snd_else_rcv [" << SND_ELSE_RCV << "]. Ignoring.");
1178 return false;
1179 }
1180 // else
1181
1182 interrupting = false;
1183 FLOW_LOG_INFO("Posix_mq_handle [" << *this << "]: Interrupt mode turning OFF for "
1184 "snd_else_rcv [" << SND_ELSE_RCV << "].");
1185
1186 util::pipe_consume(get_logger(), &interrupt_detector);
1187
1188 // Now any level-triggered poll-wait will detect that this mode is off.
1189 return true;
1190} // Posix_mq_handle::allow_impl()
1191
1193{
1194 return interrupt_impl<true>();
1195}
1196
1198{
1199 return allow_impl<true>();
1200}
1201
1203{
1204 return interrupt_impl<false>();
1205}
1206
1208{
1209 return allow_impl<false>();
1210}
1211
1212template<bool SND_ELSE_RCV>
1213bool Posix_mq_handle::wait_impl(util::Fine_duration timeout_from_now_or_none, Error_code* err_code)
1214{
1215 using util::Fine_time_pt;
1216 using util::Fine_duration;
1217 using flow::util::time_since_posix_epoch;
1218 using boost::chrono::round;
1219 using boost::chrono::milliseconds;
1220 using boost::system::system_category;
1221 using boost::array;
1222 using ::epoll_wait;
1223 using Epoll_event = ::epoll_event;
1224
1225 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, wait_impl<SND_ELSE_RCV>, timeout_from_now_or_none, _1);
1226 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
1227
1228 assert((!m_mq.null())
1229 && "As advertised: wait_impl() => undefined behavior if not successfully cted or was moved-from.");
1230
1231 /* By the way -- epoll_wait() takes # of milliseconds, so round up to that and no need to do crazy stuff like
1232 * timed_send() et al; just convert to milliseconds. */
1233 int epoll_timeout_from_now_ms;
1234 milliseconds epoll_timeout_from_now;
1235 if (timeout_from_now_or_none == Fine_duration::max())
1236 {
1237 epoll_timeout_from_now_ms = -1;
1238 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Infinite-await-unstarved for "
1239 "snd_else_rcv [" << SND_ELSE_RCV << "]. Will perform an epoll_wait().");
1240 }
1241 else
1242 {
1243 epoll_timeout_from_now = round<milliseconds>(timeout_from_now_or_none);
1244 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Blocking-await/poll-unstarved for "
1245 "snd_else_rcv [" << SND_ELSE_RCV << "]; timeout ~[" << epoll_timeout_from_now << "] -- "
1246 "if 0 then poll. Will perform an epoll_wait().");
1247 epoll_timeout_from_now_ms = int(epoll_timeout_from_now.count());
1248 }
1249
1250 array<Epoll_event, 2> evs; // Only one possible event (we choose 1 of 2 event sets).
1251 Native_handle::handle_t epoll_hndl;
1252 if constexpr(SND_ELSE_RCV) { epoll_hndl = m_epoll_hndl_snd.m_native_handle; } else
1253 { epoll_hndl = m_epoll_hndl_rcv.m_native_handle; }
1254 const auto epoll_result = epoll_wait(epoll_hndl, evs.begin(), sizeof(evs) / sizeof(Epoll_event),
1255 epoll_timeout_from_now_ms);
1256 if (epoll_result == -1)
1257 {
1258 const auto& sys_err_code = *err_code = {errno, system_category()};
1259 FLOW_LOG_WARNING("Posix_mq_handle [" << *this << "]: epoll_wait() yielded error. Details follow.");
1260 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
1261 return false;
1262 }
1263 // else
1264
1265 assert(epoll_result <= 2);
1266 if ((epoll_result == 2) // Max of 2 events; if interrupted then as promised disregard it being also transmissible.
1267 ||
1268 ((epoll_result == 1) // 1 event: need to check whether it's the interruptor as opposed to the actual queue.
1269 && (evs[0].data.fd != m_mq.m_native_handle)))
1270 {
1271 if (timeout_from_now_or_none == Fine_duration::max())
1272 {
1273 FLOW_LOG_INFO("Posix_mq_handle [" << *this << "]: Infinite-await-unstarved for "
1274 "snd_else_rcv [" << SND_ELSE_RCV << "]: interrupted.");
1275 }
1276 else
1277 {
1278 FLOW_LOG_INFO("Posix_mq_handle [" << *this << "]: Blocking-await/poll-unstarved for "
1279 "snd_else_rcv [" << SND_ELSE_RCV << "]; timeout ~[" << epoll_timeout_from_now << "] -- "
1280 "if 0 then poll: interrupted.");
1281 }
1282 *err_code = error::Code::S_INTERRUPTED;
1283 return false;
1284 }
1285 // else if (epoll_result <= 1): Not interrupted.
1286
1287 const bool success = epoll_result == 1;
1288 if (timeout_from_now_or_none == Fine_duration::max())
1289 {
1290 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Infinite-await-unstarved for "
1291 "snd_else_rcv [" << SND_ELSE_RCV << "]: succeeded? = [" << success << "].");
1292 }
1293 else
1294 {
1295 FLOW_LOG_TRACE("Posix_mq_handle [" << *this << "]: Blocking-await/poll-unstarved for "
1296 "snd_else_rcv [" << SND_ELSE_RCV << "]; timeout ~[" << epoll_timeout_from_now << "] -- "
1297 "if 0 then poll: succeeded? = [" << success << "].");
1298 }
1299
1300 err_code->clear();
1301 return success;
1302} // Posix_mq_handle::wait_impl()
1303
1305{
1306 return wait_impl<true>(util::Fine_duration::zero(), err_code);
1307}
1308
1310{
1311 wait_impl<true>(util::Fine_duration::max(), err_code);
1312}
1313
1315{
1316 return wait_impl<true>(timeout_from_now, err_code);
1317}
1318
1320{
1321 return wait_impl<false>(util::Fine_duration::zero(), err_code);
1322}
1323
1325{
1326 wait_impl<false>(util::Fine_duration::max(), err_code);
1327}
1328
1330{
1331 return wait_impl<false>(timeout_from_now, err_code);
1332}
1333
1335{
1336 return m_mq;
1337}
1338
1339void Posix_mq_handle::remove_persistent(flow::log::Logger* logger_ptr, // Static.
1340 const Shared_name& absolute_name, Error_code* err_code)
1341{
1342 using boost::system::system_category;
1343 using ::mq_unlink;
1344
1345 if (flow::error::exec_void_and_throw_on_error
1346 ([&](Error_code* actual_err_code)
1347 { remove_persistent(logger_ptr, absolute_name, actual_err_code); },
1348 err_code, "Posix_mq_handle::remove_persistent()"))
1349 {
1350 return;
1351 }
1352 // else
1353
1354 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
1355
1356 FLOW_LOG_INFO("Posix_mq @ Shared_name[" << absolute_name << "]: Removing persistent MQ if possible.");
1357 if (mq_unlink(shared_name_to_mq_name(absolute_name).c_str()) == 0)
1358 {
1359 err_code->clear();
1360 return;
1361 }
1362 // else
1363
1364 const auto& sys_err_code = *err_code = {errno, system_category()};
1365 FLOW_LOG_WARNING("Posix_mq @ Shared_name[" << absolute_name << "]: While removing persistent MQ: "
1366 "mq_unlink() yielded error. Details follow.");
1367 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
1368} // Posix_mq_handle::remove_persistent()
1369
1371{
1372 using boost::system::system_category;
1373
1374 if (result == 0)
1375 {
1376 err_code->clear();
1377 return true;
1378 }
1379 // else
1380
1381 const auto& sys_err_code = *err_code
1382 = (errno == EMSGSIZE)
1383 ? Error_code{error::Code::S_MQ_MESSAGE_SIZE_OVER_OR_UNDERFLOW} // By contract: this specific code for this.
1384 : Error_code{errno, system_category()}; // Otherwise whatever it was.
1385 FLOW_LOG_WARNING("Posix_mq_handle [" << *this << "]: mq_*() yielded error; context = [" << context << "]. "
1386 "Details follow.");
1387 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
1388
1389 return false;
1390} // Posix_mq_handle::handle_mq_api_result()
1391
1393{
1394 return m_absolute_name;
1395}
1396
1397std::ostream& operator<<(std::ostream& os, const Posix_mq_handle& val)
1398{
1399 os << '@' << &val << ": sh_name[" << val.absolute_name() << "] native_handle[";
1400 const auto native_handle = val.native_handle();
1401 if (native_handle.null())
1402 {
1403 return os << "null]";
1404 }
1405 // else
1406 return os << native_handle << ']';
1407}
1408
1409namespace
1410{
1411
1412std::string shared_name_to_mq_name(const Shared_name& name)
1413{
1414 // Pre-pend slash. See `man mq_overview`.
1415 std::string mq_name("/");
1416 return mq_name += name.str();
1417}
1418
1419} // namespace (anon)
1420
1421} // namespace ipc::transport
Implements the Persistent_mq_handle concept by wrapping the POSIX message queue API (see man mq_overv...
bool allow_sends()
Implements Persistent_mq_handle API: Turn off preemptive/concurrent interruption of blocking-sends an...
bool interrupt_receives()
Implements Persistent_mq_handle API: Turn on preemptive/concurrent interruption of blocking-receives ...
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.
bool interrupt_sends()
Implements Persistent_mq_handle API: Turn on preemptive/concurrent interruption of blocking-sends and...
util::Pipe_reader Pipe_reader
Short-hand for anonymous pipe read end.
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...
static const Shared_name S_RESOURCE_TYPE_ID
Implements concept API.
bool wait_impl(util::Fine_duration timeout_from_now_or_none, Error_code *err_code)
Impl body for *_sendable() and *_receivable().
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...
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...
bool handle_mq_api_result(int result, Error_code *err_code, util::String_view context) const
Helper that handles the result of an mq_*() call by logging WARNING(s) on error; setting *err_code on...
Pipe_reader m_interrupt_detector_snd
A byte is read from this end by allow_sends() to make it not-readable for the poll-wait in wait_impl(...
void wait_receivable(Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like receive() but without the actual popping of a message.
Native_handle m_epoll_hndl_snd
epoll_*() handle (.null() if and only if m_mq is null) that is level-triggered to be active (with onl...
bool allow_receives()
Implements Persistent_mq_handle API: Turn off preemptive/concurrent interruption of blocking-receives...
size_t max_msg_size() const
Implements Persistent_mq_handle API: Returns the max message size of the underlying queue.
Pipe_reader m_interrupt_detector_rcv
Other-direction counterpart to m_interrupt_detector_snd.
Error_code epoll_setup()
Ctor helper that sets up m_epoll_hndl_snd and m_epoll_hndl_rcv.
util::Pipe_writer Pipe_writer
Short-hand for anonymous pipe write end.
size_t max_n_msgs() const
Implements Persistent_mq_handle API: Returns the max message count of the underlying queue.
void wait_sendable(Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like send() but without the actual pushing of a message.
Pipe_writer m_interrupter_rcv
Other-direction counterpart to m_interrupter_snd.
Native_handle native_handle() const
Implements Persistent_mq_handle API: Returns the stored native MQ handle; null if not open.
friend void swap(Posix_mq_handle &val1, Posix_mq_handle &val2)
Implements Persistent_mq_handle related concept: Swaps two objects.
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...
Posix_mq_handle()
Implements Persistent_mq_handle API: Construct null handle.
bool m_interrupting_snd
Starting at false, this is made true via interrupt_sends(), and back by allow_sends(); acts as a guar...
const Shared_name & absolute_name() const
Implements Persistent_mq_handle API: Returns name equal to absolute_name passed to ctor.
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...
Native_handle m_epoll_hndl_rcv
epoll_*() handle (.null() if and only if m_mq is null) that is level-triggered to be active (with onl...
bool allow_impl()
Impl body for allow_*().
bool interrupt_impl()
Impl body for interrupt_*().
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...
~Posix_mq_handle()
Implements Persistent_mq_handle API: Destroys this handle (or no-op if no handle was successfully con...
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_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...
bool set_non_blocking(bool nb, Error_code *err_code)
Sets m_mq to blocking or non-blocking and returns true on success and clears *err_code; otherwise ret...
Shared_name m_absolute_name
See absolute_name().
static void remove_persistent(flow::log::Logger *logger_ptr, const Shared_name &name, Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Removes the named persistent MQ.
Error_code pipe_setup()
Ctor helper that sets up m_interrupt* pipe items.
bool m_interrupting_rcv
Other-direction counterpart to m_interrupting_snd.
bool is_receivable(Error_code *err_code=nullptr)
Implements Persistent_mq_handle API: Like try_receive() but without the actual popping of a message.
Pipe_writer m_interrupter_snd
A byte is written to this end by interrupt_sends() to make it readable for the poll-wait in wait_impl...
Native_handle m_mq
Underlying MQ handle.
flow::util::Task_engine m_nb_task_engine
Never used for .run() or .async() – just so we can construct Pipe_reader, Pipe_writer.
Posix_mq_handle & operator=(Posix_mq_handle &&src)
Implements Persistent_mq_handle API: Replaces handle with the source handle while making the latter i...
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
static Shared_name ct(const Source &src)
Copy-constructs from a char-sequence container such as string or util::String_view.
void clear()
Makes it so empty() == true.
const std::string & str() const
Returns (sans copying) ref to immutable entire wrapped name string, suitable to pass into sys calls w...
Definition: shared_name.cpp:89
@ S_INTERRUPTED
A blocking operation was intentionally interrupted or preemptively canceled.
@ 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(Posix_mq_handle &val1, Posix_mq_handle &val2)
Implements Persistent_mq_handle related concept: Swaps two objects.
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
void pipe_produce(flow::log::Logger *logger_ptr, Pipe_writer *pipe)
Writes a byte to the given pipe writer.
Definition: util.cpp:60
const Open_or_create OPEN_OR_CREATE
Tag value indicating an open-if-exists-else-create operation.
Definition: util.cpp:36
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
void pipe_consume(flow::log::Logger *logger_ptr, Pipe_reader *pipe)
Reads a byte via the given pipe reader.
Definition: util.cpp:89
boost::asio::writable_pipe Pipe_writer
Short-hand for anonymous pipe write end.
Definition: util_fwd.hpp:35
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
boost::asio::readable_pipe Pipe_reader
Short-hand for anonymous pipe read end.
Definition: util_fwd.hpp:38
void set_resource_permissions(flow::log::Logger *logger_ptr, const fs::path &path, const Permissions &perms, Error_code *err_code)
Utility that sets the permissions of the given resource (at the supplied file system path) to specifi...
Definition: util.cpp:52
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
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.
constexpr bool null() const noexcept
Returns true if and only if m_native_handle equals S_NULL_HANDLE.
int handle_t
The native handle type. Much logic relies on this type being light-weight (fast to copy).
handle_t m_native_handle
The native handle (possibly equal to S_NULL_HANDLE), the exact payload of this Native_handle.