use std::{
io,
sync::{Mutex, MutexGuard},
};
use windows_sys::Win32::{
Foundation::{CloseHandle, GetLastError, HANDLE, INVALID_HANDLE_VALUE},
System::IO::CreateIoCompletionPort,
};
use super::{DWORD, ULONG_PTR};
use crate::FileHandle;
pub struct IOCompletionPort {
io_completion_port: Mutex<HANDLE>,
}
unsafe impl Send for IOCompletionPort {}
unsafe impl Sync for IOCompletionPort {}
impl IOCompletionPort {
pub fn new(
file_handle: &FileHandle,
existing_completion_port: Option<&IOCompletionPort>,
completion_key: ULONG_PTR,
number_of_concurrent_threads: DWORD,
) -> io::Result<Self> {
let existing_completion_port_handle = if let Some(port) = existing_completion_port {
Some(port.mutex_guarded_handle()?)
} else {
None
};
let io_completion_port = unsafe {
CreateIoCompletionPort(
file_handle.handle,
existing_completion_port_handle.map_or(std::ptr::null_mut(), |handle| *handle),
completion_key,
number_of_concurrent_threads,
)
};
if io_completion_port == INVALID_HANDLE_VALUE {
let error_code = unsafe { GetLastError() };
return Err(io::Error::from_raw_os_error(error_code as i32));
}
Ok(Self {
io_completion_port: Mutex::new(io_completion_port),
})
}
pub fn mutex_guarded_handle(&self) -> io::Result<MutexGuard<'_, HANDLE>> {
self.io_completion_port.lock().map_err(|_| {
io::Error::new(
io::ErrorKind::WouldBlock,
"Unable to acquire lock on IOCompletionPort.",
)
})
}
}
impl Drop for IOCompletionPort {
fn drop(&mut self) {
let handle = match self.io_completion_port.lock() {
Ok(guard) => *guard,
Err(_) => {
tracing::warn!("Error when locking IOCompletionPort.");
return;
}
};
let result = unsafe { CloseHandle(handle) };
if result == 0 {
let error_code = unsafe { GetLastError() };
let error = io::Error::from_raw_os_error(error_code as i32);
tracing::warn!("Error when dropping IOCompletionPort: {:?}", error);
}
}
}
impl Default for IOCompletionPort {
fn default() -> Self {
Self {
io_completion_port: Mutex::new(INVALID_HANDLE_VALUE),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::win::file_handle::{AccessMode, ShareMode};
#[test]
fn create_io_completion_port() {
let file_name = "../test_data/delete_set_50pts.bin";
let file_handle = unsafe { FileHandle::new(file_name, AccessMode::Read, ShareMode::Read) }
.expect("Failed to create file handle.");
let io_completion_port = IOCompletionPort::new(&file_handle, None, 0, 0);
assert!(
io_completion_port.is_ok(),
"Failed to create IOCompletionPort."
);
}
#[test]
fn drop_io_completion_port() {
let file_name = "../test_data/delete_set_50pts.bin";
let file_handle = unsafe { FileHandle::new(file_name, AccessMode::Read, ShareMode::Read) }
.expect("Failed to create file handle.");
let io_completion_port = IOCompletionPort::new(&file_handle, None, 0, 0)
.expect("Failed to create IOCompletionPort.");
let _ = io_completion_port;
}
#[test]
fn default_io_completion_port() {
let io_completion_port = IOCompletionPort::default();
assert_eq!(
*io_completion_port.mutex_guarded_handle().unwrap(),
INVALID_HANDLE_VALUE,
"Default IOCompletionPort did not have INVALID_HANDLE_VALUE."
);
}
}