use std::fmt;
use std::future::{Future, poll_fn};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Arc;
use std::task::Poll;
use bacnet_transport::port::TransportPort;
use bacnet_types::error::Error;
use tokio::sync::watch;
use tokio::task::JoinHandle;
#[derive(Debug)]
pub enum RouterStopFailure {
Transport {
network_number: u16,
error: Error,
},
Worker {
network_number: Option<u16>,
worker: &'static str,
detail: String,
},
}
#[derive(Clone, Debug)]
pub struct RouterStopError(Arc<[RouterStopFailure]>);
impl RouterStopError {
pub fn failures(&self) -> &[RouterStopFailure] {
&self.0
}
fn from_failures(failures: Vec<RouterStopFailure>) -> Result<(), Self> {
if failures.is_empty() {
Ok(())
} else {
Err(Self(failures.into()))
}
}
}
impl fmt::Display for RouterStopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "router shutdown failed: {:?}", self.0)
}
}
impl std::error::Error for RouterStopError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.iter().find_map(|failure| match failure {
RouterStopFailure::Transport { error, .. } => {
Some(error as &(dyn std::error::Error + 'static))
}
RouterStopFailure::Worker { .. } => None,
})
}
}
pub(super) async fn observe<F: Future>(future: F) -> Result<F::Output, String> {
let mut future = std::pin::pin!(future);
poll_fn(
|cx| match catch_unwind(AssertUnwindSafe(|| future.as_mut().poll(cx))) {
Ok(Poll::Pending) => Poll::Pending,
Ok(Poll::Ready(value)) => Poll::Ready(Ok(value)),
Err(panic) => {
let detail = panic
.downcast_ref::<String>()
.cloned()
.or_else(|| panic.downcast_ref::<&str>().map(|s| (*s).to_owned()))
.unwrap_or_else(|| "non-string panic payload".into());
Poll::Ready(Err(detail))
}
},
)
.await
}
pub(super) struct TransportOwner<T: TransportPort> {
pub transport: T,
pub stopped: bool,
}
impl<T: TransportPort> Drop for TransportOwner<T> {
fn drop(&mut self) {
if !self.stopped {
self.transport.abort();
}
}
}
pub(super) struct Workers {
pub dispatch: Vec<JoinHandle<()>>,
pub senders: Vec<(u16, JoinHandle<Vec<RouterStopFailure>>)>,
pub aging: JoinHandle<()>,
pub limits: Vec<JoinHandle<()>>,
pub stop_senders: watch::Sender<bool>,
}
impl Workers {
async fn stop(mut self) -> Result<(), RouterStopError> {
self.stop_senders.send_replace(true);
for task in &self.dispatch {
task.abort();
}
self.aging.abort();
for task in &self.limits {
task.abort();
}
let mut failures = Vec::new();
for task in &mut self.dispatch {
joined(task, "dispatch", &mut failures).await;
}
joined(&mut self.aging, "aging", &mut failures).await;
for task in &mut self.limits {
joined(task, "limit", &mut failures).await;
}
for (network_number, task) in &mut self.senders {
match task.await {
Ok(port_failures) => failures.extend(port_failures),
Err(error) => failures.push(RouterStopFailure::Worker {
network_number: Some(*network_number),
worker: "sender owner",
detail: error.to_string(),
}),
}
}
RouterStopError::from_failures(failures)
}
}
impl Drop for Workers {
fn drop(&mut self) {
self.stop_senders.send_replace(true);
for task in &self.dispatch {
task.abort();
}
self.aging.abort();
for task in &self.limits {
task.abort();
}
}
}
async fn joined(
task: &mut JoinHandle<()>,
worker: &'static str,
failures: &mut Vec<RouterStopFailure>,
) {
if let Err(error) = task.await
&& !error.is_cancelled()
{
failures.push(RouterStopFailure::Worker {
network_number: None,
worker,
detail: error.to_string(),
});
}
}
pub(super) struct Lifecycle {
pub workers: Option<Workers>,
cleanup: Option<JoinHandle<Result<(), RouterStopError>>>,
settled: Option<Result<(), RouterStopError>>,
}
impl Lifecycle {
pub fn new(workers: Workers) -> Self {
Self {
workers: Some(workers),
cleanup: None,
settled: None,
}
}
pub fn begin(&mut self) {
if let Some(workers) = self.workers.take() {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
self.cleanup = Some(runtime.spawn(workers.stop()));
} else {
drop(workers);
}
}
}
pub async fn stop(&mut self) -> Result<(), RouterStopError> {
self.begin();
if self.settled.is_none() {
let result = match self.cleanup.as_mut() {
Some(task) => match task.await {
Ok(result) => result,
Err(error) => RouterStopError::from_failures(vec![RouterStopFailure::Worker {
network_number: None,
worker: "shutdown owner",
detail: error.to_string(),
}]),
},
None => RouterStopError::from_failures(vec![RouterStopFailure::Worker {
network_number: None,
worker: "shutdown owner",
detail: "cleanup owner unavailable".into(),
}]),
};
self.cleanup.take();
self.settled = Some(result);
}
self.settled.as_ref().unwrap().clone()
}
}
impl Drop for Lifecycle {
fn drop(&mut self) {
self.begin();
}
}