use super::windows::GLOBAL_CONFIG;
use crate::lockfree::{AtomicWakerSlot, TreiberStack};
use std::ffi::c_void;
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::ptr;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicI64, Ordering};
use std::task::{Context, Poll};
use windows_sys::Win32::Foundation::{
CloseHandle, ERROR_IO_PENDING, ERROR_PIPE_BUSY, ERROR_PIPE_CONNECTED, GENERIC_READ,
GENERIC_WRITE, GetLastError, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_FLAG_OVERLAPPED, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, ReadFile, WriteFile,
};
use windows_sys::Win32::System::IO::{
CreateIoCompletionPort, GetQueuedCompletionStatusEx, OVERLAPPED, OVERLAPPED_ENTRY,
};
use windows_sys::Win32::System::Pipes::{
ConnectNamedPipe, CreateNamedPipeW, PIPE_READMODE_BYTE, PIPE_TYPE_BYTE,
PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, WaitNamedPipeW,
};
const WAKE_KEY: usize = 0;
const PIPE_KEY: usize = 1;
const PENDING: i64 = i64::MIN;
#[repr(align(64))]
struct Port {
handle: HANDLE,
}
unsafe impl Send for Port {}
unsafe impl Sync for Port {}
static PORT: OnceLock<Port> = OnceLock::new();
fn port() -> HANDLE {
let p = PORT.get_or_init(|| {
let handle = unsafe { CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 1) };
assert!(
!handle.is_null(),
"dtact-io: named-pipe CreateIoCompletionPort failed"
);
std::thread::Builder::new()
.name("dtact-io-namedpipe-iocp".into())
.spawn(worker_loop)
.expect("failed to spawn dtact-io named-pipe IOCP worker thread");
Port { handle }
});
p.handle
}
#[repr(C)]
struct OpState {
overlapped: OVERLAPPED,
result: AtomicI64,
waker: AtomicWakerSlot,
}
impl OpState {
const fn fresh() -> Self {
Self {
overlapped: unsafe { std::mem::zeroed() },
result: AtomicI64::new(PENDING),
waker: AtomicWakerSlot::new(),
}
}
}
unsafe impl Send for OpState {}
unsafe impl Sync for OpState {}
#[allow(dead_code)]
#[repr(align(64))]
struct SlotPool {
slots: Box<[OpState]>,
free: TreiberStack,
}
static RING_DEPTH: OnceLock<usize> = OnceLock::new();
static SLOT_POOL: OnceLock<SlotPool> = OnceLock::new();
fn slot_pool() -> &'static SlotPool {
SLOT_POOL.get_or_init(|| {
let depth = *RING_DEPTH.get_or_init(|| 256);
let mut slots = Vec::with_capacity(depth);
for _ in 0..depth {
slots.push(OpState::fresh());
}
let free = TreiberStack::new(depth);
for i in 0..depth as u32 {
free.push(i);
}
SlotPool {
slots: slots.into_boxed_slice(),
free,
}
})
}
pub fn init(ring_depth: u32) {
let _ = RING_DEPTH.set(ring_depth.max(1) as usize);
let _ = slot_pool();
let _ = port();
}
fn ensure_init() {
if RING_DEPTH.get().is_none() {
init(256);
}
}
const fn encode_ok(n: usize) -> i64 {
n as i64
}
const fn encode_err(win32_code: u32) -> i64 {
-(win32_code as i64)
}
fn decode(result: i64) -> io::Result<usize> {
if result >= 0 {
Ok(result as usize)
} else {
Err(io::Error::from_raw_os_error(-result as i32))
}
}
fn worker_loop() {
let iocp = port();
let mut entries: [OVERLAPPED_ENTRY; 64] = unsafe { std::mem::zeroed() };
loop {
let mut removed: u32 = 0;
let ok = unsafe {
GetQueuedCompletionStatusEx(
iocp,
entries.as_mut_ptr(),
entries.len() as u32,
&raw mut removed,
u32::MAX,
0,
)
};
if ok == 0 {
continue;
}
for entry in &entries[..removed as usize] {
if entry.lpCompletionKey == WAKE_KEY {
continue;
}
let op_ptr = entry.lpOverlapped.cast::<OpState>();
if op_ptr.is_null() {
continue;
}
let op = unsafe { &*op_ptr };
let bytes = entry.dwNumberOfBytesTransferred as usize;
op.result.store(encode_ok(bytes), Ordering::Release);
op.waker.take_and_wake();
}
}
}
#[repr(align(64))]
enum Slot {
Pooled { shard_idx: usize, slot_idx: u32 },
Heap(Box<OpState>),
}
#[repr(align(64))]
struct Shard {
slots: Box<[OpState]>,
free: TreiberStack,
}
#[repr(align(64))]
struct ShardedSlotPool {
shards: Box<[Shard]>,
}
static SHARDED_POOL: OnceLock<ShardedSlotPool> = OnceLock::new();
fn init_sharded_pool() -> &'static ShardedSlotPool {
SHARDED_POOL.get_or_init(|| {
let num_workers = GLOBAL_CONFIG.get().map_or(1, |c| c.workers).max(1);
let depth_per_shard = *RING_DEPTH.get_or_init(|| 256);
let mut shards = Vec::with_capacity(num_workers);
for _ in 0..num_workers {
let mut slots = Vec::with_capacity(depth_per_shard);
for _ in 0..depth_per_shard {
slots.push(OpState::fresh());
}
let free = TreiberStack::new(depth_per_shard);
for i in 0..depth_per_shard as u32 {
free.push(i);
}
shards.push(Shard {
slots: slots.into_boxed_slice(),
free,
});
}
ShardedSlotPool {
shards: shards.into_boxed_slice(),
}
})
}
fn get_local_shard_idx(num_shards: usize) -> usize {
thread_local! {
static LAZY_SHARD_IDX: usize = {
static COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
COUNTER.fetch_add(1, Ordering::Relaxed)
};
}
LAZY_SHARD_IDX.with(|&idx| idx % num_shards)
}
fn acquire_slot() -> Slot {
let pool = init_sharded_pool();
let num_shards = pool.shards.len();
let shard_idx = get_local_shard_idx(num_shards);
let shard = &pool.shards[shard_idx];
shard.free.pop().map_or_else(
|| Slot::Heap(Box::new(OpState::fresh())),
|idx| {
shard.slots[idx as usize]
.result
.store(PENDING, Ordering::Relaxed);
Slot::Pooled {
shard_idx,
slot_idx: idx,
}
},
)
}
#[repr(align(64))]
struct IoOp {
slot: Slot,
}
impl IoOp {
#[inline]
fn state(&self) -> &OpState {
match &self.slot {
Slot::Pooled {
shard_idx,
slot_idx,
} => &init_sharded_pool().shards[*shard_idx].slots[*slot_idx as usize],
Slot::Heap(b) => b,
}
}
#[inline]
fn overlapped_ptr(&self) -> *mut OVERLAPPED {
match &self.slot {
Slot::Pooled {
shard_idx,
slot_idx,
} => {
let state_ref = &init_sharded_pool().shards[*shard_idx].slots[*slot_idx as usize];
std::ptr::from_ref::<OpState>(state_ref).cast_mut().cast()
}
Slot::Heap(b) => std::ptr::from_ref::<OpState>(b.as_ref()).cast_mut().cast(),
}
}
}
impl Future for IoOp {
type Output = io::Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
let r = self.state().result.load(Ordering::Acquire);
if r != PENDING {
return Poll::Ready(decode(r));
}
self.state().waker.register(cx.waker());
let r = self.state().result.load(Ordering::Acquire);
if r != PENDING {
return Poll::Ready(decode(r));
}
Poll::Pending
}
}
impl Drop for IoOp {
fn drop(&mut self) {
if let Slot::Pooled {
shard_idx,
slot_idx,
} = self.slot
{
let pool = init_sharded_pool();
let shard = &pool.shards[shard_idx];
let done = shard.slots[slot_idx as usize]
.result
.load(Ordering::Acquire)
!= PENDING;
if done {
shard.free.push(slot_idx);
}
}
}
}
#[repr(align(64))]
enum IoOpResult {
Ready(io::Result<usize>),
Pending(IoOp),
}
fn issue_read(handle: HANDLE, buf: &mut [u8]) -> IoOpResult {
let slot = acquire_slot();
let op = IoOp { slot };
let ov_ptr = op.overlapped_ptr();
let mut bytes_transferred: u32 = 0;
let ok = unsafe {
ReadFile(
handle,
buf.as_mut_ptr(),
buf.len() as u32,
&raw mut bytes_transferred,
ov_ptr,
)
};
if ok != 0 {
op.state()
.result
.store(encode_ok(bytes_transferred as usize), Ordering::Relaxed);
return IoOpResult::Ready(Ok(bytes_transferred as usize));
}
let err = unsafe { GetLastError() };
if err == ERROR_IO_PENDING {
IoOpResult::Pending(op)
} else {
op.state().result.store(encode_err(err), Ordering::Relaxed);
IoOpResult::Ready(Err(io::Error::from_raw_os_error(err as i32)))
}
}
fn issue_write(handle: HANDLE, buf: &[u8]) -> IoOpResult {
let slot = acquire_slot();
let op = IoOp { slot };
let ov_ptr = op.overlapped_ptr();
let mut bytes_transferred: u32 = 0;
let ok = unsafe {
WriteFile(
handle,
buf.as_ptr(),
buf.len() as u32,
&raw mut bytes_transferred,
ov_ptr,
)
};
if ok != 0 {
op.state()
.result
.store(encode_ok(bytes_transferred as usize), Ordering::Relaxed);
return IoOpResult::Ready(Ok(bytes_transferred as usize));
}
let err = unsafe { GetLastError() };
if err == ERROR_IO_PENDING {
IoOpResult::Pending(op)
} else {
op.state().result.store(encode_err(err), Ordering::Relaxed);
IoOpResult::Ready(Err(io::Error::from_raw_os_error(err as i32)))
}
}
fn issue_connect(handle: HANDLE) -> IoOp {
let op = IoOp {
slot: acquire_slot(),
};
let ov_ptr = op.overlapped_ptr();
let ok = unsafe { ConnectNamedPipe(handle, ov_ptr) };
if ok == 0 {
let err = unsafe { GetLastError() };
if err == ERROR_PIPE_CONNECTED {
op.state().result.store(encode_ok(0), Ordering::Release);
} else if err != ERROR_IO_PENDING {
op.state().result.store(encode_err(err), Ordering::Release);
}
}
op
}
fn to_wide(s: &str) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
std::ffi::OsStr::new(s)
.encode_wide()
.chain(Some(0))
.collect()
}
#[repr(align(64))]
pub struct DtactNamedPipeHandle {
handle: HANDLE,
}
unsafe impl Send for DtactNamedPipeHandle {}
unsafe impl Sync for DtactNamedPipeHandle {}
impl DtactNamedPipeHandle {
pub async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
match issue_read(self.handle, buf) {
IoOpResult::Ready(res) => res,
IoOpResult::Pending(fut) => fut.await,
}
}
pub async fn write(&self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
match issue_write(self.handle, buf) {
IoOpResult::Ready(res) => res,
IoOpResult::Pending(fut) => fut.await,
}
}
}
impl Drop for DtactNamedPipeHandle {
fn drop(&mut self) {
unsafe {
CloseHandle(self.handle);
}
}
}
impl crate::io::AsyncRead for DtactNamedPipeHandle {
async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
self.read(buf).await
}
}
impl crate::io::AsyncWrite for DtactNamedPipeHandle {
async fn write(&self, buf: &[u8]) -> io::Result<usize> {
self.write(buf).await
}
}
pub struct DtactNamedPipeServer {
handle: HANDLE,
}
unsafe impl Send for DtactNamedPipeServer {}
unsafe impl Sync for DtactNamedPipeServer {}
impl DtactNamedPipeServer {
pub fn create(name: &str) -> io::Result<Self> {
ensure_init();
let wide = to_wide(name);
let handle = unsafe {
CreateNamedPipeW(
wide.as_ptr(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
4096,
4096,
0,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let iocp = port();
let assoc = unsafe { CreateIoCompletionPort(handle, iocp, PIPE_KEY, 0) };
if assoc.is_null() {
let e = io::Error::last_os_error();
unsafe { CloseHandle(handle) };
return Err(e);
}
Ok(Self { handle })
}
pub async fn connect(self) -> io::Result<DtactNamedPipeHandle> {
issue_connect(self.handle).await?;
let handle = self.handle;
std::mem::forget(self); Ok(DtactNamedPipeHandle { handle })
}
}
impl Drop for DtactNamedPipeServer {
fn drop(&mut self) {
unsafe {
CloseHandle(self.handle);
}
}
}
pub struct DtactNamedPipeClient;
impl DtactNamedPipeClient {
pub async fn connect(name: &str) -> io::Result<DtactNamedPipeHandle> {
ensure_init();
let name_owned = name.to_string();
let (tx, rx) = crate::sync::oneshot::channel();
std::thread::Builder::new()
.name("dtact-io-namedpipe-connect".into())
.spawn(move || {
let _ = tx.send(connect_blocking(&name_owned).map(SendHandle));
})
.expect("failed to spawn dtact-io named-pipe connect thread");
let handle = rx
.await
.unwrap_or_else(|_| {
Err(io::Error::other(
"dtact-io: named-pipe connect thread panicked before sending a result",
))
})?
.0;
let iocp = port();
let assoc = unsafe { CreateIoCompletionPort(handle, iocp, PIPE_KEY, 0) };
if assoc.is_null() {
let e = io::Error::last_os_error();
unsafe { CloseHandle(handle) };
return Err(e);
}
Ok(DtactNamedPipeHandle { handle })
}
}
struct SendHandle(HANDLE);
unsafe impl Send for SendHandle {}
fn connect_blocking(name: &str) -> io::Result<HANDLE> {
let wide = to_wide(name);
loop {
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
0,
ptr::null_mut(),
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
ptr::null_mut::<c_void>() as HANDLE,
)
};
if handle != INVALID_HANDLE_VALUE {
return Ok(handle);
}
let err = unsafe { GetLastError() };
if err != ERROR_PIPE_BUSY {
return Err(io::Error::last_os_error());
}
unsafe { WaitNamedPipeW(wide.as_ptr(), 5000) };
}
}