Flow-IPC 1.0.0
Flow-IPC project: Public API.
|
A documentation-only concept defining the local side of an IPC conversation (session) with another entity (typically a separate process), also represented by a Session-implementing object, through which one can easily open IPC channels (ipc::transport::Channel), among other IPC features. More...
#include <session.hpp>
Public Types | |
using | Channel_obj = unspecified |
Each successful open_channel() and on-passive-open handler firing shall yield a concrete transport::Channel instance of this type – in PEER state. More... | |
using | Mdt_payload_obj = Mdt_payload |
Short-hand for Mdt_payload template arg. | |
using | Mdt_builder = typename transport::struc::schema::Metadata< Mdt_payload_obj >::Builder |
Pointee of Mdt_builder_ptr. | |
using | Mdt_builder_ptr = shared_ptr< Mdt_builder > |
Ref-counted handle to a capnp-generated Builder (and the payload it accesses) through which the user shall mutate the open_channel() metadata before invoking open_channel(). More... | |
using | Mdt_reader_ptr = shared_ptr< typename transport::struc::schema::Metadata< Mdt_payload_obj >::Reader > |
Ref-counted handle to a capnp-generated Reader (and the payload it accesses) through which the user shall access the metadata the other side prepared via Mdt_builder_ptr. More... | |
template<typename Message_body > | |
using | Structured_channel = transport::struc::Channel< unspecified > |
Convenience alias for the transport::struc::Channel concrete type to use if one desires (1) to upgrade a *this -generated channel to a struc::Channel and (2) to efficiently use the built-in capabilities of *this (notably, if applicable: SHM) for zero-copy performance when sending messages through that channel. More... | |
using | Structured_msg_builder_config = unspecified |
Convenience alias equal to Structured_channel<M>::Builder_config (regardless of M ). More... | |
using | Structured_msg_reader_config = unspecified |
Convenience alias equal to Structured_channel<M>::Reader_config (regardless of M ). More... | |
Public Member Functions | |
Session () | |
Default ctor: creates Session in NULL state (not operational). More... | |
Session (Session &&src) | |
Move ctor: *this becomes identical to src ; while src becomes as-if default-cted. More... | |
Session (const Session &)=delete | |
Copy construction is disallowed. | |
~Session () | |
In NULL state, no-op; in PEER state: Ends the session while informing (if possible) the opposing peer Session that the present session has ended. More... | |
Session & | operator= (Session &&src) |
Move assignment: acts as-if *this dtor executed; then *this becomes identical to src ; while src becomes as-if default-cted. More... | |
Session & | operator= (const Session &)=delete |
Copy assignment is disallowed. | |
Mdt_builder_ptr | mdt_builder () |
Returns a new metadata holder to be subsequently mutated by the user and then passed to open_channel() to initiate active-open attempt. More... | |
bool | open_channel (Channel_obj *target_channel, const Mdt_builder_ptr &mdt, Error_code *err_code=0) |
Synchronously active-opens a new channel which, on success, is moved-to *target_channel . More... | |
bool | open_channel (Channel_obj *target_channel, Error_code *err_code=0) |
Identical to open_channel(target_channel, mdt_builder(), err_code) ; in other words attempts to open channel with an uninitialized channel-open metadata payload. More... | |
const Session_token & | session_token () const |
In PEER state: Returns the (non-nil) logged-in session token to be used for any struc::Channel s one may wish to upgrade from Channel_obj channels yielded by open_channel() or passive-opens; or nil if in NULL state, or if *this session is hosed from a prior error. More... | |
Static Public Attributes | |
static constexpr schema::ShmType | S_SHM_TYPE = unspecified |
Specifies the SHM-provider for which this Session concept implementation provides support; or NONE if this is a vanilla Session that does not provide SHM-based zero-copy support. More... | |
static constexpr bool | S_SHM_ENABLED = unspecified |
Specifies whether this Session concept implementation provides support for zero-copy via SHM plus direct access to SHM arenas and lending/borrowing within them. More... | |
A documentation-only concept defining the local side of an IPC conversation (session) with another entity (typically a separate process), also represented by a Session-implementing object, through which one can easily open IPC channels (ipc::transport::Channel), among other IPC features.
While it is possible to open a Channel
without the help of this Session concept, for many users writing large multi-channel applications it would be too laborious to do so. The Session concept is the central one in ipc::session.
The concept defines the following behaviors/requirements.
false
/sentinel and no-op.Session
s do so, the session is essentially useless. Formally this is supported, simply because there is no need to enforce otherwise.)The concept (intentionally) does not define the following behaviors:
sync_io
onesThere are 3, overall, ways to obtain a transport::Channel via ipc::session.
Channel
as a pointer out-arg.In all cases these channels shall bear sync_io
-pattern peer objects: not async-I/O ones.
move()
semantics.As is typical: 2 different Session objects are safe to invoke methods on concurrently; it is not safe to invoke a non-const
method on a given *this
concurrently to any other method on the same *this
.
The handlers – namely the passive-open handler (optional) and the on-error handler (mandatory) – shall be invoked from some unspecified thread that is not any one of the user's calling threads. It is not allowed to invoke *this
APIs from within any such handler. For example, you may not open_channel() from the passive-open handler. Informally, it is recommended that any user handler post its actual handling of the event onto their own thread(s), such as via flow::async::Single_thread_task_loop::post()
.
As summarized above, a Session – at entry to PEER state – is optionally capable of accepting the partner Session's active-open (open_channel()) attempt. The user must make the decision whether to be capable or incapable of performing such a passive-open. If they decide the latter, they must not set the on-passive-open handler; else they must set it by the time PEER state begins. (The mechanism of doing so is unspecified by the concept. The current impls, Server_session and Client_session, use different APIs for this.)
If the choice is made to indeed perform passive-opens (accept other side's active-opens), then suppose the handler H is indeed saved inside *this
(which is in PEER state). Then the following shall be invoked from some unspecified thread that is not one of the user's public API-invoking threads: H(C, M)
, where C
is a Channel_obj onto which the new PEER-state transport::Channel shall be move()
d; while M
is the metadata reader (explained below). Therefore the H signature must be compatible with:
Note that Mdt_reader_ptr is a shared_ptr
to a capnp-generated Reader
to a capnp-struct
parameterized on Mdt_payload_obj. The memory resources taken by the serialization of the metadata (including certain internal zero-copy "baggage") shall be freed once that shared_ptr
ref-count group is empty. As for what the heck this even is: see open_channel()'s metadata arg docs. This is the Reader
counterpart to the Builder
mutated by the opposing open_channel() caller.
open_channel() is used to initiate a channel opening on one side, and the on-passive-open handler is used to accept the same on the other side (for a given channel). The result, on each side, is a Channel_obj, which is a Channel
template instantiation. Note that it is not a struc::Channel
; the user may choose to immediate convert Channel
to struc::Channel
via std::move(), but whether they want to or not is up to them and their desired use case. Now, there are millions of patterns of what the acceptor might want to accept in a session; there could be one channel for some app-wide updates and one per processor core; and possibly more differentiation than that regarding which channel is for what purpose. Meanwhile the only way to know which channel is being opened, other than a set ordering (certainly inconvenient at best in many cases), is to perhaps exchange some information on the channels themselves. That too is inconvenient: even having discovered that a certain channel is for a certain purpose, distributing them around the app is no fun probably. Furthermore, it's only a Channel
: is the user supposed to encode something in a binary blob? Inconvenient. But if one turns it into a struc::Channel
, then that is irreversible and may not be what the user wants. The solution we use: the open_channel() call is issued with a separate (to any structured in-message the user would see) chunk of capnp-structured metadata. This is transmitted and supplied to the on-passive-open handler per the preceding doc section. Details:
It is encoded in a capnp-struct
Metadata
, defined in common.capnp. However that struct
is generically parameterized on another capnp-type of the user's choice. That is the Session template arg Mdt_payload_obj. Hence the user shall decide how to encode the channel-differentiating info on a per-Session basis. capnp shall generate the usual Reader
and Builder
nested classes; the latter to build it to pass into open_channel(); the former to read it, the on-passive-open handler having fired. Call capnp-generated Builder
setters and Reader
getters accordingly on either side.
This feature is optional to use. You may set Mdt_payload_obj to capnp::Void
; omit the metadata argument to open_channel(); and ignore the Reader
counterpart in the on-passive-open handler.
Informal suggestion: While Session concept does not specify anything about clients or servers, as of this writing the client-server model is the only one available, hence Client_session and Server_session are the impls used. Server_session
s are produced, in this model, via Session_server. The latter class template requires that Mdt_payload
be specified as a template arg to be applied to all Server_session objects it produces. Therefore it must be decided not just for each session but realistically for the entire client/server split (in other words the entire meta-application that was split into two): all the Server_session
s; and therefore to all the Client_session
s as well (since the 2 are peers to each other). So the method of signaling channel type (if needed) via metadata is quite a high-level decision. One obvious idea is to maintain a split-wide capnp schema that specifies the struct
to use as Mdt_payload
; in that struct
define perhaps a capnp-enum
type for channel type. Alternatively one can define a top-level anon capnp-union
; its which()
selector can function as an enumeration (in fact it is one in the generated C++); and any further data (e.g., the processor core index, if there's some channel-per-core setup) inside that particular which()
's struct
. Do be careful to (1) maintain backward and forward compatibility as channel types are added over time; and (2) do not abuse the metadata store with large structures. Anything that is not directly used in differentiating between channels should simply be communicated over the channel itself, once it has been routed to the proper area of code; that's what transport::struc::Channel is for in particular.
There are 2 sources of errors: background (async) errors and synchronously from invoking active-open: open_channel(). A given error is either session-hosing (fatal) or not. To make handling error simpler these are the semantics:
Even if, internally, open_channel() in some way triggers a session-hosing error, it shall be emitted as-if it were an async error in a way indistinguishable in practice from any other async error.
When a session-hosing error occurs, it is emitted exactly once (through the on-error handler). After such an event, open_channel() and other APIs shall no-op/return sentinel subsequently if invoked.
This is simpler than how transport::struc::Channel emits errors; there the (always synchronous) send()
can emit an error synchronously that is session-hosing; in addition to incoming-direction session-hosing errors; but still only one of these can occur and only once. So in that sense open_channel() is not like send()
: if it emits an error, *this
is still formally in non-session-hosed state.
However, of course, the user is free to treat an open_channel()-emitted error as fatal and destroy *this
.
Channel
s in PEER state through it. Session
s is a Client_session and a Server_session. Server_session
s in a process, designated as the server in an IPC split, are obtained (following the acceptor pattern) via a single Session_server in that process. On the other side, a Client_session is directly constructed (in NULL state) and enters PEER state via Client_session::sync_connect(). (Conceptually this is similar to the relationship between, e.g., Native_socket_stream (server side), Native_socket_stream_acceptor, and Native_socket_stream (client side). However, it so happens that in the case of Session
s, a different class is used depending on which side the Session sits. Since they both implement the same concept, however, code can be written that behaves identically – given any Session in PEER state – regardless of whether it's really a Client_session or Server_session. That said, such code does have to be templated on the particular Session as a template param.) Mdt_payload | capnp-generated class for the user's capnp-struct of choice for open_channel() transmitting metadata information to be received on the other side's on-passive-open handler. See discussion above. Use capnp::Void if not planning to use this metadata feature. |
using ipc::session::Session< Mdt_payload >::Channel_obj = unspecified |
Each successful open_channel() and on-passive-open handler firing shall yield a concrete transport::Channel instance of this type – in PEER state.
The concept does not specify how the concrete Channel instance type – meaning the template params to Channel
– is determined. In practice it is likely to be controlled by unspecified compile-time knobs, likely additional template params, to the particular Session impl. E.g., see Client_session:S_MQS_ENABLED.
In practice a user is likely to declare Channel
variables by using this alias (or via auto
).
true
. E.g., if it's got a native-socket-stream, it'll be transport::sync_io::Native_socket_stream – not transport::Native_socket_stream. If you want one with transport::Channel::S_IS_ASYNC_IO_OBJ then use transport::Channel::async_io_obj() (or to just get the type at compile time: transport::Channel::Async_io_obj). using ipc::session::Session< Mdt_payload >::Mdt_builder_ptr = shared_ptr<Mdt_builder> |
Ref-counted handle to a capnp-generated Builder
(and the payload it accesses) through which the user shall mutate the open_channel() metadata before invoking open_channel().
mdt_builder() returns a new one; then user mutates it; then user calls open_channel(). To mutate an x
of this type: auto root = x->initPayload();
, then mutate via the root
sub-builder (standard capnp-generated API for whatever fields capnp-struct
Mdt_payload_obj defines).
using ipc::session::Session< Mdt_payload >::Mdt_reader_ptr = shared_ptr<typename transport::struc::schema::Metadata<Mdt_payload_obj>::Reader> |
Ref-counted handle to a capnp-generated Reader
(and the payload it accesses) through which the user shall access the metadata the other side prepared via Mdt_builder_ptr.
On-passive-open handler yields a handle of this type. To access via an x
of this type: auto root = x->getPayload();
, then access via the root
sub-reader (standard capnp-generated API for whatever fields capnp-struct
Mdt_payload_obj defines).
using ipc::session::Session< Mdt_payload >::Structured_channel = transport::struc::Channel<unspecified> |
Convenience alias for the transport::struc::Channel concrete type to use if one desires (1) to upgrade a *this
-generated channel to a struc::Channel
and (2) to efficiently use the built-in capabilities of *this
(notably, if applicable: SHM) for zero-copy performance when sending messages through that channel.
struc::Channel
. (See util::sync_io doc header for background on the 2 patterns, async-I/O and sync_io
.) To obtain the sync_io
-core counterpart type use: Session::Structured_channel::Sync_io_obj
.Use this (parameterized by Message_body
of choice of course) as your transport::struc::Channel concrete type. Then use the appropriate tag-form struc::Channel
ctor. Namely as of this writing:
*_shm_*_session
: Use tag transport::struc::Channel_base::S_SERIALIZE_VIA_APP_SHM or transport::struc::Channel_base::S_SERIALIZE_VIA_SESSION_SHM*_session
: Use tag transport::struc::Channel_base::S_SERIALIZE_VIA_HEAP.Message_body | See transport::struc::Channel. All the other annoying decisions are made for you using this alias; but of course you must still specify the language you shall be speaking over the channel. |
using ipc::session::Session< Mdt_payload >::Structured_msg_builder_config = unspecified |
Convenience alias equal to Structured_channel<M>::Builder_config
(regardless of M
).
See Structured_channel.
Informally: The impl template may choose to provide methods to obtain a Structured_msg_builder_config to use for a Structured_channel::Msg_out
that can be constructed without a concrete Structured_channel available. These methods might in some cases even be static
(in which case they should take a Logger* logger_ptr
1st arg), so that not even having a Session
object in hand is needed.
using ipc::session::Session< Mdt_payload >::Structured_msg_reader_config = unspecified |
Convenience alias equal to Structured_channel<M>::Reader_config
(regardless of M
).
See Structured_channel. The utility of explicit use of this alias by the user is less than Structured_msg_builder_config, as a transport::struc::Msg_in is not constructed directly but by transport::struc::Channel internals.
ipc::session::Session< Mdt_payload >::Session | ( | ) |
Default ctor: creates Session in NULL state (not operational).
Beyond this and the move ctor, other ctors are unspecified by the concept but may well exist. See discussion in concept doc header.
The practical intended use for this ctor is as a target for a subsequent move-to assignment. For example, Session_server::async_accept() would typically be given a default-cted Server_session which, on success, is moved-to to enter PEER state.
ipc::session::Session< Mdt_payload >::Session | ( | Session< Mdt_payload > && | src | ) |
Move ctor: *this
becomes identical to src
; while src
becomes as-if default-cted.
src | Moved-from session that enters NULL state. |
ipc::session::Session< Mdt_payload >::~Session | ( | ) |
In NULL state, no-op; in PEER state: Ends the session while informing (if possible) the opposing peer Session that the present session has ended.
This may incur a short delay before the dtor exits (in practice likely to flush out any pending sends over an internal session master channel, informally speaking).
Any Channel_obj previously yielded by open_channel() and/or passive-opens shall continue operating, if still operating, and it is up to the user to perform any cleanup/flushing/etc. on these. This is consistent with the idea that open_channel() and passive-open passes ownership of such channels to the user.
There are 2 mutually exclusive expected triggers for why this dtor would be invoked:
*this
informs user of this via emitted error (see concept doc header regarding session-hosing error emission semantics); therefore *this
is no longer usable; hence user invokes *this
dtor. It tries to inform the other side, but there's nothing to inform; it quickly returns. Mdt_builder_ptr ipc::session::Session< Mdt_payload >::mdt_builder | ( | ) |
Returns a new metadata holder to be subsequently mutated by the user and then passed to open_channel() to initiate active-open attempt.
The payload (see Mdt_payload_obj) is left uninitialized: the user would likely x->initPayload()
and then mutate stuff inside that (where x
was returned).
If *this
is not in PEER state, returns null.
This call is not needed, if one plans to use open_channel() overload that takes no metadata.
bool ipc::session::Session< Mdt_payload >::open_channel | ( | Channel_obj * | target_channel, |
const Mdt_builder_ptr & | mdt, | ||
Error_code * | err_code = 0 |
||
) |
Synchronously active-opens a new channel which, on success, is moved-to *target_channel
.
No-op and return false
if *this
is not in PEER state, the session is hosed by a previous error, or mdt
is null. Else returns true
.
mdt
shall be from an mdt_builder() call, and mdt
must not have been passed to open_channel() previously; otherwise behavior undefined. See also the open_channel() overload that takes no mdt
.
*target_channel
is of a sync_io
-core-bearing type. However, on successful open_channel() you can trivially obtain an async-I/O version via target_channel->async_io_obj()
.That's a matter of perspective; but formally it must be either non-blocking or, if blocking, then limited to a timeout no greater than seconds (not minutes). That said, informally, the implementation shall strive to make this quite fast, enough so to be considered non-blocking, as long as both Session objects are in PEER state.
If false
is returned, even if open_channel() internally triggered a session-hosing condition, it shall be fired through the on-error handler and not emitted by open_channel().
If true
is returned an error may be detected and is then emitted via standard Flow semantics (if err_code
then set *err_code
to error; otherwise throw). Such an error is not session-hosing.
In particular if passive-opens are disabled on the opposing side it must emit error::Code::S_SESSION_OPEN_CHANNEL_REMOTE_PEER_REJECTED_PASSIVE_OPEN. Informally: an impl is likely to be capable of emitting the non-fatal error::Code::S_SESSION_OPEN_CHANNEL_ACTIVE_TIMEOUT, though the user is reasonably likely to want to treat it as fatal in practice and end the session.
target_channel | On success *target_channel is moved-to from a (new) Channel_obj in PEER state. |
mdt | See mdt_builder(). If mdt is null, open_channel() returns false . |
err_code | See flow::Error_code docs for error reporting semantics. Error_code generated: error::Code::S_SESSION_OPEN_CHANNEL_REMOTE_PEER_REJECTED_PASSIVE_OPEN, other possible unspecified errors according to impl's discretion. See above discussion. |
bool ipc::session::Session< Mdt_payload >::open_channel | ( | Channel_obj * | target_channel, |
Error_code * | err_code = 0 |
||
) |
Identical to open_channel(target_channel, mdt_builder(), err_code)
; in other words attempts to open channel with an uninitialized channel-open metadata payload.
Informally: this overload can be used if differentiation among channels, for the passive-open side's benefit, is not necessary. In that case Mdt_payload_obj is typically set to capnp::Void
.
*target_channel
is of a sync_io
-core-bearing type. However, on successful open_channel() you can trivially obtain an async-I/O version via target_channel->async_io_obj()
.target_channel | See other open_channel(). |
err_code | See other open_channel(). |
false
due to an empty mdt
, as mdt
is internally generated (and left uninitialized). Session & ipc::session::Session< Mdt_payload >::operator= | ( | Session< Mdt_payload > && | src | ) |
Move assignment: acts as-if *this
dtor executed; then *this
becomes identical to src
; while src
becomes as-if default-cted.
No-op if &src == this
.
src | Moved-from session that enters NULL state (unless &src == this ). |
*this
. const Session_token & ipc::session::Session< Mdt_payload >::session_token | ( | ) | const |
In PEER state: Returns the (non-nil) logged-in session token to be used for any struc::Channel
s one may wish to upgrade from Channel_obj channels yielded by open_channel() or passive-opens; or nil if in NULL state, or if *this
session is hosed from a prior error.
Informal context: Firstly see transport::struc::Channel::session_token() and class template doc header. However note that open_channel() (active-open) and on-passive-open handler yields an unstructured, unused Channel_obj: it is entirely optional that the user then upgrade it to a struc::Channel
. If one does so, however, then a non-nil session-token shall be required and, for safety, must equal what this session_token() returns.
|
staticconstexpr |
Specifies whether this Session
concept implementation provides support for zero-copy via SHM plus direct access to SHM arenas and lending/borrowing within them.
May be useful for generic programming; perhaps in std::conditional
or if constexpr()
uses.
|
staticconstexpr |
Specifies the SHM-provider for which this Session
concept implementation provides support; or NONE
if this is a vanilla Session
that does not provide SHM-based zero-copy support.
May be useful for generic programming; perhaps in std::conditional
or if constexpr()
uses. Informally we recommend against frequent use of this except logging/reporting/alerting and debug/test scenarios.