use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::io;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Condvar, Mutex, MutexGuard};
use windows_sys::Win32::System::IO::OVERLAPPED;
static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
fn try_next_generation(sequence: &AtomicU64) -> Option<u64> {
sequence
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
(current != u64::MAX).then(|| current + 1)
})
.ok()
}
fn next_generation(sequence: &AtomicU64) -> u64 {
try_next_generation(sequence).unwrap_or_else(|| {
panic!(
"the operation-generation sequence is exhausted; continuing would reissue \
generations already in use and reintroduce stale-identity aliasing"
)
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OperationId {
overlapped: *mut OVERLAPPED,
generation: u64,
}
unsafe impl Send for OperationId {}
unsafe impl Sync for OperationId {}
impl OperationId {
#[must_use]
pub fn mint(overlapped: *mut OVERLAPPED) -> Self {
Self {
overlapped,
generation: next_generation(&NEXT_GENERATION),
}
}
#[must_use]
pub unsafe fn forge(overlapped: *mut OVERLAPPED, generation: u64) -> Self {
Self {
overlapped,
generation,
}
}
pub(crate) fn from_recorded_parts(overlapped: *mut OVERLAPPED, generation: u64) -> Self {
Self {
overlapped,
generation,
}
}
#[must_use]
pub fn as_ptr(self) -> *mut OVERLAPPED {
self.overlapped
}
#[must_use]
pub fn generation(self) -> u64 {
self.generation
}
}
fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex.lock().unwrap_or_else(|poison| poison.into_inner())
}
#[derive(Debug)]
pub struct OperationRegistry {
live: Mutex<HashMap<usize, u64>>,
drained: Condvar,
}
impl OperationRegistry {
#[must_use]
pub fn new() -> Self {
Self {
live: Mutex::new(HashMap::new()),
drained: Condvar::new(),
}
}
pub fn insert(&self, id: OperationId) {
let mut live = lock(&self.live);
match live.entry(id.as_ptr() as usize) {
Entry::Occupied(occupied) => panic!(
"windows-overlapped-io-sys: operation storage {address:p} was registered for \
generation {new} while generation {existing} was still registered at the same \
address. An address must never be registered while it is available for reuse. \
This is a defect in the completion backend: it either deregistered a completed \
operation after its storage was freed rather than before (leaving a window in \
which a concurrent submission can be handed the same address), or submitted one \
operation's storage twice while it was still in flight.",
address = id.as_ptr(),
new = id.generation(),
existing = occupied.get(),
),
Entry::Vacant(slot) => {
slot.insert(id.generation());
}
}
}
pub fn remove(&self, overlapped: *mut OVERLAPPED) -> Option<OperationId> {
let mut live = lock(&self.live);
let generation = live.remove(&(overlapped as usize));
if live.is_empty() {
self.drained.notify_all();
}
generation.map(|generation| OperationId::from_recorded_parts(overlapped, generation))
}
#[must_use]
pub fn is_live(&self, id: OperationId) -> bool {
lock(&self.live).get(&(id.as_ptr() as usize)) == Some(&id.generation())
}
pub fn cancel_if_live<F>(&self, id: OperationId, cancel: F) -> io::Result<()>
where
F: FnOnce() -> io::Result<()>,
{
let live = lock(&self.live);
if live.get(&(id.as_ptr() as usize)) != Some(&id.generation()) {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"the operation named by this identity is no longer outstanding",
));
}
let result = cancel();
drop(live);
result
}
#[must_use]
pub fn identify(&self, overlapped: *mut OVERLAPPED) -> Option<OperationId> {
lock(&self.live)
.get(&(overlapped as usize))
.copied()
.map(|generation| OperationId::from_recorded_parts(overlapped, generation))
}
#[must_use]
pub fn len(&self) -> usize {
lock(&self.live).len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn wait_until_empty(&self) {
let mut live = lock(&self.live);
while !live.is_empty() {
live = self
.drained
.wait(live)
.unwrap_or_else(|poison| poison.into_inner());
}
}
}
impl Default for OperationRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests;