use super::{AssociatedEndpoint, CompletionPort, Issued, Submitted};
use crate::{Operation, OperationState, UnassociatedEndpoint};
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::io::OwnedHandle;
use std::path::PathBuf;
const FILE_FLAG_OVERLAPPED: u32 = 0x4000_0000;
fn associate_temp_file<'port>(
port: &'port CompletionPort,
tag: &str,
) -> (AssociatedEndpoint<'port>, PathBuf) {
let path = std::env::temp_dir().join(format!(
"windows-overlapped-io-sys-{tag}-{}.tmp",
std::process::id()
));
let file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.custom_flags(FILE_FLAG_OVERLAPPED)
.open(&path)
.expect("create overlapped temp file");
let owned = OwnedHandle::from(file);
let endpoint = unsafe { UnassociatedEndpoint::assume_overlapped(owned) };
let associated = port.associate(endpoint, 0).expect("associate");
(associated, path)
}
#[test]
fn posts_and_dequeues_a_user_packet() {
let port = CompletionPort::new(0).expect("create port");
port.post(0xABCD, 42).expect("post packet");
let completion = port.get(1_000).expect("get packet").expect("a packet");
assert_eq!(completion.key(), 0xABCD);
assert_eq!(completion.bytes_transferred(), 42);
assert!(completion.overlapped_ptr().is_null());
assert!(completion.error().is_none());
}
#[test]
fn get_times_out_when_empty() {
let port = CompletionPort::new(0).expect("create port");
assert!(port.get(0).expect("get").is_none());
}
#[test]
fn associates_an_overlapped_handle() {
let port = CompletionPort::new(0).expect("create port");
let path = std::env::temp_dir().join(format!(
"windows-overlapped-io-sys-iocp-{}.tmp",
std::process::id()
));
let file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.custom_flags(FILE_FLAG_OVERLAPPED)
.open(&path)
.expect("create overlapped temp file");
let owned = OwnedHandle::from(file);
let endpoint = unsafe { UnassociatedEndpoint::assume_overlapped(owned) };
let associated = port.associate(endpoint, 0x55).expect("associate");
assert_eq!(associated.key(), 0x55);
drop(associated);
let _ = std::fs::remove_file(&path);
}
#[test]
fn submit_pending_then_claim_recovers_the_operation() {
let port = CompletionPort::new(0).expect("create port");
let (endpoint, path) = associate_temp_file(&port, "submit-pending");
let operation = Operation::new(vec![1_u8, 2, 3]);
let submitted = unsafe {
endpoint.submit(operation, |_handle, overlapped| {
port.post_raw(0, 3, overlapped)?;
Ok(Issued::Pending)
})
};
assert!(matches!(submitted, Submitted::Pending(_)));
let Submitted::Pending(id) = submitted else {
unreachable!("just asserted pending");
};
assert_eq!(port.outstanding(), 1);
let completion = port.get(1_000).expect("get").expect("a packet");
assert_eq!(completion.overlapped_ptr(), id.as_ptr());
let operation = unsafe { completion.claim::<Vec<u8>>() };
assert_eq!(operation.state(), OperationState::Completed);
assert_eq!(operation.payload(), &vec![1_u8, 2, 3]);
assert_eq!(port.outstanding(), 0);
drop(endpoint);
let _ = std::fs::remove_file(&path);
}
#[test]
fn submit_immediate_failure_returns_the_operation() {
let port = CompletionPort::new(0).expect("create port");
let (endpoint, path) = associate_temp_file(&port, "submit-fail");
let operation = Operation::new(vec![9_u8]);
let submitted = unsafe {
endpoint.submit(operation, |_handle, _overlapped| {
Err(std::io::Error::from_raw_os_error(5))
})
};
match submitted {
Submitted::Failed { operation, error } => {
assert_eq!(operation.payload(), &vec![9_u8]);
assert_eq!(operation.state(), OperationState::Idle);
assert_eq!(error.raw_os_error(), Some(5));
}
Submitted::Completed { .. } => panic!("expected immediate failure"),
Submitted::Pending(_) => panic!("expected immediate failure"),
}
assert_eq!(port.outstanding(), 0);
drop(endpoint);
let _ = std::fs::remove_file(&path);
}
#[test]
fn unclaimed_completion_reclaims_on_drop() {
let port = CompletionPort::new(0).expect("create port");
let (endpoint, path) = associate_temp_file(&port, "unclaimed");
let operation = Operation::new(vec![7_u8]);
let submitted = unsafe {
endpoint.submit(operation, |_handle, overlapped| {
port.post_raw(0, 1, overlapped)?;
Ok(Issued::Pending)
})
};
assert!(matches!(submitted, Submitted::Pending(_)));
assert_eq!(port.outstanding(), 1);
let completion = port.get(1_000).expect("get").expect("a packet");
drop(completion);
assert_eq!(port.outstanding(), 0);
drop(endpoint);
let _ = std::fs::remove_file(&path);
}
#[test]
fn dequeuing_clears_the_outstanding_count_even_while_held() {
let port = CompletionPort::new(0).expect("create port");
let (endpoint, path) = associate_temp_file(&port, "held-outstanding");
let operation = Operation::new(vec![7_u8]);
let submitted = unsafe {
endpoint.submit(operation, |_handle, overlapped| {
port.post_raw(0, 1, overlapped)?;
Ok(Issued::Pending)
})
};
assert!(matches!(submitted, Submitted::Pending(_)));
assert_eq!(port.outstanding(), 1, "queued, not yet delivered");
let completion = port.get(1_000).expect("get").expect("a packet");
assert_eq!(
port.outstanding(),
0,
"the packet has been delivered, so nothing is outstanding while it is held"
);
drop(completion);
assert_eq!(port.outstanding(), 0);
drop(endpoint);
let _ = std::fs::remove_file(&path);
}
#[test]
fn dropping_the_port_while_a_completion_is_held_does_not_hang() {
use std::sync::mpsc;
let (tx, rx) = mpsc::channel::<()>();
std::thread::spawn(move || {
let port = CompletionPort::new(0).expect("create port");
let (endpoint, path) = associate_temp_file(&port, "held-port-drop");
let operation = Operation::new(vec![9_u8]);
let submitted = unsafe {
endpoint.submit(operation, |_handle, overlapped| {
port.post_raw(0, 1, overlapped)?;
Ok(Issued::Pending)
})
};
assert!(matches!(submitted, Submitted::Pending(_)));
let completion = port.get(1_000).expect("get").expect("a packet");
drop(endpoint);
drop(port);
drop(completion);
let _ = std::fs::remove_file(&path);
let _ = tx.send(());
});
rx.recv_timeout(std::time::Duration::from_secs(20))
.expect("dropping the port with a completion held must not block");
}
#[test]
fn run_down_does_not_hang_when_another_thread_clears_the_last_operation() {
use crate::OperationId;
use std::sync::Arc;
use std::sync::mpsc;
let port = Arc::new(CompletionPort::new(0).expect("create port"));
let overlapped = 0x1000_usize as *mut super::OVERLAPPED;
port.state.live.insert(OperationId::mint(overlapped));
assert_eq!(port.outstanding(), 1);
let (tx, rx) = mpsc::channel::<()>();
let drainer = Arc::clone(&port);
std::thread::spawn(move || {
drainer.run_down().expect("run_down");
let _ = tx.send(());
});
std::thread::sleep(std::time::Duration::from_millis(50));
port.state.live.remove(overlapped);
rx.recv_timeout(std::time::Duration::from_secs(20))
.expect("run_down must return once the last operation is cleared by another thread");
assert_eq!(port.outstanding(), 0);
}
#[test]
fn synchronous_completion_reclaims_inline_without_a_packet() {
let port = CompletionPort::new(0).expect("create port");
let (endpoint, path) = associate_temp_file(&port, "sync-complete");
let operation = Operation::new(vec![5_u8, 6, 7]);
let submitted = unsafe {
endpoint.submit(operation, |_handle, _overlapped| {
Ok(Issued::Completed {
bytes_transferred: 3,
})
})
};
match submitted {
Submitted::Completed {
operation,
bytes_transferred,
} => {
assert_eq!(bytes_transferred, 3);
assert_eq!(operation.state(), OperationState::Completed);
assert_eq!(operation.payload(), &vec![5_u8, 6, 7]);
}
_ => panic!("expected synchronous completion"),
}
assert_eq!(port.outstanding(), 0);
drop(endpoint);
let _ = std::fs::remove_file(&path);
}