use std::sync::{Arc, OnceLock};
use crate::backend::Backend;
use crate::checks::{self, DevContext};
use crate::endpoint::ConnId;
use crate::route::{self, Flow, QuickAckCheck, RouteCheck, RouteError};
use crate::warn::State;
pub(super) enum RouteIn {
Lookup(Result<Flow, RouteError>, Arc<OnceLock<RouteCheck>>),
Known(RouteCheck),
}
pub(super) struct ConnJob {
pub(super) conn: ConnId,
pub(super) backend: Backend,
pub(super) ctx: Arc<DevContext>,
pub(super) napi: Option<u32>,
pub(super) route: RouteIn,
}
impl ConnJob {
pub(super) fn run(self, st: &mut State) {
let (check, flow) = match self.route {
RouteIn::Lookup(flow, cell) => {
let addressing = flow.as_ref().ok().copied();
let mut line = None;
let check = route::check_with(
QuickAckCheck::Warn,
flow,
route::lookup,
&route::WARNED,
|l| line = Some(l),
)
.unwrap_or(RouteCheck::NotChecked);
let _ = cell.set(check.clone());
if let Some(l) = line {
st.print(l);
}
(check, addressing)
}
RouteIn::Known(check) => (check, None),
};
let Some(r) = check.route() else {
return;
};
if r.is_local() {
return;
}
let dev = r.dev.clone().unwrap_or_default();
if let Some(l) = checks::napi_line(self.backend, self.conn, flow.as_ref(), r, self.napi)
&& st.first(format!("I4 {dev}"))
{
st.print(l);
}
if !dev.is_empty() {
st.check_device(&dev, &self.ctx);
}
}
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use std::sync::Arc;
use ergon_tune::{Host, HotSet, Role};
use super::{ConnJob, RouteIn};
use crate::backend::Backend;
use crate::checks::{DevContext, SETTLE_READS};
use crate::endpoint::ConnId;
use crate::route::{self, RouteCheck};
use crate::warn::{self, State};
type R<T = ()> = Result<T, Box<dyn std::error::Error>>;
const V4_DEFAULT_REPLY: [u8; 52] = [
0x34, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
0x0f, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x08, 0x00, 0x05, 0x00, 0xac, 0x11, 0x00, 0x01, 0x08,
0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
];
fn write(root: &Path, abs: &str, text: &str) -> R {
let p = root.join(abs.trim_start_matches('/'));
if let Some(d) = p.parent() {
std::fs::create_dir_all(d)?;
}
std::fs::write(p, text)?;
Ok(())
}
fn link(root: &Path, abs: &str, target: &str) -> R {
let p = root.join(abs.trim_start_matches('/'));
if let Some(d) = p.parent() {
std::fs::create_dir_all(d)?;
}
std::os::unix::fs::symlink(target, p)?;
Ok(())
}
fn host_with_irq_on_the_loop(root: &Path) -> R {
write(root, "/sys/devices/system/cpu/online", "0-7\n")?;
write(root, "/sys/devices/system/cpu/isolated", "1-3,5-7\n")?;
std::fs::create_dir_all(root.join("sys/bus/pci/drivers"))?;
let d = "/sys/bus/pci/devices/0000:00:05.0";
link(root, &format!("{d}/subsystem"), "../../../../bus/pci")?;
write(root, &format!("{d}/msi_irqs/61"), "msix\n")?;
write(root, "/proc/irq/61/smp_affinity_list", "2\n")?;
write(root, "/sys/class/net/ens5/carrier_changes", "2\n")?;
link(
root,
"/sys/class/net/ens5/device",
"../../../bus/pci/devices/0000:00:05.0",
)?;
Ok(())
}
fn job(route: &route::Route) -> ConnJob {
ConnJob {
conn: ConnId::default(),
backend: Backend::RecvBusy,
ctx: Arc::new(DevContext {
hot: HotSet::new().with(2, Role::Loop),
interrupt_driven: false,
}),
napi: Some(8193),
route: RouteIn::Known(RouteCheck::Route(Box::new(route.clone()))),
}
}
#[test]
fn a_reset_is_reported_again_when_the_application_reconnects_at_once() -> R {
let root: PathBuf =
std::env::temp_dir().join(format!("ergon-io-conn-checks-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
host_with_irq_on_the_loop(&root)?;
let mut r = route::decode_reply(&V4_DEFAULT_REPLY, 1)?;
r.dev = Some("ens5".to_owned());
warn::keep_lines();
let mut st = State::at(Host::at(&root));
job(&r).run(&mut st);
write(&root, "/sys/class/net/ens5/carrier_changes", "4\n")?;
job(&r).run(&mut st);
for _ in 0..=SETTLE_READS {
st.recheck_now();
}
let _ = std::fs::remove_dir_all(&root);
let lines = warn::take_lines();
let t2 = lines
.iter()
.filter(|l| l.contains("T2 warn") && l.contains("IRQ 61"))
.count();
assert_eq!(t2, 2, "once at first, once after the reset: {lines:#?}");
Ok(())
}
}