use super::*;
impl BACnetRouter {
pub(super) async fn start_dispatch<T: TransportPort + 'static>(
ports: Vec<RouterPort<T>>,
track_depth: bool,
control: Arc<control_policy::ControlGate>,
network_control: Option<AdmissionSender<ReceivedNetworkControl>>,
engine_options: bacnet_router_core::router::Options,
) -> Result<(Self, mpsc::Receiver<ReceivedApdu>, QueueAdmissionCounters), Error> {
if ports.is_empty() || ports.len() > PORTS {
return Err(Error::Encoding(format!(
"A router needs 1 to {PORTS} ports, not {}",
ports.len()
)));
}
{
let mut seen = std::collections::HashSet::new();
for port in &ports {
if matches!(port.network_number, 0 | 0xFFFF) {
return Err(Error::Encoding(format!(
"A router port can't be on network {}: ports take 1 to 65534",
port.network_number
)));
}
if !seen.insert(port.network_number) {
return Err(Error::Encoding(format!(
"Duplicate network number {} in router ports",
port.network_number
)));
}
}
}
let mut startup = Startup::new(ports);
let (local_tx, local_rx, counters) = AdmissionReceiver::channel(track_depth);
let mut port_receivers = Vec::new();
let mut port_info = Vec::new();
let mut configs = Vec::new();
let mut networks = Vec::new();
let mut limit_changes = Vec::new();
let setup = async {
for index in 0..startup.ports.len() {
startup.attempted = index + 1;
let port = &mut startup.ports[index];
port.transport.prepare_for_routing();
let rx = port.transport.start().await?;
port_receivers.push(rx);
let mac = Mac::from_slice(port.transport.local_mac()).map_err(|_| {
Error::Encoding(format!(
"Router port on network {} has a MAC longer than {} octets",
port.network_number,
bacnet_npdu::MAX_MAC_LEN
))
})?;
port_info.push(RouterPortInfo {
index: index as u8,
network_number: port.network_number,
mac: MacAddr::from_slice(mac.as_slice()),
});
configs.push(Engine::port(
port.network_number,
mac,
port.transport.max_npdu(),
PortGroups {
groups: port.transport.group_destinations(),
local_broadcasts: port.transport.local_broadcast_destinations(),
},
));
limit_changes.push(port.transport.max_npdu_changes());
networks.push(port.network_number);
}
Engine::new(&configs, tokio_clock(), engine_options)
.map_err(|error| Error::Encoding(error.to_string()))
}
.await;
let engine = match setup {
Ok(engine) => engine,
Err(error) => {
if let Some(cleanup) = startup.cleanup()
&& let Err(join_error) = cleanup.await
{
tracing::error!(%join_error, "router startup rollback owner failed");
}
return Err(error);
}
};
let ports = std::mem::take(&mut startup.ports);
let network_control_ingress_sequence = Arc::new(AtomicU64::new(0));
let shared = Arc::new(Shared::new(engine, control, networks));
let (send_txs, send_rxs): (Vec<_>, Vec<_>) = (0..ports.len())
.map(|_| send_queue::channel(send_queue::CAPACITY))
.unzip();
let replies = Arc::new(replies::ReplyLifetime::default());
let outputs = Arc::new(Outputs {
replies: Arc::clone(&replies),
send_txs,
local_tx,
local_control: local_control::LocalControl::new(
network_control,
Arc::clone(&network_control_ingress_sequence),
),
});
let (stop_senders, sender_stop) = tokio::sync::watch::channel(false);
let sender_tasks = ports
.into_iter()
.zip(send_rxs)
.enumerate()
.map(|(index, (port, send_rx))| {
(
port.network_number,
tokio::spawn(sender::owned(
port.transport,
PortId(index as u8),
send_rx,
Arc::clone(&shared),
Arc::downgrade(&outputs),
sender_stop.clone(),
port.network_number,
)),
)
})
.collect();
let limit_tasks = limit_changes
.into_iter()
.enumerate()
.filter_map(|(index, changes)| {
let port = PortId(index as u8);
let task = limits::follow(Arc::clone(&shared), port, changes?);
Some(tokio::spawn(task))
})
.collect();
shared.announce(&outputs.send_txs);
let dispatch_tasks = port_receivers
.into_iter()
.enumerate()
.map(|(index, rx)| {
let port = PortDispatch {
shared: Arc::clone(&shared),
outputs: Arc::clone(&outputs),
port: index as u8,
};
tokio::spawn(port.run(rx))
})
.collect();
let aging = Arc::clone(&shared);
let aging_task = tokio::spawn(async move {
let clock = aging.engine.clock();
let wake = |next: Option<Instant>| next.and_then(|at| clock.to_std(at));
let mut next = wake(aging.engine.lock().next_deadline());
loop {
match next {
Some(at) => tokio::time::sleep_until(at.into()).await,
None => std::future::pending().await,
}
next = wake(aging.sweep());
}
});
Ok((
Self {
replies,
outputs: Arc::downgrade(&outputs),
shared,
lifecycle: shutdown::Lifecycle::new(shutdown::Workers {
dispatch: dispatch_tasks,
senders: sender_tasks,
aging: aging_task,
limits: limit_tasks,
stop_senders,
}),
network_control_ingress_sequence,
port_info,
},
local_rx,
counters,
))
}
}
struct Startup<T: TransportPort + 'static> {
ports: Vec<RouterPort<T>>,
attempted: usize,
}
impl<T: TransportPort + 'static> Startup<T> {
fn new(ports: Vec<RouterPort<T>>) -> Self {
Self {
ports,
attempted: 0,
}
}
fn cleanup(&mut self) -> Option<tokio::task::JoinHandle<()>> {
if self.ports.is_empty() {
return None;
}
let cleanup = Rollback {
ports: std::mem::take(&mut self.ports),
attempted: self.attempted,
stopped: vec![false; self.attempted],
};
match tokio::runtime::Handle::try_current() {
Ok(runtime) => Some(runtime.spawn(cleanup.run())),
Err(_) => {
drop(cleanup);
None
}
}
}
}
impl<T: TransportPort + 'static> Drop for Startup<T> {
fn drop(&mut self) {
self.cleanup();
}
}
struct Rollback<T: TransportPort> {
ports: Vec<RouterPort<T>>,
attempted: usize,
stopped: Vec<bool>,
}
impl<T: TransportPort> Rollback<T> {
async fn run(mut self) {
for index in 0..self.attempted {
let port = &mut self.ports[index];
match shutdown::observe(port.transport.stop()).await {
Ok(Ok(())) => self.stopped[index] = true,
Ok(Err(error)) => tracing::error!(
network_number = port.network_number,
?error,
"router startup rollback transport stop failed"
),
Err(detail) => {
tracing::error!(network_number = port.network_number, %detail, "router startup rollback transport stop panicked")
}
}
}
}
}
impl<T: TransportPort> Drop for Rollback<T> {
fn drop(&mut self) {
for (index, port) in self.ports.iter_mut().take(self.attempted).enumerate() {
if !self.stopped[index] {
port.transport.abort();
}
}
}
}