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