Flow-IPC 1.0.2
Flow-IPC project: Public API.
Public Types | Public Member Functions | Related Functions | List of all members
ipc::session::Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload > Class Template Reference

To be instantiated typically once in a given process, an object of this type asynchronously listens for Client_app processes each of which wishes to establish a session with this server process; emits resulting Server_session objects locally. More...

#include <session_server.hpp>

Inherits Session_server_impl< Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >, Server_session< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload > >.

Public Types

using Server_session_obj = Server_session< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >
 Short-hand for the concrete Server_session-like type emitted by async_accept().
 
using Mdt_reader_ptr = typename Impl::Mdt_reader_ptr
 Short-hand for Session_mv::Mdt_reader_ptr.
 
using Mdt_builder = typename Server_session_obj::Mdt_builder
 Metadata builder type passed to mdt_load_func() in advanced async_accept() overload.
 
using Channels = typename Impl::Channels
 Short-hand for Session_mv::Channels.
 

Public Member Functions

 Session_server (flow::log::Logger *logger_ptr, const Server_app &srv_app_ref, const Client_app::Master_set &cli_app_master_set_ref, Error_code *err_code=0)
 Constructor: immediately begins listening for incoming session-open attempts from allowed clients. More...
 
 ~Session_server ()
 Destroys this acceptor which will stop listening in the background and cancel any pending completion handlers by invoking them ASAP with session::error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER. More...
 
template<typename Task_err >
void async_accept (Server_session_obj *target_session, Task_err &&on_done_func)
 Asynchronously awaits for an opposing Client_session to request session establishment and calls on_done_func(), once the connection occurs and log-in exchange completes, or an error occurs, in the former case move-assigning an almost-PEER-state Server_session object to the passed-in Server_session *target_session. More...
 
template<typename Task_err , typename N_init_channels_by_srv_req_func , typename Mdt_load_func >
void async_accept (Server_session_obj *target_session, Channels *init_channels_by_srv_req, Mdt_reader_ptr *mdt_from_cli_or_null, Channels *init_channels_by_cli_req, N_init_channels_by_srv_req_func &&n_init_channels_by_srv_req_func, Mdt_load_func &&mdt_load_func, Task_err &&on_done_func)
 Identical to the simpler async_accept() overload but offers added advanced capabilities: metadata exchange; initial-channel opening. More...
 
void to_ostream (std::ostream *os) const
 Prints string representation to the given ostream. More...
 

Related Functions

(Note that these are not member functions.)

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload >
std::ostream & operator<< (std::ostream &os, const Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload > &val)
 Prints string representation of the given Session_server to the given ostream. More...
 

Detailed Description

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload>
class ipc::session::Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >

To be instantiated typically once in a given process, an object of this type asynchronously listens for Client_app processes each of which wishes to establish a session with this server process; emits resulting Server_session objects locally.

When to use

In the ipc::session paradigm: See Server_session doc header "When to use" section. If and only if, based on that text, you wish to use this application to instantiate Server_sessions, then you must instantiate a single Session_server to emit them. (It is not possible to directly construct a PEER-state – i.e., at all useful – Server_session.)

Note that while in a typical (non-test/debug) scenario one would only instantiate a single Session_server, that does not mean it cannot or should not instantiate Client_sessions as well. In a given split, a given application is either the server or the client; but in a different split it be the other thing. So a Session_server handles all the splits in which this application is the server; but for other splits one would instantiate 0+ Client_sessions separately.

How to use

Similarly to transport::Native_socket_stream_acceptor constructing *this immediately async-listens to incoming connections (each an attempt to establish a opposing Client_session to partner with a new local Server_session). async_accept() shall accept such connections, one at a time, in FIFO order (including any that may have queued up in the meantime, including after mere construction). The meaning of FIFO order is discussed below.

Once a Server_session is emitted to a handler passed to async_accept(), refer to Server_session doc header for how to operate it. Spoiler alert: you must still call Server_session::init_handlers(); at which point the Server_session is a Session concept impl in PEER state.

Error handling

The model followed is the typical boost.asio-like one: Each async_accept() takes a handler, F, and it is eventually invoked no matter what: either with a falsy (success) Error_code, or with a truthy (fail) one. If the op does not complete, and one invokes the *this dtor, then F() is invoked at dtor time with a particular operation-aborted Error_code.

That said, unlike with (say) transport::Native_socket_stream_acceptor – and many other classes in ipc and boost.asio – the failure of a given async_accept() does not formally hose *this. Formally, other async_accept(), including future ones, may succeed. Rationale:

Informally there are a few ways one could deal with each given error.

As of this writing I (ygoldfel) lack sufficient data in the field to make certain pronouncements.

Relationship with Server_app and Client_app; relationship with file system

This class operates at quite a high level; there should be (outside of test/debug) only on of them in a process. There are some important relationships to understand:

As of this writing we expect Session_server to be in charge of maintaining the process's only PID file, in the sense that a typical daemon is expected to maintain a general-use PID file, with the usual format (where the file contains a decimal encoding of the PID plus a newline). If this is not acceptable – i.e., we don't want the ipc system to "step" on some other mechanism for PID files already in use – then the location may be tweaked, at least via Server_app::m_kernel_persistent_run_dir_override or possibly with a different default than the current choice (/var/run).

Thread safety; handler invocation

After construction of *this, concurrent access to API methods and the dtor is not safe, unless all methods being invoked concurrently are const.

You may invoke async_accept() directly from within another async_accept()-passed handler. (Informally we suggest you post all real handling onto your own thread(s).)

However: async_accept()-passed user handlers may be invoked concurrently to each other (if 2+ async_accept()s are outstanding). It is your responsibility, then, to ensure you do not invoke 2+ async_accept()s concurrently. (One way is to only have only 1 outstanding at any given time. Another is to post all handling on your own thread. Really informally we'd say it's best to do both.)

Subtlety about "FIFO order" w/r/t async_accept()

Above we promise that async_accept() shall emit ready Server_sessionss in "FIFO" order. The following expands on that, for completeness, even though it is unlikely a typical user will care. The FIFO order is subtly different from that in, say, Native_socket_stream_acceptor or boost.asio's transport::asio_local_stream_socket::Acceptor. To wit:

Suppose async-accept request R1 was followed by R2. As a black box, it is possible that the handler for R2 will fire before the handler for R1. I.e., the "first out" refers to something different than the order of emission. Rather: each given request R consists of 2 steps internally (listed here in the public section only for exposition):

  1. Accept a Unix domain socket connection. Immediately upon it being accepted:
  2. Execute a log-in exchange on that connection, including things like Client_app identification and verification and a session token thing.

The "FIFO order" refers to the first step. Request R1 shall indeed handle the first incoming socket connection; R2 the second: first-come, first-served. However, the log-in exchange must still occur. In practice this should be quite quick: Client_session will immediately ping us, and we'll ping them back, and that's that. However, it is possible that (for whatever reason) R2 will win the race against R1 despite a head start for the latter. Therefore, it won't look quite like FIFO, though probably it will – and if not, then close enough.

Rationale: Honestly: It really has to do with simplicity of implementation (discussed in the impl section below but of little interest to the user and not included in the public-API Doxygen-generated docs). Implementing it that way was simpler and easier to maintain; probably the perf less latent too. The fact that, as a result, the FIFO order might get sometimes switched around did not seem important in practice. Still it is worth remarking upon formally.

Informally: are there any practical concerns about this for the user? It seems unlikely. It is, one supposes, sort-of conceivable that some system would rely on the ordering of incoming sessions being requested to be significant in some larger inter-application algorithm. However, 1, that sounds highly unorthodox to put it mildly (more like an abuse of the ipc::session system's versus its intended usefulness); and 2, even then one could then simply issue async_accept() R2 upon the success of async_accept() R1, if an ordering guarantee is really required.

Template Parameters
S_MQ_TYPE_OR_NONEEmitted Server_sessions shall have the concrete typed based on this value for S_MQ_TYPE_OR_NONE. See Server_session_obj.
S_TRANSMIT_NATIVE_HANDLESEmitted Server_sessions shall have the concrete typed based on this value for S_TRANSMIT_NATIVE_HANDLES. See Server_session_obj.
Mdt_payloadEmitted Server_sessions shall have the concrete typed based on this value for Mdt_payload. See Server_session_obj. In addition the same type may be used for mdt_from_cli_or_null (and srv->cli counterpart) in async_accept(). (Recall that you can use a capnp-union internally for various purposes.)

Constructor & Destructor Documentation

◆ Session_server()

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload >
ipc::session::Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >::Session_server ( flow::log::Logger *  logger_ptr,
const Server_app srv_app_ref,
const Client_app::Master_set cli_app_master_set_ref,
Error_code err_code = 0 
)
explicit

Constructor: immediately begins listening for incoming session-open attempts from allowed clients.

This will write to the CNS (PID file) and then listen on Unix domain server socket whose abstract address is based off its contents (as explained in the class doc header).

If either step fails (file error, address-bind error being the most likely culprits), an error is emitted via normal Flow error semantics. If this occurs, via the non-exception-throwing invocation style, then you must not call async_accept(); or behavior is undefined (assertion may trip).

Parameters
logger_ptrLogger to use for logging subsequently.
srv_app_refProperties of this server application. The address is copied; the object is not copied.
cli_app_master_set_refThe set of all known Client_apps. The address is copied; the object is not copied. Technically, from our POV, it need only list the Client_apps whose names are in srv_app_ref.m_allowed_client_apps. Refer to App doc header for best practices on maintaining this master list in practice.
err_codeSee flow::Error_code docs for error reporting semantics. Error_code generated: interprocess-mutex-related errors (probably from boost.interprocess) w/r/t writing the CNS (PID file); file-related system errors w/r/t writing the CNS (PID file) (see class doc header for background); errors emitted by transport::Native_socket_stream_acceptor ctor (see that ctor's doc header; but note that they comprise name-too-long and name-conflict errors which ipc::session specifically exists to avoid, so you need not worry about even the fact there's an abstract Unix domain socket address involved).

◆ ~Session_server()

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload >
ipc::session::Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >::~Session_server ( )
default

Destroys this acceptor which will stop listening in the background and cancel any pending completion handlers by invoking them ASAP with session::error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER.

Subtlety: expect session::error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER, not transport::error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER, regardless of the fact that internally a transport::Native_socket_stream_acceptor is used.

You must not call this from directly within a completion handler; else undefined behavior.

Each pending completion handler will be called from an unspecified thread that is not the calling thread. Any associated captured state for that handler will be freed shortly after the handler returns.

We informally but very strongly recommend that your completion handler immediately return if the Error_code passed to it is error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER. This is similar to what one should do when using boost.asio and receiving the conceptually identical operation_aborted error code to an async_...() completion handler. In both cases, this condition means, "we have decided to shut this thing down, so the completion handlers are simply being informed of this."

Member Function Documentation

◆ async_accept() [1/2]

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload >
template<typename Task_err , typename N_init_channels_by_srv_req_func , typename Mdt_load_func >
void ipc::session::Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >::async_accept ( Server_session_obj target_session,
Channels init_channels_by_srv_req,
Mdt_reader_ptr mdt_from_cli_or_null,
Channels init_channels_by_cli_req,
N_init_channels_by_srv_req_func &&  n_init_channels_by_srv_req_func,
Mdt_load_func &&  mdt_load_func,
Task_err &&  on_done_func 
)

Identical to the simpler async_accept() overload but offers added advanced capabilities: metadata exchange; initial-channel opening.

The other overload is identical to async_accept(target_session, nullptr, nullptr, nullptr, ...N/A..., NO_OP_FUNC, on_done_func) (where NO_OP_FUNC() no-ops) and requires the opposing sync_connect() to similarly not use the corresponding features.

The advanced capabilities complement/mirror the ones on Client_session_mv::sync_connect() (advanced overload); see its doc header first. Then come back here. The same mechanisms underly the following; but due to client-server asymmetry the API is somewhat different.

Server->client metadata exchange

Server may wish to provide information in some way, but without a structured channel – or any channel – yet available, this provides an opportunity to do so in a structured way with a one-off message available at session open, together with the opposing Session object itself.

The metadata payload you may wish to load to emit to the opposing Client_session::sync_connect() may depend on the following information available during the session-open procedure, namely upon receiving (internally) the log-in request from the client side:

  • the Client_app that wishes to open the session;
  • how many init-channels the client is requesting be opened (see below);
  • the client->Server metadata (see below).

Thus supply Mdt_load_func mdt_load_func arg which takes all 3 of these data, plus a blank capnp Mdt_payload structure to fill; and loads the latter as desired.

To omit using this feature do nothing in mdt_load_func(). (The other async_accept() overload provides such a no-op function.) Suggest this value in that case: [](auto&&...) {}.

Client->server metadata exchange

This is the reverse of the above. Whatever the opposing client chose to supply as client->server metadata shall be deserializable at *mdt_from_cli_or_null once (and if) on_done_func(Error_code()) (successful accept) fires. If mdt_from_cli_or_null is null, the cli->srv metadata shall be ignored.

Init-channels by server request

Once the session is open, open_channel() and the on-passive-open handler may be used to open channels at will. In many use cases, however, a certain number of channels is required immediately before work can really begin (and, frequently, no further channels are even needed). For convenience (to avoid asynchrony/boiler-plate) the init-channels feature will pre-open a requested # of channels making them available right away, together with the freshly-open session – to both sides.

The server may request 0 or more init-channels. They shall be opened and placed into *init_channels_by_srv_req. The number of channels requested may depend on the 3 piece of info outlined above in "Server->client metadata exchange." Thus supply N_init_channels_by_srv_req_func n_init_channels_by_srv_req_func arg which takes all 3 of these data and returns the size_t channel count (possibly 0). The resulting channels shall be loaded into *init_channels_by_srv_req before successful on_done_func() execution.

If and only if n_init_channels_by_srv_req_func() would always return 0, you may provide a null init_channels_by_srv_req. n_init_channels_by_srv_req_func will then be ignored. Suggest this value in that case: [](auto&&...) -> size_t { return 0; }

Init-channels by client request

This is the reverse of the above. The opposing side shall request 0 or more init-channels-by-client-request; that number of channels shall be opened; and they will be placed into *init_channels_by_cli_req which shall be ->resize()d accordingly, once (and if) on_done_func(Error_code()) (successful connect) fires.

init_channels_by_srv_cli being null is allowed, but only if the opposing server requests 0 init-channels-by-server-request. Otherwise an error shall be emitted (see below).

Template Parameters
Task_errSee other async_accept() overload.
N_init_channels_by_srv_req_funcSee above: function type matching signature size_t F(const Client_app&, size_t n_init_channels_by_cli_req, Mdt_reader_ptr&& mdt_from_cli).
Mdt_load_funcSee above: function type matching signature void F(const Client_app&, size_t n_init_channels_by_cli_req, Mdt_reader_ptr&& mdt_from_cli, Mdt_builder* mdt_from_srv).
Parameters
target_sessionSee other async_accept() overload.
init_channels_by_srv_reqSee above: null or pointer to container of Channel_obj which shall be .clear()ed and replaced by a container of PEER-state Channel_obj on success the number being determined by n_init_channels_by_srv_req_func(). null is treated as-if n_init_channels_by_srv_req_func() == 0 for any input.
mdt_from_cli_or_nullSee above: null or pointer to Reader of metadata which shall be set for access on success.
init_channels_by_cli_reqSee above: null or pointer to container of Channel_obj which shall be .clear()ed and replaced by a container of PEER-state Channel_obj on success the number being specified by the opposing (client) side. The number may be zero. null is allowed if and only if the number is zero; otherwise error::Code::S_INVALID_ARGUMENT is emitted.
n_init_channels_by_srv_req_funcSee N_init_channels_by_srv_req_func. Ignored on null init_channels_by_srv_req.
mdt_load_funcSee Mdt_load_func.
on_done_funcSee other async_accept() overload. Note the above target (pointer) args are touched only if falsy Error_code is passed to this handler.

◆ async_accept() [2/2]

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload >
template<typename Task_err >
void ipc::session::Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >::async_accept ( Server_session_obj target_session,
Task_err &&  on_done_func 
)

Asynchronously awaits for an opposing Client_session to request session establishment and calls on_done_func(), once the connection occurs and log-in exchange completes, or an error occurs, in the former case move-assigning an almost-PEER-state Server_session object to the passed-in Server_session *target_session.

on_done_func(Error_code()) is called on success. on_done_func(E), where E is a non-success error code, is called otherwise. In the latter case *this may continue operation, and further async_accept()s may succeed. See class doc header regarding error handling.

Multiple async_accept() calls can be queued while no session-open is pending; they will grab incoming connections in FIFO fashion as they arrive, but log-ins may race afterwards, resulting in not-quite-FIFO emission via handler. See class doc header regarding FIFO ordering.

The aforementioned Server_session generated and move-assigned to *target_session on success shall inherit this->get_logger() as its ->get_logger().

on_done_func() shall be called from some unspecified thread, not the calling thread and possibly concurrently with other such completion handlers. Your implementation must be non-blocking. Informally we recommend you place the true on-event logic onto some task loop of your own; so ideally it would consist of essentially a single post(F) statement of some kind.

You must not call this from directly within a completion handler; else undefined behavior.

Error_code generated and passed to on_done_func(): session::error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER (destructor called, canceling all pending ops; spiritually identical to boost::asio::error::operation_aborted), other system codes most likely from boost::asio::error or boost::system::errc (but never would-block), indicating the underlying transport::Native_socket_stream_acceptor is hosed for that specific reason, those returned by transport::Native_socket_stream::remote_peer_process_credentials(), those emitted by transport::struc::Channel::send(), those emitted by transport::struc::Channel via on-error handler (most likely transport::error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE indicating graceful shutdown of opposing process coincidentally during log-in procedure, prematurely ending session while it was starting), error::Code::S_SERVER_MASTER_LOG_IN_REQUEST_CLIENT_APP_DISALLOWED_OR_UNKNOWN, error::Code::S_SERVER_MASTER_LOG_IN_REQUEST_CLIENT_APP_INCONSISTENT_CREDS, error::Code::S_SERVER_MASTER_LOG_IN_REQUEST_CONFIG_MISMATCH, error::Code::S_INVALID_ARGUMENT (other side expected other async_accept() overload with non-null init_channels_by_cli_req arg), error::Code::S_SESSION_OPEN_CHANNEL_SERVER_CANNOT_PROCEED_RESOURCE_UNAVAILABLE (unable to acquire init-channel resources – as of this writing MQ-related ones).

Error handling discussion

See class doc header regarding error handling, then come back here. The last 3 specific Error_code values listed can be considered, informally, individual misbehavior on the part of the opposing client process. While indicating potentially serious misconfiguration in the system, surely to be investigated ASAP, it is conceivable to continue attempting further async_accept()s.

Do note, though, that this is not a network client-server situation. That is, probably, one should not expect lots of – or any – such problems in a smoothly functioning IPC universe on a given production server machine. E.g., there could be a security problem, or perhaps more likely there's a software mismatch between client and server w/r/t their master App sets. Your software deployment story may or may not be designed to allow for this as a transient condition during software upgrades and such.

Template Parameters
Task_errHandler type matching signature of flow::async::Task_asio_err.
Parameters
target_sessionPointer to Server_session which shall be assigned an almost-PEER-state (open, requires Server_session::init_handlers() to enter PEER state) as on_done_func() is called. Not touched on error.
on_done_funcCompletion handler. See above. The captured state in this function object shall be freed shortly upon its completed execution from the unspecified thread.

◆ to_ostream()

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload >
void ipc::session::Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload >::to_ostream ( std::ostream *  os) const

Prints string representation to the given ostream.

Parameters
osStream to which to write.

Friends And Related Function Documentation

◆ operator<<()

template<schema::MqType S_MQ_TYPE_OR_NONE, bool S_TRANSMIT_NATIVE_HANDLES, typename Mdt_payload >
std::ostream & operator<< ( std::ostream &  os,
const Session_server< S_MQ_TYPE_OR_NONE, S_TRANSMIT_NATIVE_HANDLES, Mdt_payload > &  val 
)
related

Prints string representation of the given Session_server to the given ostream.

Parameters
osStream to which to write.
valObject to serialize.
Returns
os.

The documentation for this class was generated from the following files: