use std::io;
use std::ops::Range;
use std::ptr;
use std::time::{Duration, Instant};
use win_numa_sys::NumaNode;
use windows_ioring_sys::{
Batch, IoRing, NumaBuffer, OperationId, PushOptions, RegisteredBuffers, RegisteredSpan,
WriteCaching,
};
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::SystemInformation::GROUP_AFFINITY;
use windows_sys::Win32::System::Threading::{GetCurrentThread, SetThreadGroupAffinity};
use crate::plan::DomainPlan;
const OP_TIMEOUT: Duration = Duration::from_secs(30);
pub struct DomainReport {
pub label: String,
pub bytes_copied: u64,
pub elapsed: Duration,
}
pub fn copy_domain(
plan: &DomainPlan,
source: HANDLE,
destination: HANDLE,
byte_range: Range<u64>,
chunk_len: usize,
numa_node: Option<NumaNode>,
) -> io::Result<DomainReport> {
affinitize(plan.group, plan.mask)?;
let mut ring = IoRing::new(8, 8)?;
let buffer = NumaBuffer::new(chunk_len, numa_node)?;
let registration = register_buffer(&mut ring, buffer)?;
let start = Instant::now();
let mut offset = byte_range.start;
let mut bytes_copied = 0_u64;
while offset < byte_range.end {
let requested = u32::try_from((byte_range.end - offset).min(chunk_len as u64))
.expect("chunk_len fits in u32");
let read_span = RegisteredSpan {
buffer_index: 0,
offset: 0,
len: requested,
};
let transferred = submit_one(&mut ring, |batch| {
unsafe {
batch.read_registered_raw_owned(
source,
®istration,
read_span,
(),
offset,
PushOptions::new(),
)
}
})?;
if transferred == 0 {
break;
}
let write_span = RegisteredSpan {
buffer_index: 0,
offset: 0,
len: transferred,
};
submit_one(&mut ring, |batch| {
unsafe {
batch.write_registered_raw_owned(
destination,
®istration,
write_span,
(),
offset,
PushOptions::new(),
WriteCaching::Cached,
)
}
})?;
offset += u64::from(transferred);
bytes_copied += u64::from(transferred);
}
drop(registration);
Ok(DomainReport {
label: plan.label.clone(),
bytes_copied,
elapsed: start.elapsed(),
})
}
fn affinitize(group: u16, mask: usize) -> io::Result<()> {
let affinity = GROUP_AFFINITY {
Mask: mask,
Group: group,
Reserved: [0; 3],
};
let ok = unsafe { SetThreadGroupAffinity(GetCurrentThread(), &affinity, ptr::null_mut()) };
if ok == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
fn register_buffer(
ring: &mut IoRing,
buffer: NumaBuffer,
) -> io::Result<RegisteredBuffers<NumaBuffer>> {
let pending = {
let mut batch = Batch::new(ring);
let pending = batch.register_buffers(vec![buffer])?;
batch.submit()?;
pending
};
let (completion, _held) = ring.pop_within(OP_TIMEOUT)?.ok_or_else(|| {
io::Error::new(
io::ErrorKind::TimedOut,
"no completion for buffer registration",
)
})?;
match pending.claim_if(&completion) {
Ok(result) => result,
Err(_) => Err(io::Error::other(
"completion did not match the pending buffer registration",
)),
}
}
fn submit_one<F, E>(ring: &mut IoRing, push: F) -> io::Result<u32>
where
F: FnOnce(&mut Batch<'_>) -> Result<OperationId, E>,
io::Error: From<E>,
{
{
let mut batch = Batch::new(ring);
push(&mut batch)?;
batch.submit()?;
}
let (completion, held) = ring.pop_within(OP_TIMEOUT)?.ok_or_else(|| {
io::Error::new(
io::ErrorKind::TimedOut,
"no completion within the operation timeout",
)
})?;
if held.is_none() {
return Err(io::Error::other(
"completion did not match anything this ring was holding",
));
}
let transferred = completion.result()?;
u32::try_from(transferred)
.map_err(|_| io::Error::other("transferred byte count does not fit in u32"))
}