Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
asio_waitable_native_hndl.hpp
Go to the documentation of this file.
1/* Flow-IPC: Core
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include <boost/asio.hpp>
23#include <cassert>
24#include <type_traits>
25
26namespace ipc::util::sync_io
27{
28
29// Types.
30
31/**
32 * Useful if using the `sync_io` pattern within a user event loop built on boost.asio (optionally with flow.async
33 * help), an object of this class wraps a non-null Native_handle and allows one to use `.async_wait()` to perform
34 * event waiting on behalf of any `sync_io`-implementing ipc::transport or ipc::session object. If boost.asio
35 * integration is not the goal, it can at least act as a mere container of a Native_handle. `sync_io` pattern
36 * uses Asio_waitable_native_handle in either capacity. If you're familiar with boost.asio POSIX `descriptor`,
37 * then you're familiar with this class.
38 *
39 * @see sync_io::Event_wait_func doc header; and specifically section "Integrating with boost.asio."
40 * It provides context and rationale for this guy when used for `.async_wait()`.
41 *
42 * It may also be useful in its own right independently.
43 *
44 * In any case use is quite straightforward:
45 * -# Construct from a non-null, opened w/r/t kernel Native_handle --
46 * and an executor/execution context (typically a `Task_engine` or possibly boost.asio `strand`).
47 * (If boost.asio integration is not the goal, then any blank/otherwise-unused `Task_engine` is fine.)
48 * -# (Optional) If the wrapped handle is unknown at construction time, use the 1-arg ctor form;
49 * then `.assign()` it when it is known.
50 * -# Invoke `.async_wait()` (from Asio_waitable_native_handle::Base a/k/a `boost::asio::posix::descriptor`) as
51 * many times as desired.
52 * - And/or invoke accessor `.native_handle()` to re-obtain the wrapped handle as desired.
53 * -# Destroy when no more `.async_wait()`ing or `.native_handle()` access is necessary.
54 * -# `.release()` may be used to essentially nullify a `*this`. This may be desirable for cleanliness
55 * in some situations.
56 *
57 * You may not use `*this` object directly to perform actual I/O; it lacks such methods as `async_read_some()`.
58 * Moreover, performing actual I/O by obtaining the handle via `.native_handle()` and then doing reads/writes on that
59 * handle is likely against the (informal) aim of Asio_waitable_native_handle.
60 *
61 * Construction with a handle is likely performed by internal Flow-IPC code only, in practice. If integrating with
62 * boost.asio, you will construct handle-less ones (associated with your execution context) in the function you pass
63 * to `replace_event_wait_handles()`. Beyond that you are free to use it for your own purposes.
64 *
65 * @warning This object, like Asio_waitable_native_handle::Base, isn't *just* a Native_handle wrapper:
66 * It also has to be associated with an execution context, usually `Task_engine` (`boost::asio::io_context`)
67 * or boost.asio `strand`. If you associate, via ctor or assign(), 2 `*this`es each wrapping
68 * the same-valued Native_handle (storing the same raw handle) with the same `Task_engine`,
69 * behavior is undefined formally; though internally it'll cause a duplicate-descriptor error/exception
70 * (in Linux from `errno == EEXIST` from an internal `epoll_ctl()`). Even if you don't plan to
71 * `async_wait()`, this will unfortunately still happen; and for `async_wait()` purposes it will
72 * and *should* happen. So just take care not to create duplicate-handle `*this`es associated with
73 * the same `Task_engine`.
74 * @note Move assignment works which can be useful to associate a `*this` with a different execution context
75 * (event loop, `Task_engine`, etc.).
76 *
77 * ### Rationale for `protected` rather than `public` inheritance ###
78 * `public` inheritance would have been fine, more or less, but it seemed prudent to close off access to mutators
79 * from Asio_waitable_native_handle::Base like `close()` and `non_blocking()` which could sow sheer chaos;
80 * the point here is to watch for events on this guy, not (e.g.) blow up some internal transport within
81 * transport::Native_socket_stream et al.
82 */
83class Asio_waitable_native_handle : protected boost::asio::posix::descriptor
84{
85public:
86 // Types.
87
88 /// Short-hand for our base type; can be used for, e.g., `Base::wait_write` and `Base::wait_read`.
89 using Base = boost::asio::posix::descriptor;
90
91 // Constructors/destructor.
92
93 /**
94 * Construct boost.asio descriptor object such that one can `async_wait()` events on the given non-`.null()`
95 * Native_handle. The handle must be a valid (not closed) handle (descriptor, FD).
96 *
97 * Consider also the 1-arg ctor variant which stores no handle; followed by assign().
98 *
99 * @param ex
100 * See boost.asio docs for similar `posix::basic_descriptor` ctor.
101 * @param hndl
102 * The handle that might be watched subsequently. `.null()` leads to undefined behavior;
103 * assertion may trip. If the associated `Task_engine` is already associated with another
104 * Asio_waitable_native_handle (or other I/O object) that stores the same Native_handle::m_native_handle,
105 * it is formally undefined behavior (in reality a boost.asio exception is thrown; but just don't do it).
106 */
107 explicit Asio_waitable_native_handle(const Base::executor_type& ex, Native_handle hndl);
108
109 /**
110 * Construct boost.asio descriptor object that stores no handle. You may call assign() to change this.
111 *
112 * @param ex
113 * See boost.asio docs for similar `posix::basic_descriptor` ctor.
114 */
115 explicit Asio_waitable_native_handle(const Base::executor_type& ex);
116
117 /**
118 * Construct boost.asio descriptor object such that one can `async_wait()` events on the given non-`.null()`
119 * Native_handle. The handle must be a valid (not closed) handle (descriptor, FD).
120 *
121 * Consider also the 1-arg ctor variant which stores no handle; followed by assign().
122 *
123 * @tparam Execution_context
124 * See boost.asio docs for similar `posix::basic_descriptor` ctor.
125 * @param context
126 * See boost.asio docs for similar `posix::basic_descriptor` ctor.
127 * @param hndl
128 * The handle that might be watched subsequently. `.null()` leads to undefined behavior;
129 * assertion may trip. If the associated `Task_engine` is already associated with another
130 * Asio_waitable_native_handle that stores the same Native_handle::m_native_handle, it is formally
131 * undefined behavior (in reality a boost.asio exception is thrown; but just don't do it).
132 * @param ignored
133 * Disregard.
134 */
135 template<typename Execution_context>
136 explicit Asio_waitable_native_handle(Execution_context& context, Native_handle hndl,
137 typename std::enable_if_t<std::is_convertible_v
138 <Execution_context&,
139 boost::asio::execution_context&>>*
140 ignored = nullptr);
141
142 /**
143 * Construct boost.asio descriptor object that stores no handle. You may call assign() to change this.
144 *
145 * @tparam Execution_context
146 * See boost.asio docs for similar `posix::basic_descriptor` ctor.
147 * @param context
148 * See boost.asio docs for similar `posix::basic_descriptor` ctor.
149 * @param ignored
150 * Disregard.
151 */
152 template<typename Execution_context>
153 explicit Asio_waitable_native_handle(Execution_context& context,
154 typename std::enable_if_t<std::is_convertible_v
155 <Execution_context&,
156 boost::asio::execution_context&>>*
157 ignored = nullptr);
158
159 /**
160 * Move-construct. See boost.asio docs for similar `posix::basic_descriptor` ctor.
161 *
162 * @param src
163 * See above.
164 */
166
167 /**
168 * Destructor that does *not* OS-close (in the Native_handle::close() sense) the stored native handle (if any)
169 * but rather performs `this->release()` (eating any potential error in doing so).
170 */
172
173 // Methods.
174
175 /**
176 * Move-assign. See boost.asio docs for similar `posix::basic_descriptor` method.
177 *
178 * @param src
179 * See above.
180 * @return See above.
181 */
183
184 /**
185 * Returns the same Native_handle as passed to original handle-loading ctor or assign(), whichever happened last;
186 * or `.null()` Native_handle, if neither has been invoked, or release() was invoked more recently than either.
187 *
188 * @return See above.
189 */
191
192 /**
193 * Loads value to be returned by native_handle(). As with the 2+-arg ctor(s) the handle must be valid
194 * (not closed). See also `.release()` and dtor.
195 *
196 * If a handle is already loaded (`Base::is_open() == true`), this method automatically `release()`s it first.
197 *
198 * @param hndl
199 * The handle that might be watched subsequently. `.null()` leads to undefined behavior;
200 * assertion may trip. If the associated `Task_engine` is already associated with another
201 * Asio_waitable_native_handle that stores the same Native_handle::m_native_handle, it is formally
202 * undefined behavior (in reality a boost.asio exception is thrown; but just don't do it).
203 */
204 void assign(Native_handle hndl);
205
206 /// Importantly, inherit and publicly expose `posix::basic_descriptor::async_wait()`. See its boost.asio docs.
207 using Base::async_wait;
208
209 /// Inherit and publicly expose `posix::basic_descriptor::release()`. See its boost.asio docs.
210 using Base::release;
211
212 /// Inherit and publicly expose `posix::basic_descriptor::is_open()`. See its boost.asio docs.
213 using Base::is_open;
214}; // class Asio_waitable_native_handle
215
216// Template implementations.
217
218template<typename Execution_context>
220 (Execution_context& context, Native_handle hndl,
221 typename std::enable_if_t<std::is_convertible_v<Execution_context&,
222 boost::asio::execution_context&>>*) :
223 Base(context, hndl.m_native_handle)
224{
225 // See comment in similar ctor in .cpp.
226 assert(!hndl.null());
227}
228
229template<typename Execution_context>
231 (Execution_context& context,
232 typename std::enable_if_t<std::is_convertible_v<Execution_context&,
233 boost::asio::execution_context&>>*) :
234 Base(context)
235{
236 // Yeah.
237}
238
239} // namespace ipc::util::sync_io
Useful if using the sync_io pattern within a user event loop built on boost.asio (optionally with flo...
~Asio_waitable_native_handle()
Destructor that does not OS-close (in the Native_handle::close() sense) the stored native handle (if ...
Asio_waitable_native_handle(const Base::executor_type &ex, Native_handle hndl)
Construct boost.asio descriptor object such that one can async_wait() events on the given non-....
boost::asio::posix::descriptor Base
Short-hand for our base type; can be used for, e.g., Base::wait_write and Base::wait_read.
void assign(Native_handle hndl)
Loads value to be returned by native_handle().
Asio_waitable_native_handle(Asio_waitable_native_handle &&src)
Move-construct.
Native_handle native_handle()
Returns the same Native_handle as passed to original handle-loading ctor or assign(),...
Asio_waitable_native_handle & operator=(Asio_waitable_native_handle &&src)
Move-assign.
Contains common code, as well as important explanatory documentation in the following text,...
Definition: util_fwd.hpp:217
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.