use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{Duration, Instant};
use nuvec_core::{Error, Result};
use super::frame::Frame;
pub(crate) const SEND: u64 = 1;
pub(crate) const RECV: u64 = 2;
pub(crate) fn wr_id(kind: u64, slot: usize, tag: usize) -> u64 {
kind << 16 | (slot as u64) << 8 | tag as u64
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct Destination {
pub(crate) lid: i32,
pub(crate) qpn: i32,
pub(crate) psn: i32,
pub(crate) gid: [u8; 16],
}
impl Destination {
pub(crate) const BYTES: usize = 28;
pub(crate) fn to_bytes(self) -> [u8; Self::BYTES] {
let mut b = [0u8; Self::BYTES];
b[0..4].copy_from_slice(&self.lid.to_le_bytes());
b[4..8].copy_from_slice(&self.qpn.to_le_bytes());
b[8..12].copy_from_slice(&self.psn.to_le_bytes());
b[12..].copy_from_slice(&self.gid);
b
}
pub(crate) fn from_bytes(b: &[u8]) -> Self {
let int = |i: usize| i32::from_le_bytes(b[i..i + 4].try_into().expect("4 bytes"));
Self {
lid: int(0),
qpn: int(4),
psn: int(8),
gid: b[12..28].try_into().expect("16 bytes"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Status {
Success,
Error { code: i32, name: String },
}
#[derive(Clone, Debug)]
pub(crate) struct Completion {
pub(crate) wr_id: u64,
pub(crate) status: Status,
pub(crate) sequence: Option<u32>,
}
#[derive(Default)]
pub(crate) struct Sequence {
sent: AtomicU32,
received: AtomicU32,
}
impl Sequence {
pub(crate) fn next_send(&self) -> u32 {
self.sent.fetch_add(1, Ordering::Relaxed)
}
pub(crate) fn check(&self, c: &mut Completion) {
if c.kind() != RECV || c.status != Status::Success {
return;
}
let want = self.received.fetch_add(1, Ordering::Relaxed);
if c.sequence != Some(want) {
c.status = Status::Error {
code: -1,
name: match c.sequence {
Some(got) => format!("message {want} was lost (received message {got})"),
None => "a message without a sequence number".into(),
},
};
}
}
}
impl Completion {
pub(crate) fn kind(&self) -> u64 {
self.wr_id >> 16
}
pub(crate) fn slot(&self) -> usize {
((self.wr_id >> 8) & 0xff) as usize
}
pub(crate) fn tag(&self) -> usize {
(self.wr_id & 0xff) as usize
}
pub(crate) fn check(&self, peer: usize) -> Result<()> {
match &self.status {
Status::Success => Ok(()),
Status::Error { code, name } => {
let what = if self.kind() == SEND {
"send"
} else {
"receive"
};
Err(Error::Backend(format!(
"jaccl: rank {peer}: {what} failed: {name} ({code})"
)))
}
}
}
}
pub(crate) trait Endpoint: Send + Sync {
fn destination(&self) -> Result<Destination>;
fn connect(&self, peer: &Destination) -> Result<()>;
fn register(&self, frame: &Frame) -> Result<()>;
fn post_send(&self, frame: &Frame, wr_id: u64) -> Result<()>;
fn post_recv(&self, frame: &Frame, wr_id: u64) -> Result<()>;
fn poll(&self, out: &mut Vec<Completion>, max: usize) -> Result<usize>;
}
pub(crate) trait Fabric: Send + Sync {
fn open(&self, device: &str, peer: usize) -> Result<Arc<dyn Endpoint>>;
}
const SPIN: Duration = Duration::from_micros(50);
pub(crate) struct Waiter {
timeout: Duration,
since: Instant,
empty: u32,
yielding: bool,
}
impl Waiter {
pub(crate) fn new(timeout: Duration) -> Self {
Self {
timeout,
since: Instant::now(),
empty: 0,
yielding: false,
}
}
pub(crate) fn polled(&mut self, n: usize, waiting: impl FnOnce() -> String) -> Result<()> {
if n > 0 {
self.empty = 0;
self.yielding = false;
self.since = Instant::now();
return Ok(());
}
self.empty += 1;
if self.yielding {
std::thread::yield_now();
} else if !self.empty.is_multiple_of(1024) {
return Ok(());
}
let idle = self.since.elapsed();
if idle > self.timeout {
return Err(Error::Backend(format!(
"jaccl: {}: no completion within {:.1} s",
waiting(),
self.timeout.as_secs_f64()
)));
}
self.yielding = idle > SPIN;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packs_work_requests_and_destinations() {
let c = Completion {
wr_id: wr_id(RECV, 1, 7),
status: Status::Success,
sequence: Some(0),
};
assert_eq!((c.kind(), c.slot(), c.tag()), (RECV, 1, 7));
let d = Destination {
lid: 3,
qpn: -2,
psn: 7,
gid: [9; 16],
};
assert_eq!(Destination::from_bytes(&d.to_bytes()), d);
let bad = Completion {
wr_id: wr_id(SEND, 0, 2),
status: Status::Error {
code: 5,
name: "transport retry counter exceeded".into(),
},
sequence: None,
};
let e = bad.check(2).unwrap_err().to_string();
assert!(e.contains("rank 2: send failed"), "{e}");
let seq = Sequence::default();
let mut ok = c.clone();
seq.check(&mut ok);
assert_eq!(ok.status, Status::Success);
let mut gap = Completion {
sequence: Some(2),
..c
};
seq.check(&mut gap);
let e = gap.check(7).unwrap_err().to_string();
assert!(
e.contains("rank 7: receive failed: message 1 was lost"),
"{e}"
);
}
#[test]
fn waits_are_bounded() {
let mut w = Waiter::new(Duration::from_millis(20));
let start = Instant::now();
let e = loop {
if let Err(e) = w.polled(0, || "rank 1".into()) {
break e;
}
};
assert!(start.elapsed() >= Duration::from_millis(20));
assert!(e.to_string().contains("rank 1: no completion"), "{e}");
}
}