From 9a7042df7f9a7ad0324c4442ae918c2a4a442966 Mon Sep 17 00:00:00 2001 From: tcmal Date: Sun, 8 Sep 2024 18:46:43 +0100 Subject: Add framework for separate printer control thread --- crates/control/src/lib.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 crates/control/src/lib.rs (limited to 'crates/control/src') 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, + thread_handle: ManuallyDrop>, +} + +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, +} + +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, + } + } + } +} -- cgit v1.2.3