use crate::sys::FI_EAGAIN;
use crate::sys::fi_context2;
use dma_libfabric_protocol::DmaError;
use dma_libfabric_protocol::checksum;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::os::raw::c_void;
use std::sync::Arc;
use std::sync::mpsc::{Receiver, TryRecvError};
use std::time::Instant;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::BatchCompleter;
use crate::Completion;
use crate::Configuration;
use crate::Direction;
use crate::Operands;
use crate::TransferDone;
use crate::TransferRequest;
use crate::endpoint::LibfabricEndpoint;
use crate::error::fabric_error;
use crate::local_regions::LocalOperand;
use crate::pool::Pool;
use crate::region_cache::Registration;
use crate::reply::Answer;
use crate::server::WorkerMessage;
const TARGET_COMPLETIONS: usize = 10;
pub fn worker_main<T: Operands + Send + 'static>(
configuration: Configuration,
ready: Answer<Result<Vec<u8>, DmaError>>,
receiver: &Receiver<WorkerMessage<T>>,
complete_batch: Arc<BatchCompleter<T>>,
pool: Arc<Pool>,
outstanding: &AtomicUsize,
) {
let mut endpoint = match LibfabricEndpoint::open(&configuration, configuration.bind.as_deref())
.and_then(|endpoint| {
let address = endpoint.local_address()?;
Ok((endpoint, address))
}) {
Ok((endpoint, address)) => {
ready.send(Ok(address));
endpoint
}
Err(error) => {
ready.send(Err(error));
return;
}
};
let cap = in_flight_cap(&endpoint, configuration.max_in_flight);
let again = -(FI_EAGAIN as isize);
let progress_deadline = configuration.progress_deadline;
let mut outstanding_by_client: HashMap<u64, usize> = HashMap::new();
let mut pending_removal: HashSet<u64> = HashSet::new();
let mut pending: VecDeque<Prepared<T>> = VecDeque::new();
let mut in_flight: usize = 0;
let mut reaped: Vec<(*mut c_void, Result<(), DmaError>)> = Vec::new();
let mut disconnected = false;
let mut completions: Vec<Completion<T>> = Vec::new();
let mut crc_pending: Vec<ReapedOp<T>> = Vec::new();
let mut drain_spins = 0;
loop {
loop {
match receiver.try_recv() {
Ok(WorkerMessage::Transfer(request)) => accept(
request,
&mut outstanding_by_client,
outstanding,
&mut pending,
&mut completions,
),
Ok(WorkerMessage::AddPeer {
client_id,
address,
reply,
}) => {
reply.send(endpoint.insert_peer(client_id, &address).map(|_| ()));
}
Ok(WorkerMessage::RemovePeer(client_id)) => remove_or_defer(
&mut endpoint,
&outstanding_by_client,
&mut pending_removal,
client_id,
),
Ok(WorkerMessage::Register {
base,
length,
reply,
}) => {
reply.send(endpoint.pin_region(base, length));
}
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
disconnected = true;
break;
}
}
}
let mut posted = 0usize;
let mut attempts = pending.len();
while in_flight < cap && 0 < attempts {
attempts -= 1;
let Some(mut prepared) = pending.pop_front() else {
break;
};
let error = match post(&mut endpoint, &prepared) {
Ok(0) => {
in_flight += 1;
posted += 1;
continue;
}
Ok(code) if code == again => {
let stalled_since = *prepared.stalled_since.get_or_insert_with(Instant::now);
if stalled_since.elapsed() < progress_deadline {
pending.push_back(prepared);
continue;
}
DmaError::Transfer(format!(
"no progress posting for {progress_deadline:?}: peer unreachable or transmit queue stuck"
))
}
Ok(code) => fabric_error(code as i32, "post"),
Err(error) => error,
};
let client_id = prepared.client_id;
completions.push(reclaim(prepared.context).into_completion(Err(error)));
finish_op(
&mut endpoint,
&mut outstanding_by_client,
&mut pending_removal,
outstanding,
client_id,
);
}
reaped.clear();
if let Err(error) = endpoint.reap(&mut reaped, TARGET_COMPLETIONS) {
tracing::warn!("completion queue read failed: {error}");
}
in_flight = in_flight.saturating_sub(reaped.len());
let reaped_any = !reaped.is_empty();
if 0 < posted || reaped_any {
let _pass = tracing::debug_span!(
parent: None,
"worker_pass",
in_flight,
posted,
reaped = reaped.len(),
pending = pending.len(),
);
}
for (context, result) in reaped.drain(..) {
let inflight = reclaim(context.cast::<InFlight<T>>());
finish_op(
&mut endpoint,
&mut outstanding_by_client,
&mut pending_removal,
outstanding,
inflight.client_id,
);
if inflight.want_checksum {
crc_pending.push((inflight, result));
} else {
completions.push(inflight.into_completion(result));
}
}
if should_flush_crc(
crc_pending.len(),
posted,
reaped_any,
pending.is_empty() && 0 == in_flight,
) {
submit_crc_batch(&pool, &complete_batch, std::mem::take(&mut crc_pending));
}
if !completions.is_empty() {
if completions.len() < TARGET_COMPLETIONS && drain_spins < 10 {
std::hint::spin_loop();
drain_spins += 1;
continue;
}
drain_spins = 0;
(*complete_batch)(completions.drain(..));
}
if disconnected && pending.is_empty() && 0 == in_flight {
break;
}
if 0 == in_flight && pending.is_empty() {
match receiver.recv() {
Ok(WorkerMessage::Transfer(request)) => accept(
request,
&mut outstanding_by_client,
outstanding,
&mut pending,
&mut completions,
),
Ok(WorkerMessage::AddPeer {
client_id,
address,
reply,
}) => {
reply.send(endpoint.insert_peer(client_id, &address).map(|_| ()));
}
Ok(WorkerMessage::RemovePeer(client_id)) => remove_or_defer(
&mut endpoint,
&outstanding_by_client,
&mut pending_removal,
client_id,
),
Ok(WorkerMessage::Register {
base,
length,
reply,
}) => {
reply.send(endpoint.pin_region(base, length));
}
Err(_) => break, }
} else if 0 == posted && !reaped_any {
if 0 == in_flight {
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
}
}
let mut leftover: Vec<Completion<T>> = pending
.drain(..)
.map(|prepared| {
let error = DmaError::Fabric("fabric worker is gone".into());
reclaim(prepared.context).into_completion(Err(error))
})
.chain(
crc_pending
.into_iter()
.map(|(op, result)| op.into_completion(result)),
)
.collect();
if !leftover.is_empty() {
(*complete_batch)(leftover.drain(..));
}
}
fn should_flush_crc(batched: usize, posted: usize, reaped_any: bool, about_to_park: bool) -> bool {
0 < batched && (batched >= TARGET_COMPLETIONS || (0 == posted && !reaped_any) || about_to_park)
}
fn submit_crc_batch<T: Operands + Send + 'static>(
pool: &Arc<Pool>,
complete_batch: &Arc<BatchCompleter<T>>,
batch: Vec<ReapedOp<T>>,
) {
let complete = Arc::clone(complete_batch);
pool.submit(Box::new(move || {
let mut done: Vec<Completion<T>> = batch
.into_iter()
.map(|(op, result)| op.into_completion(result))
.collect();
(*complete)(done.drain(..));
}));
}
fn accept<T: Operands>(
request: TransferRequest<T>,
outstanding_by_client: &mut HashMap<u64, usize>,
outstanding: &AtomicUsize,
pending: &mut VecDeque<Prepared<T>>,
completions: &mut Vec<Completion<T>>,
) {
match prepare(request) {
Ok(prepared) => {
*outstanding_by_client.entry(prepared.client_id).or_insert(0) += 1;
pending.push_back(prepared);
}
Err(completion) => {
release_outstanding(outstanding);
completions.push(completion);
}
}
}
fn remove_or_defer(
endpoint: &mut LibfabricEndpoint,
outstanding_by_client: &HashMap<u64, usize>,
pending_removal: &mut HashSet<u64>,
client_id: u64,
) {
if 0 == outstanding_by_client.get(&client_id).copied().unwrap_or(0) {
endpoint.release_peer(client_id);
} else {
pending_removal.insert(client_id);
}
}
fn finish_op(
endpoint: &mut LibfabricEndpoint,
outstanding_by_client: &mut HashMap<u64, usize>,
pending_removal: &mut HashSet<u64>,
outstanding: &AtomicUsize,
client_id: u64,
) {
release_outstanding(outstanding);
let remaining = match outstanding_by_client.get_mut(&client_id) {
Some(count) => {
*count -= 1;
*count
}
None => 0,
};
if 0 == remaining {
outstanding_by_client.remove(&client_id);
if pending_removal.remove(&client_id) {
endpoint.release_peer(client_id);
}
}
}
fn release_outstanding(outstanding: &AtomicUsize) {
outstanding.update(Ordering::Relaxed, Ordering::Relaxed, |current| {
current.saturating_sub(1)
});
}
type ReapedOp<T> = (Box<InFlight<T>>, Result<(), DmaError>);
fn reclaim<T>(context: *mut InFlight<T>) -> Box<InFlight<T>> {
unsafe { Box::from_raw(context) }
}
fn post<T: Operands>(
endpoint: &mut LibfabricEndpoint,
prepared: &Prepared<T>,
) -> Result<isize, DmaError> {
let peer = endpoint.insert_peer(prepared.client_id, &prepared.peer_address)?;
let context = prepared.context.cast::<c_void>();
let length = prepared.length;
let cacheable_span = || unsafe { (*prepared.context).caller_context.cacheable_span() };
let LocalOperand { descriptor, lease } =
endpoint.local_operand(prepared.pointer, length, cacheable_span)?;
let connection = endpoint.connection(&peer, prepared.remote_key, prepared.remote_address);
let wire = tracing::info_span!(
parent: prepared.parent_id.clone(),
"wire",
direction = ?prepared.direction,
length
);
let code = {
let _posting = wire.enter();
match prepared.direction {
Direction::ToPeer => {
let bytes = unsafe { std::slice::from_raw_parts(prepared.pointer, length) };
connection.post_out(bytes, descriptor, context)
}
Direction::FromPeer { .. } => {
let bytes = unsafe { std::slice::from_raw_parts_mut(prepared.pointer, length) };
connection.post_in(bytes, descriptor, context)
}
}
};
if 0 == code {
let await_completion = tracing::info_span!(parent: &wire, "await_completion");
unsafe {
(*prepared.context).wire_span = wire;
(*prepared.context).await_span = await_completion;
(*prepared.context).local_lease = lease;
}
}
Ok(code)
}
fn in_flight_cap(endpoint: &LibfabricEndpoint, configured: Option<usize>) -> usize {
let requested = configured.unwrap_or(64).max(1);
match endpoint.max_tx() {
0 => requested,
hardware => requested.min(hardware),
}
}
#[repr(C)]
struct InFlight<T> {
_context2: fi_context2,
caller_context: T,
client_id: u64,
length: usize,
want_checksum: bool,
buffer_pointer: *mut u8,
local_lease: Option<Arc<Registration>>,
wire_span: tracing::Span,
await_span: tracing::Span,
}
unsafe impl<T: Send> Send for InFlight<T> {}
impl<T> InFlight<T> {
#[expect(
clippy::boxed_local,
reason = "the box pins the operand's address across the hash"
)]
fn into_completion(self: Box<Self>, result: Result<(), DmaError>) -> Completion<T> {
let outcome = result.map(|()| {
let checksum_value = self.want_checksum.then(|| {
let _span =
tracing::info_span!(parent: &self.wire_span, "checksum", length = self.length)
.entered();
checksum(unsafe { std::slice::from_raw_parts(self.buffer_pointer, self.length) })
});
TransferDone {
bytes: self.length,
checksum: checksum_value,
}
});
Completion {
caller_context: self.caller_context,
outcome,
}
}
}
struct Prepared<T> {
context: *mut InFlight<T>,
client_id: u64,
peer_address: Vec<u8>,
remote_key: u64,
remote_address: u64,
pointer: *mut u8,
length: usize,
direction: Direction,
parent_id: Option<tracing::span::Id>,
stalled_since: Option<Instant>,
}
fn prepare<T: Operands>(request: TransferRequest<T>) -> Result<Prepared<T>, Completion<T>> {
let mut inflight = Box::new(InFlight {
_context2: unsafe { std::mem::zeroed() },
caller_context: request.caller_context,
client_id: request.client_id,
length: 0,
buffer_pointer: std::ptr::null_mut(),
want_checksum: request.want_checksum,
local_lease: None,
wire_span: tracing::Span::none(),
await_span: tracing::Span::none(),
});
let operand = match request.direction {
Direction::ToPeer => match inflight.caller_context.source() {
Some(bytes) => Ok((bytes.as_ptr().cast_mut(), bytes.len())),
None => Err(DmaError::Fabric(
"the caller context supplied no source bytes for a ToPeer transfer".into(),
)),
},
Direction::FromPeer { length } => match inflight.caller_context.allocate(length) {
Some(landing) if landing.len() < length => Err(DmaError::Fabric(format!(
"the caller context allocated {} bytes for a {length} byte FromPeer transfer",
landing.len()
))),
Some(landing) => Ok((landing.as_mut_ptr(), length)),
None => Err(DmaError::Fabric(
"the caller context allocated no landing buffer for a FromPeer transfer".into(),
)),
},
};
let (pointer, length) = match operand {
Ok(operand) => operand,
Err(error) => {
return Err(Completion {
caller_context: inflight.caller_context,
outcome: Err(error),
});
}
};
inflight.buffer_pointer = pointer;
inflight.length = length;
Ok(Prepared {
context: Box::into_raw(inflight),
client_id: request.client_id,
peer_address: request.peer_address,
remote_key: request.remote_key,
remote_address: request.remote_address,
pointer,
length,
direction: request.direction,
parent_id: request.parent_id,
stalled_since: None,
})
}
#[cfg(test)]
mod tests {
use super::{TARGET_COMPLETIONS, should_flush_crc};
#[test]
fn flushes_a_partial_batch_before_parking() {
assert!(should_flush_crc(1, 0, true, true));
}
#[test]
fn flushes_a_full_batch_even_with_work_left() {
assert!(should_flush_crc(TARGET_COMPLETIONS, 4, true, false));
}
#[test]
fn flushes_a_partial_batch_when_nothing_moved() {
assert!(should_flush_crc(1, 0, false, false));
}
#[test]
fn holds_a_partial_batch_while_work_is_moving() {
assert!(!should_flush_crc(1, 1, true, false));
}
#[test]
fn never_flushes_an_empty_batch() {
assert!(!should_flush_crc(0, 0, false, true));
}
}