ump_ng/
lib.rs

1//! Micro Message Pass: Next Generation (ump-ng) is a library for passing
2//! messages between thread/tasks.  It is similar to the `ump` library, but
3//! with an added uni-directional message passing primitive.
4//!
5//! The primary purpose of ump(-ng) is to create simple RPC-like designs, but
6//! between threads/tasks within a process rather than between processes over
7//! networks.
8//!
9//! # High-level usage overview
10//! An application calls [`channel`] to create a linked pair of a [`Server`]
11//! and a [`Client`].
12//!
13//! The server calls [`Server::wait()`]/[`Server::async_wait()`], which
14//! blocks and waits for an incoming message from a client.
15//!
16//! A client, in a separate thread or task, calls [`Client::post()`] to send a
17//! unidirectional message to the server, or [`Client::req()`]/
18//! [`Client::areq()`] to send a message to the server and wait for a reply.
19//!
20//! The server's wait call returns either a _post_ message or a _request_
21//! message that consist a pair of a message and a [`ReplyContext`] that is
22//! used to send a reply back to the client.
23//!
24//! After processing its application-defined message, the server *must* call
25//! the [`ReplyContext::reply()`] on the returned reply context object to
26//! return a reply message to the client.
27//!
28//! Typically the server calls wait again to wait for next message from a
29//! client.
30//!
31//! The client receives the reply from the server and processes it.
32//!
33//! # Example
34//! ```
35//! use std::thread;
36//!
37//! use ump_ng::{channel, MsgType};
38//!
39//! let (server, client) = channel::<String, String, String, ()>();
40//!
41//! let server_thread = thread::spawn(move || {
42//!   // Wait for data to arrive from a client
43//!   loop {
44//!     println!("Server waiting for message ..");
45//!     match server.wait().unwrap() {
46//!       MsgType::Post(data) => {
47//!         println!("Server received Post: '{}'", data);
48//!       }
49//!       MsgType::Request(data, rctx) => {
50//!         println!("Server received Request: '{}'", data);
51//!
52//!         // Process data from client
53//!
54//!         // Reply to client
55//!         let reply = format!("Hello, {}!", data);
56//!         println!("Server replying '{}'", reply);
57//!         rctx.reply(reply);
58//!         break;
59//!       }
60//!     }
61//!   }
62//!
63//!   println!("Server done");
64//! });
65//!
66//! let msg = String::from("Client");
67//! println!("Client putting '{}'", msg);
68//! let reply = client.post(msg).unwrap();
69//!
70//! let msg = String::from("Client");
71//! println!("Client requesting '{}'", msg);
72//! let reply = client.req(msg).unwrap();
73//! println!("Client received reply '{}'", reply);
74//!
75//! println!("Client done");
76//!
77//! server_thread.join().unwrap();
78//! ```
79//! In practice the send/reply types will probably be `enum`s used to
80//! indicate command/return type with associated data.  The third type argument
81//! to [`channel`] is an error type that can be used to explicitly pass errors
82//! back to the sender.
83//!
84//! # Semantics
85//! There are some potentially useful semantic quirks that can be good to know
86//! about, but some of them should be used with caution.  This section will
87//! describe some semantics that you can rely on, and others that you should be
88//! careful about relying on.
89//!
90//! ## Stable invariants
91//!
92//! These are behaviors which should not change in future versions.
93//!
94//! - The reply contexts are independent of the `Server` context.  This has
95//!   some useful implications for server threads that spawn separate threads
96//!   to process messages and return replies:  *The server can safely terminate
97//!   while there are clients waiting for replies* (implied: the server can
98//!   safely terminate while there are reply contexts in-flight).
99//! - A cloned client is paired with the same server as its origin, but in all
100//!   other respects the clone and its origin are independent of each other.
101//! - A client can be moved to a new thread.
102//! - Any permutation of sync/async server/clients can be combined.  `async`
103//!   code must use the async method variants when available.
104//!
105//! ## Unstable invariants
106//!
107//! These are invariants you can trust will work in the current version, but
108//! they exist merely as a side-effect of the current implementation.  Avoid
109//! relying on these if possible.
110//!
111//! - A single client can be used from two different threads.  If a `Client`
112//!   object in placed in an Arc, is cloned and passed to another thread/task
113//!   then both the clone and the original can be used simultaneously.  In the
114//!   future this may not be allowed. It is recommended that a new clone of the
115//!   client be created instead.
116//! - Put/Request messages arrive in the same order they were added to the
117//!   queue.  In future versions one type may be prioritized over the other.
118
119mod client;
120mod err;
121mod rctx;
122mod server;
123
124pub use err::Error;
125
126pub use crate::{
127  client::{Client, Post as PostClient, WaitReply, Weak as WeakClient},
128  rctx::ReplyContext,
129  server::{MsgType, Server}
130};
131
132/// Create a pair of linked [`Server`] and [`Client`] objects.
133///
134/// The [`Server`] object is used to wait for incoming messages from connected
135/// clients.  Once a message arrives it must reply to it using a
136/// [`ReplyContext`] that's returned to it in the same call that returned the
137/// message.
138///
139/// The [`Client`] object can be used to send messages to the [`Server`].  The
140/// [`Client::req()`] call will not return until the server has replied.
141///
142/// Clients can be [cloned](Client::clone()); each clone will create a
143/// new client object that is connected to the same server object, but is
144/// completely independent of the original client.
145///
146/// The `S` type parameter is the "send" data type that clients will transfer
147/// to the server.  The `R` type parameter is the "receive" data type that
148/// clients will receive from the server.  The `E` type parameter can be used
149/// to return application specific errors from the server to the client.
150#[allow(clippy::type_complexity)]
151#[must_use]
152pub fn channel<P, S, R, E>() -> (Server<P, S, R, E>, Client<P, S, R, E>) {
153  let (qpusher, qpuller) = sigq::new();
154
155  (Server(qpuller), Client(qpusher))
156}
157
158// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :