Index: Cargo.toml ================================================================== --- Cargo.toml +++ Cargo.toml @@ -1,8 +1,8 @@ [package] name = "ump-server" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "0BSD" categories = [ "concurrency", "asynchronous" ] keywords = [ "channel", "threads", "sync", "message-passing" ] repository = "https://repos.qrnch.tech/pub/ump-server" @@ -19,13 +19,18 @@ [features] default = ["tokio"] tokio = ["dep:tokio", "dep:async-trait"] [dependencies] -async-trait = { version = "0.1.73", optional = true } -tokio = { version = "1.32.0", features = ["rt"], optional = true } +async-trait = { version = "0.1.77", optional = true } +# ToDo: Shouldn't need "net", but without it the docs will not build. +# Once this is fixed in tokio, remove "net". +tokio = { version = "1.35.1", features = ["net", "rt"], optional = true } ump = { version = "0.12.1" } +[dev-dependencies] +tokio-test = { version = "0.4.3" } + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] Index: README.md ================================================================== --- README.md +++ README.md @@ -1,4 +1,5 @@ # Server message dispatch loop for ump -The _ump-server_ crate is a server message dispatch abstraction for ump. +The _ump-server_ crate is a server message dispatch abstraction for +[ump](https://crates.io/crates/ump). Index: src/task.rs ================================================================== --- src/task.rs +++ src/task.rs @@ -1,6 +1,54 @@ //! ump server running in an async task. +//! +//! ``` +//! # tokio_test::block_on(async { +//! use std::ops::ControlFlow; +//! use ump_server::{ +//! async_trait, +//! task::{Handler, spawn}, +//! ump::ReplyContext +//! }; +//! enum Request { +//! Add(usize, usize) +//! } +//! enum Reply { +//! Sum(usize) +//! } +//! enum MyError { } +//! struct MyHandler {}; +//! #[async_trait] +//! impl Handler for MyHandler { +//! async fn proc_req( +//! &mut self, +//! msg: Request, +//! rctx: ReplyContext +//! ) -> ControlFlow<(), ()> { +//! match msg { +//! Request::Add(a, b) => { +//! rctx.reply(Reply::Sum(a + b)); +//! ControlFlow::Continue(()) +//! } +//! } +//! } +//! } +//! +//! let (clnt, jh) = spawn(|clnt| { +//! MyHandler { } +//! }); +//! +//! let Ok(Reply::Sum(sum)) = clnt.areq(Request::Add(3, 7)).await else { +//! panic!("Unexpected reply"); +//! }; +//! assert_eq!(sum, 10); +//! +//! // Dropping the only client will terminate the dispatch loop +//! drop(clnt); +//! +//! let _ = jh.await; +//! # }); +//! ``` use std::ops::ControlFlow; use tokio::task::{self, JoinHandle}; @@ -52,20 +100,24 @@ /// Run a task which will process incoming messages from an ump server /// end-point. /// /// See top module's documentation for an overview of the [dispatch /// loop](crate#dispatch-loop). -pub fn spawn( - mut handler: impl Handler + Send + 'static +pub fn spawn( + hbldr: impl FnOnce(&Client) -> F ) -> (Client, JoinHandle>) where S: 'static + Send, R: 'static + Send, E: 'static + Send, - RV: 'static + Send + RV: 'static + Send, + F: Handler + Send + 'static { let (server, client) = channel(); + + let mut handler = hbldr(&client); + let weak_client = client.weak(); let jh = task::spawn(async move { handler.init(weak_client); let ret = loop { let (msg, rctx) = match server.async_wait().await { Index: src/thread.rs ================================================================== --- src/thread.rs +++ src/thread.rs @@ -1,6 +1,50 @@ //! ump server running on a thread. +//! +//! ``` +//! use std::ops::ControlFlow; +//! use ump_server::{ +//! thread::{Handler, spawn}, +//! ump::ReplyContext +//! }; +//! enum Request { +//! Add(usize, usize) +//! } +//! enum Reply { +//! Sum(usize) +//! } +//! enum MyError { } +//! struct MyHandler {}; +//! impl Handler for MyHandler { +//! fn proc_req( +//! &mut self, +//! msg: Request, +//! rctx: ReplyContext +//! ) -> ControlFlow<(), ()> { +//! match msg { +//! Request::Add(a, b) => { +//! rctx.reply(Reply::Sum(a + b)); +//! ControlFlow::Continue(()) +//! } +//! } +//! } +//! } +//! +//! let (clnt, jh) = spawn(|clnt| { +//! MyHandler { } +//! }); +//! +//! let Ok(Reply::Sum(sum)) = clnt.req(Request::Add(3, 7)) else { +//! panic!("Unexpected reply"); +//! }; +//! assert_eq!(sum, 10); +//! +//! // Dropping the only client will terminate the dispatch loop +//! drop(clnt); +//! +//! let _ = jh.join(); +//! ``` use std::{ops::ControlFlow, thread}; use super::{channel, Client, ReplyContext}; @@ -47,20 +91,24 @@ /// Run a thread which will process incoming messages from an ump server /// end-point. /// /// See top module's documentation for an overview of the [dispatch /// loop](crate#dispatch-loop). -pub fn spawn( - mut handler: impl Handler + Send + 'static +pub fn spawn( + hbldr: impl FnOnce(&Client) -> F ) -> (Client, thread::JoinHandle>) where S: 'static + Send, R: 'static + Send, E: 'static + Send, - RV: 'static + Send + RV: 'static + Send, + F: Handler + Send + 'static { let (server, client) = channel(); + + let mut handler = hbldr(&client); + let weak_client = client.weak(); let jh = thread::spawn(move || { handler.init(weak_client); let ret = loop { let (msg, rctx) = match server.wait() { Index: tests/term.rs ================================================================== --- tests/term.rs +++ tests/term.rs @@ -3,12 +3,11 @@ use common::{Reply, Request, ThreadedServer}; // Terminate the dispatcher loop by dropping the only client. #[test] fn no_clients() { - let handler = ThreadedServer {}; - let (clnt, jh) = ump_server::spawn_thread(handler); + let (clnt, jh) = ump_server::spawn_thread(|_clnt| ThreadedServer {}); // Drop the (only) client, which should cause dispatch loop to terminate. drop(clnt); // Termination by clients disappearing should return None @@ -17,15 +16,14 @@ // Terminate the dispatcher loop by explicitly requesting it to terminate from // its handler. #[test] fn handler_req_term() { - let handler = ThreadedServer {}; - let (clnt, jh) = ump_server::spawn_thread(handler); + let (clnt, jh) = ump_server::spawn_thread(|_clnt| ThreadedServer {}); assert_eq!(clnt.req(Request::Add(2, 4)).unwrap(), Reply::Sum(6)); assert_eq!(clnt.req(Request::Croak).unwrap(), Reply::OkIWillCroak); assert_eq!(jh.join().unwrap(), Some(42)); } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 : Index: www/changelog.md ================================================================== --- www/changelog.md +++ www/changelog.md @@ -1,15 +1,36 @@ # Change Log ## [Unreleased] +[Details](/vdiff?from=ump-server-0.2.0&to=trunk) + ### Added ### Changed ### Removed +--- + +## [0.2.0] - 2024-01-28 + +[Details](/vdiff?from=ump-server-0.1.0&to=ump-server-0.2.0) + +### Added + +- Add `net` feature to `tokio` dependency to work around what appears to be a + bug in tokio which prohibits doc generation without it. + +### Changed + +- Instead of taking in an `impl Handler` into the `{thread,task}::spawn()` + function, take in a closure that returns the handler. A reference to the + handler channel's client endpoint is passed to the closure, which makes it + possible to store `Client`/`WeakClient` in the handler, without involving an + `Option` (or similar). + --- ## [0.1.0] - 2023-10-03 Initial release. Index: www/index.md ================================================================== --- www/index.md +++ www/index.md @@ -1,19 +1,19 @@ # ump-server -The _ump-server_ crate is a server message dispatch abstraction for ump. +The _ump-server_ crate is a server message dispatch abstraction for +[ump](https://repos.qrnch.tech/pub/ump). ## Feature labels in documentation The crate's documentation uses automatically generated feature labels, which currently requires nightly featuers. To build the documentation locally use: ``` -RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features +RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features ``` - ## Change log The details of changes can always be found in the timeline, but for a high-level view of changes between released versions there's a manually