diff options
-rw-r--r-- | Cargo.lock | 10 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | crates/control/Cargo.toml | 9 | ||||
-rw-r--r-- | crates/control/src/lib.rs | 81 | ||||
-rw-r--r-- | crates/web/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/web/src/main.rs | 20 |
6 files changed, 116 insertions, 8 deletions
@@ -18,9 +18,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] +name = "argonaut-control" +version = "0.1.0" +dependencies = [ + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] name = "argonaut-web" version = "0.1.0" dependencies = [ + "argonaut-control", "axum", "serde", "tokio", @@ -1,7 +1,8 @@ [workspace] resolver = "2" members = [ - "crates/web" + "crates/control", + "crates/web" ] [workspace.dependencies] diff --git a/crates/control/Cargo.toml b/crates/control/Cargo.toml new file mode 100644 index 0000000..6934db5 --- /dev/null +++ b/crates/control/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "argonaut-control" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } diff --git a/crates/control/src/lib.rs b/crates/control/src/lib.rs new file mode 100644 index 0000000..5691cf2 --- /dev/null +++ b/crates/control/src/lib.rs @@ -0,0 +1,81 @@ +//! Printer control related code +//! +//! To ensure the volume of web requests doesn't mess with printing, communication with the printer is done +//! in a separate thread, on its own async executor. +//! +//! The main server spawns this thread by creating a [`Handle`] and communicates by sending [messages](crate::Message) over an MPSC channel. + +use std::{ + mem::ManuallyDrop, + thread::{self, JoinHandle}, +}; +use tokio::{ + runtime, + sync::mpsc::{self, Receiver, Sender}, +}; +use tracing::info; + +/// The maximum number of messages to buffer before 'blocking' new ones +/// This provides a form of backpressure +const CHANNEL_CAPACITY: usize = 100; + +/// A control message sent from the API to the printer control thread +pub enum Message { + Quit, +} + +/// A handle to the printer control thread, allowing for communication. +pub struct Handle { + cmd_send: Sender<Message>, + thread_handle: ManuallyDrop<JoinHandle<()>>, +} + +impl Handle { + /// Spawn a printer control thread, returning a handle + pub fn spawn() -> Self { + let (send, recv) = mpsc::channel(CHANNEL_CAPACITY); + + let state = State { cmd_recv: recv }; + let thread_handle = thread::spawn(move || { + let rt = runtime::Builder::new_current_thread() + .build() + .expect("error spawning printer control thread"); + + rt.block_on(async move { state.entrypoint().await }); + }); + + Self { + cmd_send: send, + thread_handle: ManuallyDrop::new(thread_handle), + } + } +} + +impl Drop for Handle { + fn drop(&mut self) { + let _ = self.cmd_send.blocking_send(Message::Quit); + unsafe { + info!("waiting for printer thread to exit..."); + ManuallyDrop::take(&mut self.thread_handle) + .join() + .expect("error joining printer thread") + }; + } +} + +/// The state held by the printer control thread +pub struct State { + cmd_recv: Receiver<Message>, +} + +impl State { + /// The entrypoint of the printer control thread + pub async fn entrypoint(mut self) { + info!("listening for events"); + while let Some(msg) = self.cmd_recv.recv().await { + match msg { + Message::Quit => break, + } + } + } +} diff --git a/crates/web/Cargo.toml b/crates/web/Cargo.toml index 62a3504..2528a0a 100644 --- a/crates/web/Cargo.toml +++ b/crates/web/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +argonaut-control = { path = "../control" } axum = "0.7.5" tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/web/src/main.rs b/crates/web/src/main.rs index f641ce0..d80b232 100644 --- a/crates/web/src/main.rs +++ b/crates/web/src/main.rs @@ -1,17 +1,23 @@ -use axum::{ - http::StatusCode, - routing::{get, post}, - Json, Router, -}; -use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use axum::{routing::get, Router}; + +pub use argonaut_control::Handle as PrinterThread; +use tracing::info; #[tokio::main(flavor = "current_thread")] async fn main() { tracing_subscriber::fmt::init(); - let app = Router::new().route("/", get(root)); + let printer_thread = PrinterThread::spawn(); + let printer_thread = Arc::new(printer_thread); + let app = Router::new() + .route("/", get(root)) + .with_state(printer_thread); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); + + info!("init completed, listening"); axum::serve(listener, app).await.unwrap(); } |