use std::thread::JoinHandle;
use tokio::sync::{mpsc, oneshot};
use crate::Error;
pub(crate) struct Ready(oneshot::Sender<Result<String, Error>>);
impl Ready {
#[must_use]
pub(crate) fn ok(self, name: &str) -> bool {
self.0.send(Ok(name.to_string())).is_ok()
}
pub(crate) fn err(self, err: Error) {
let _ = self.0.send(Err(err));
}
}
pub(crate) struct Worker<R> {
tx: Option<mpsc::UnboundedSender<R>>,
handle: Option<JoinHandle<()>>,
name: String,
abandoned: bool,
}
impl<R: Send + 'static> Worker<R> {
pub(crate) async fn open(
thread_name: &'static str,
run: impl FnOnce(Ready, mpsc::UnboundedReceiver<R>) + Send + 'static,
) -> Result<Self, Error> {
let (req_tx, req_rx) = mpsc::unbounded_channel::<R>();
let (ready_tx, ready_rx) = oneshot::channel::<Result<String, Error>>();
let handle = std::thread::Builder::new()
.name(thread_name.into())
.spawn(move || run(Ready(ready_tx), req_rx))
.map_err(|err| Error::Codec(anyhow::anyhow!("failed to spawn the {thread_name} thread: {err}")))?;
match ready_rx.await {
Ok(Ok(name)) => Ok(Self {
tx: Some(req_tx),
handle: Some(handle),
name,
abandoned: false,
}),
Ok(Err(err)) => Err(err),
Err(_) => {
let _ = handle.join();
Err(Error::Codec(anyhow::anyhow!(
"{thread_name} thread exited before opening"
)))
}
}
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn send(&self, req: R) -> Result<(), Error> {
self.tx.as_ref().ok_or_else(gone)?.send(req).map_err(|_| gone())
}
pub(crate) async fn request<T>(
&mut self,
build: impl FnOnce(oneshot::Sender<Result<T, Error>>) -> R,
) -> Result<T, Error> {
if self.abandoned {
return Err(abandoned());
}
let (resp_tx, resp_rx) = oneshot::channel();
self.send(build(resp_tx))?;
self.abandoned = true;
let reply = resp_rx.await;
self.abandoned = false;
reply.map_err(|_| gone())?
}
}
impl<R> Drop for Worker<R> {
fn drop(&mut self) {
self.tx.take();
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
fn gone() -> Error {
Error::Codec(anyhow::anyhow!("codec thread stopped unexpectedly"))
}
fn abandoned() -> Error {
Error::Codec(anyhow::anyhow!(
"a cancelled call left the codec ahead of this stream; drop it and open another"
))
}