3 unstable releases
0.2.1 | Sep 26, 2024 |
---|---|
0.2.0 | Sep 10, 2024 |
0.1.0 | Oct 2, 2023 |
#1032 in Concurrency
248 downloads per month
Used in 2 crates
28KB
255 lines
Micro-Message Passing Library
The ump-ng crate is a simple client/server message passing library for intra-process communication. Its primary purpose is to allow cross async/non-async communication (for both the server and client endpoints).
ump-ng is similar to ump, but it has a uni-directional message passing operation.
lib.rs
:
Micro Message Pass: Next Generation (ump-ng) is a library for passing
messages between thread/tasks. It is similar to the ump
library, but
with an added uni-directional message passing primitive.
The primary purpose of ump(-ng) is to create simple RPC-like designs, but between threads/tasks within a process rather than between processes over networks.
High-level usage overview
An application calls channel
to create a linked pair of a Server
and a Client
.
The server calls [Server::wait()
]/[Server::async_wait()
], which
blocks and waits for an incoming message from a client.
A client, in a separate thread or task, calls [Client::post()
] to send a
unidirectional message to the server, or [Client::req()
]/
[Client::areq()
] to send a message to the server and wait for a reply.
The server's wait call returns either a post message or a request
message that consist a pair of a message and a ReplyContext
that is
used to send a reply back to the client.
After processing its application-defined message, the server must call
the [ReplyContext::reply()
] on the returned reply context object to
return a reply message to the client.
Typically the server calls wait again to wait for next message from a client.
The client receives the reply from the server and processes it.
Example
use std::thread;
use ump_ng::{channel, MsgType};
let (server, client) = channel::<String, String, String, ()>();
let server_thread = thread::spawn(move || {
// Wait for data to arrive from a client
loop {
println!("Server waiting for message ..");
match server.wait().unwrap() {
MsgType::Post(data) => {
println!("Server received Post: '{}'", data);
}
MsgType::Request(data, rctx) => {
println!("Server received Request: '{}'", data);
// Process data from client
// Reply to client
let reply = format!("Hello, {}!", data);
println!("Server replying '{}'", reply);
rctx.reply(reply);
break;
}
}
}
println!("Server done");
});
let msg = String::from("Client");
println!("Client putting '{}'", msg);
let reply = client.post(msg).unwrap();
let msg = String::from("Client");
println!("Client requesting '{}'", msg);
let reply = client.req(msg).unwrap();
println!("Client received reply '{}'", reply);
println!("Client done");
server_thread.join().unwrap();
In practice the send/reply types will probably be enum
s used to
indicate command/return type with associated data. The third type argument
to channel
is an error type that can be used to explicitly pass errors
back to the sender.
Semantics
There are some potentially useful semantic quirks that can be good to know about, but some of them should be used with caution. This section will describe some semantics that you can rely on, and others that you should be careful about relying on.
Stable invariants
These are behaviors which should not change in future versions.
- The reply contexts are independent of the
Server
context. This has some useful implications for server threads that spawn separate threads to process messages and return replies: The server can safely terminate while there are clients waiting for replies (implied: the server can safely terminate while there are reply contexts in-flight). - A cloned client is paired with the same server as its origin, but in all other respects the clone and its origin are independent of each other.
- A client can be moved to a new thread.
- Any permutation of sync/async server/clients can be combined.
async
code must use the async method variants when available.
Unstable invariants
These are invariants you can trust will work in the current version, but they exist merely as a side-effect of the current implementation. Avoid relying on these if possible.
- A single client can be used from two different threads. If a
Client
object in placed in an Arc, is cloned and passed to another thread/task then both the clone and the original can be used simultaneously. In the future this may not be allowed. It is recommended that a new clone of the client be created instead. - Put/Request messages arrive in the same order they were added to the queue. In future versions one type may be prioritized over the other.
Dependencies
~1.1–6MB
~26K SLoC