use std::io;
use std::os::windows::io::OwnedHandle;
use std::sync::{Arc, Weak};
use std::time::Duration;
use win_shared_os_owned_handle::SharedHandle;
use win_sync_sys::{Event, ResetMode};
use win_time_sys::{Clock, InterruptClock, InterruptTime, Steady, TimePoint};
use windows_ioring_sys::{
Batch, Completion, IoBuf, IoBufMut, IoRing, PushRefused, RegisteredBuffers, RegisteredFiles,
RegisteredSpan,
};
use windows_threadpool_sys::callback_env::CallbackEnviron;
use crate::windows_backend::pool::{PoolDelivery, PoolEngine};
use crate::windows_backend::signalled::SignalledDelivery;
use crate::windows_backend::{
Construction, Win32FileTarget, WindowsBackend, WindowsBackendError, WindowsBackendRefusal,
WindowsBackendSetup,
};
use durable_ioring::DurableIoCore;
use durable_ioring::IoTarget;
use durable_ioring::StampClock;
use durable_ioring::backend::{IoBackend, RegisteredRegion, SubmissionData};
use durable_ioring::driver::Home;
use durable_ioring::driver::PumpedDelivery;
use durable_ioring::driver::first_repeat;
use durable_ioring::driver::{Answer, DomainCall};
use durable_ioring::driver::{Engine, EngineGuard};
use durable_ioring::{
AddTargetError, DurabilityRequest, EndLineageError, EndLineageRefusal, Epoch, EpochState,
FailureInfo, FlushDomain, ImportScope, Imported, IoTargetKey, Leftovers, LineageInfo,
ReadOptions, RemoveTargetError, Resolution, ResolveError, RetireLineageError,
RetireLineageRefusal, SealError, Tag, UnknownLineage, WriteOptions,
};
use durable_ioring::{DiagnosticSink, NoDiagnostics};
use durable_ioring::{DioringIds, FailureId, FailureToken, Lineage, LineageHandle};
use durable_ioring::{
DomainCompletion, DomainRequest, DurabilityProvider, FlushRequest, ProviderPart,
ProviderRequest,
};
use durable_ioring::{DurableRing, EntryOf, EpochId, PushResult, RegisteredBufferRing};
mod delivery;
mod hub;
use delivery::Delivery;
use hub::Hub;
#[cfg(test)]
pub(crate) mod tests;
pub trait TimeBase: StampClock<Instant = WinInstant> {}
impl<K: StampClock<Instant = WinInstant>> TimeBase for K {}
pub type WinInstant = TimePoint<InterruptTime>;
pub type WindowsIds<E> = DioringIds<E, WinInstant>;
#[derive(Clone, Copy, Debug, Default)]
pub struct InterruptStamps;
impl StampClock for InterruptStamps {
type Instant = WinInstant;
fn now(&self) -> WinInstant {
Clock::now(&InterruptClock)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FromClock<K>(pub K);
impl<K: Clock<Timeline = InterruptTime> + Steady + Send + 'static> StampClock for FromClock<K> {
type Instant = WinInstant;
fn now(&self) -> WinInstant {
Clock::now(&self.0)
}
}
pub(crate) const REGISTRATION_BOUND: Duration = Duration::from_secs(30);
pub struct Setup<'env, 'pool, E: EpochId + 'static, R> {
pub submission_queue_size: u32,
pub completion_queue_size: u32,
pub files: Vec<TargetSetup>,
pub buffers: Vec<R>,
pub provider: Option<Box<dyn DurabilityProvider<WindowsIds<E>>>>,
pub diagnostics: Option<Arc<dyn DiagnosticSink>>,
pub delivery: DeliveryMode<'env, 'pool>,
}
pub enum DeliveryMode<'env, 'pool> {
Pool(Option<&'env mut CallbackEnviron<'pool>>),
Pumped,
Signalled(Option<&'env mut CallbackEnviron<'pool>>),
}
#[derive(Debug)]
pub struct TargetSetup {
pub key: IoTargetKey,
pub target: Win32FileTarget,
}
#[derive(Debug)]
pub enum SetupRefusal {
DuplicateKey(IoTargetKey),
DuplicateProviderDomain(FlushDomain),
Ring(io::Error),
}
pub struct SetupError<E: EpochId + 'static, R> {
pub reason: SetupRefusal,
pub files: Vec<TargetSetup>,
pub buffers: Option<Vec<R>>,
pub provider: Option<Box<dyn DurabilityProvider<WindowsIds<E>>>>,
}
impl<E: EpochId + 'static, R> std::fmt::Debug for SetupError<E, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SetupError")
.field("reason", &self.reason)
.field("files", &self.files)
.field("buffers", &self.buffers.as_ref().map(Vec::len))
.field("provider", &self.provider)
.finish()
}
}
pub struct Dioring<
B: IoBuf,
E: EpochId + 'static = u64,
C = (),
R: IoBufMut = Vec<u8>,
K: StampClock = InterruptStamps,
> {
pub(crate) delivery: Option<Delivery<E, B, C, K, R>>,
pub(crate) hub: Arc<Hub<E, B, C, K, R>>,
pub(crate) provider: Option<Box<dyn DurabilityProvider<WindowsIds<E>>>>,
pub(crate) default: LineageHandle,
}
impl<B, E, C, R> Dioring<B, E, C, R>
where
B: IoBuf + Send + 'static,
E: EpochId + Send + Sync + 'static,
C: Send + 'static,
R: IoBufMut + Send + 'static,
{
pub fn new(setup: Setup<'_, '_, E, R>) -> Result<(Self, LineageHandle), SetupError<E, R>> {
Self::with_clock(setup, InterruptStamps)
}
}
impl<B, E, C, R, K> Dioring<B, E, C, R, K>
where
B: IoBuf + Send + 'static,
E: EpochId + Send + Sync + 'static,
C: Send + 'static,
R: IoBufMut + Send + 'static,
K: TimeBase,
{
pub fn with_clock(
setup: Setup<'_, '_, E, R>,
clock: K,
) -> Result<(Self, LineageHandle), SetupError<E, R>> {
Self::with_construction(setup, clock, Construction::default())
}
pub(crate) fn with_construction(
setup: Setup<'_, '_, E, R>,
clock: K,
construction: Construction<B>,
) -> Result<(Self, LineageHandle), SetupError<E, R>> {
let Setup {
submission_queue_size,
completion_queue_size,
files,
buffers,
provider,
diagnostics,
delivery,
} = setup;
let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(NoDiagnostics));
if let Some(key) = first_repeat(files.iter().map(|f| f.key)) {
return Err(SetupError {
reason: SetupRefusal::DuplicateKey(key),
files,
buffers: Some(buffers),
provider,
});
}
let provider_domains = provider.as_ref().map(|p| p.domains()).unwrap_or_default();
if let Some(domain) = first_repeat(provider_domains.iter().cloned()) {
return Err(SetupError {
reason: SetupRefusal::DuplicateProviderDomain(domain),
files,
buffers: Some(buffers),
provider,
});
}
let signals = Event::new(ResetMode::Auto, false)
.and_then(|readiness| Ok((readiness.try_clone()?, readiness)));
let (ready, readiness) = match signals {
Ok(signals) => signals,
Err(error) => {
return Err(SetupError {
reason: SetupRefusal::Ring(error),
files,
buffers: Some(buffers),
provider,
});
}
};
let mut core = if provider.is_some() {
DurableIoCore::with_provider(clock, Arc::clone(&diagnostics), provider_domains)
.expect("a domain named twice was refused above")
} else {
DurableIoCore::new(clock, Arc::clone(&diagnostics))
};
let keys: Vec<IoTargetKey> = files.iter().map(|f| f.key).collect();
for file in &files {
core.add_target(file.key, file.target.describe())
.expect("the keys were checked unique above");
}
let targets = files
.into_iter()
.map(|TargetSetup { key, target }| (key, target))
.collect();
let backend = match WindowsBackend::construct(
WindowsBackendSetup {
submission_queue_size,
completion_queue_size,
targets,
buffers,
},
construction,
) {
Ok(backend) => backend,
Err(WindowsBackendError {
reason,
targets,
buffers,
}) => {
let reason = match reason {
WindowsBackendRefusal::DuplicateKey(key) => SetupRefusal::DuplicateKey(key),
WindowsBackendRefusal::Ring(error) => SetupRefusal::Ring(error),
};
return Err(SetupError {
reason,
files: setups(targets),
buffers,
provider,
});
}
};
let default_lineage = core.default_lineage();
let on_ready = move || {
let set = ready.set();
debug_assert!(set.is_ok(), "setting the readiness event failed: {set:?}");
};
let engine = Engine::new(core, backend);
let delivered = match delivery {
DeliveryMode::Pool(environment) => {
PoolDelivery::new(engine, on_ready, environment).map(Delivery::Pool)
}
DeliveryMode::Pumped => PumpedDelivery::new(engine).map(Delivery::Pumped),
DeliveryMode::Signalled(environment) => {
SignalledDelivery::new(engine, environment).map(Delivery::Signalled)
}
};
let delivery = match delivered {
Ok(delivery) => delivery,
Err((error, engine)) => {
let mut backend = engine.backend;
let files = keys
.into_iter()
.filter_map(|key| {
backend
.detach(key)
.map(|target| TargetSetup { key, target })
})
.collect();
return Err(SetupError {
reason: SetupRefusal::Ring(error),
files,
buffers: None,
provider,
});
}
};
let hub = Arc::new(Hub::new(
delivery.shared(),
readiness,
default_lineage.instance,
diagnostics,
));
let default = LineageHandle::mint(default_lineage, home(&hub));
let handle = default.clone();
Ok((
Self {
delivery: Some(delivery),
hub,
provider,
default,
},
handle,
))
}
}
fn setups(targets: Vec<(IoTargetKey, Win32FileTarget)>) -> Vec<TargetSetup> {
targets
.into_iter()
.map(|(key, target)| TargetSetup { key, target })
.collect()
}
fn home<T: Home + 'static>(hub: &Arc<T>) -> Weak<dyn Home> {
let home: Weak<T> = Arc::downgrade(hub);
home
}
fn answers<T: Answer + 'static>(hub: &Arc<T>) -> Weak<dyn Answer> {
let answers: Weak<T> = Arc::downgrade(hub);
answers
}
impl<B, E, C, R, K> DurableRing for Dioring<B, E, C, R, K>
where
B: IoBuf + Send + 'static,
E: EpochId + Send + Sync + 'static,
C: Send + 'static,
R: IoBufMut + Send + 'static,
K: TimeBase,
{
type Ids = WindowsIds<E>;
type Buffer = B;
type Context = C;
type Readiness = Event;
type Target = Win32FileTarget;
fn readiness(&mut self) -> io::Result<Event> {
self.hub.readiness()
}
fn add_target(
&mut self,
key: IoTargetKey,
target: Win32FileTarget,
) -> Result<(), AddTargetError<Win32FileTarget>> {
let mut engine = self.engine();
if engine.core.target(key).is_some() || engine.backend.target(key).is_some() {
return Err(AddTargetError { key, target });
}
engine
.core
.add_target(key, target.describe())
.expect("the key is free, checked above");
engine
.backend
.attach(key, target)
.expect("the key is free, checked above");
Ok(())
}
fn remove_target(
&mut self,
key: IoTargetKey,
) -> Result<Win32FileTarget, RemoveTargetError<Self::Ids>> {
let mut engine = self.engine();
engine.core.remove_target(key)?;
Ok(engine
.backend
.detach(key)
.expect("a target the core decided for is attached"))
}
fn write_with(
&mut self,
file: IoTargetKey,
offset: u64,
buffer: B,
tag: Tag<'_, Self::Ids>,
context: C,
options: WriteOptions<Self::Ids>,
) -> PushResult<Self>
where
B: IoBuf,
{
let (pushed, attention) = {
let mut engine = self.engine();
let pushed = engine.write(
file,
offset,
SubmissionData::Owned(buffer),
tag.epoch(),
context,
options,
);
(pushed, engine.take_attention())
};
self.attend(attention);
pushed
}
fn read_with(
&mut self,
file: IoTargetKey,
offset: u64,
buffer: B,
context: C,
options: ReadOptions<Self::Ids>,
) -> PushResult<Self>
where
B: IoBufMut,
{
let (pushed, attention) = {
let mut engine = self.engine();
engine.backend.enable_owned_reads();
let pushed = engine.read(
file,
offset,
SubmissionData::Owned(buffer),
context,
options,
);
(pushed, engine.take_attention())
};
self.attend(attention);
pushed
}
fn pop(&mut self) -> io::Result<Option<EntryOf<Self>>> {
let popped = {
let mut engine = self.engine();
let submitted = engine.backend.submit();
match engine.core.pop() {
Some(entry) => Ok(Some(entry)),
None => match (engine.take_fault(), submitted) {
(Some(fault), _) => Err(io::Error::other(fault)),
(None, Err(cause)) => Err(io::Error::other(cause)),
(None, Ok(())) => Ok(None),
},
}
};
self.call_provider();
popped
}
fn make_durable_through(
&mut self,
through: Tag<'_, Self::Ids>,
) -> Result<DurabilityRequest<Self::Ids>, SealError<Self::Ids>> {
let sealed = self.engine().make_durable_through(through.epoch());
let (answer, deliver) = sealed?;
self.settled(deliver);
Ok(answer)
}
fn durable_through(&self, lineage: Lineage) -> Result<Option<E>, UnknownLineage<Self::Ids>> {
self.engine().core.durable_through(lineage)
}
fn sealed_through(&self, lineage: Lineage) -> Result<Option<E>, UnknownLineage<Self::Ids>> {
self.engine().core.sealed_through(lineage)
}
fn epoch_state(
&self,
epoch: Epoch<Self::Ids>,
) -> Result<EpochState<Self::Ids>, UnknownLineage<Self::Ids>> {
self.engine().core.epoch_state(epoch)
}
fn default_lineage(&self) -> LineageHandle {
self.default.clone()
}
fn mint_lineage(&mut self, description: Option<String>) -> LineageHandle {
let lineage = self.engine().core.mint_lineage(description);
LineageHandle::mint(lineage, home(&self.hub))
}
fn lineages(&self) -> Vec<LineageInfo<Self::Ids>> {
self.engine().core.lineages()
}
fn end_lineage(&mut self, handle: LineageHandle) -> Result<(), EndLineageError<Self::Ids>> {
let lineage = handle.lineage();
let reason = if !self.owns(&handle) {
EndLineageRefusal::Foreign(lineage)
} else if lineage == self.default.lineage() {
EndLineageRefusal::Default
} else {
match handle.take_last() {
Ok(lineage) => {
let ended = {
let mut engine = self.engine();
let ended = engine.core.end_lineage(lineage);
ended.map(|actions| engine.act(actions))
};
match ended {
Ok(deliver) => self.settled(deliver),
Err(refusal) => self.hub.inconsistent(
"the last handle of a lineage found it already ended",
format_args!("lineage {lineage:?}, refused as {refusal:?}"),
),
}
return Ok(());
}
Err(handle) => {
return Err(EndLineageError {
reason: EndLineageRefusal::Shared,
handle,
});
}
}
};
Err(EndLineageError { reason, handle })
}
fn retire_lineage(
&mut self,
handle: LineageHandle,
) -> Result<(), RetireLineageError<Self::Ids>> {
let lineage = handle.lineage();
let reason = if !self.owns(&handle) {
RetireLineageRefusal::Foreign(lineage)
} else if lineage == self.default.lineage() {
RetireLineageRefusal::Default
} else if !handle.is_last() {
RetireLineageRefusal::Shared
} else {
let retired = self.engine().core.retire_lineage(lineage);
match retired {
Err(refusal) => refusal,
Ok(()) => {
let taken = handle.take_last();
debug_assert!(taken.is_ok(), "the last handle, owned here");
return Ok(());
}
}
};
Err(RetireLineageError { reason, handle })
}
fn resolve(
&mut self,
items: Vec<(FailureToken, Resolution)>,
) -> Result<(), ResolveError<Self::Ids>> {
let resolved = {
let mut engine = self.engine();
engine
.core
.resolve(items)
.map(|actions| engine.act(actions))
};
let deliver = resolved?;
self.settled(deliver);
Ok(())
}
fn import_failure(
&mut self,
scope: ImportScope<Self::Ids>,
) -> Result<Imported<Self::Ids>, UnknownLineage<Self::Ids>> {
let imported = {
let mut engine = self.engine();
engine
.core
.import_failure(scope)
.map(|(imported, actions)| (imported, engine.act(actions)))
};
let (imported, deliver) = imported?;
self.settled(deliver);
Ok(imported)
}
fn failures(&self) -> Vec<FailureInfo<Self::Ids>> {
self.engine().core.failures()
}
fn take_token(&mut self, failure: FailureId) -> Option<FailureToken> {
self.engine().core.take_token(failure)
}
fn close(mut self) -> Leftovers<Self::Ids, B, C> {
self.end().unwrap_or_else(|| Leftovers {
held: Vec::new(),
failures: Vec::new(),
lineages: Vec::new(),
})
}
}
impl<B, E, C, R, K> Dioring<B, E, C, R, K>
where
B: IoBuf + Send + 'static,
E: EpochId + Send + Sync + 'static,
C: Send + 'static,
R: IoBufMut + Send + 'static,
K: TimeBase,
{
pub fn pump(&mut self, timeout: Option<Duration>) -> io::Result<bool> {
let Some(pumped) = self
.delivery
.as_mut()
.expect("an instance is live until it ends")
.pump(timeout)
else {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"the instance was built with pool delivery, which drains it; pumping is for \
DeliveryMode::Pumped and DeliveryMode::Signalled",
));
};
self.settled(pumped.ready);
pumped.waited.map(|_| pumped.ready)
}
pub fn completion_signal(&self) -> io::Result<Event> {
self.delivery
.as_ref()
.expect("an instance is live until it ends")
.signal()
.unwrap_or_else(|| {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"only an instance built with DeliveryMode::Signalled has a completion signal",
))
})
}
pub fn completion_event(&self) -> io::Result<OwnedHandle> {
self.delivery
.as_ref()
.expect("an instance is live until it ends")
.completion_event()
.unwrap_or_else(|| {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"only an instance built with DeliveryMode::Pumped offers the ring's own \
completion event; a signalled one offers completion_signal",
))
})
}
fn attend(&self, attention: bool) {
if attention {
self.hub.signal();
}
}
fn settled(&mut self, deliver: bool) {
if deliver {
self.hub.signal();
}
self.call_provider();
}
fn call_provider(&mut self) {
let requests = self.engine().take_requests();
if requests.is_empty() {
return;
}
let home = answers(&self.hub);
let requests: Vec<FlushRequest<WindowsIds<E>>> = requests
.into_iter()
.map(|request| flush_request(request, &home))
.collect();
let provider = self
.provider
.as_mut()
.expect("only a provider's domains make requests");
for request in requests {
provider.make_durable(request);
}
}
}
impl<B: IoBuf, E: EpochId + 'static, C, R: IoBufMut, K: StampClock> Dioring<B, E, C, R, K> {
pub(crate) fn engine(&self) -> EngineGuard<'_, PoolEngine<E, B, C, K, R>> {
self.delivery
.as_ref()
.expect("an instance is live until it ends")
.engine()
}
fn owns(&self, handle: &LineageHandle) -> bool {
handle.is_homed_at(&self.hub)
}
pub(crate) fn end(&mut self) -> Option<Leftovers<DioringIds<E, K::Instant>, B, C>> {
let delivery = self.delivery.take()?;
let engine = delivery.into_engine();
self.provider = None;
self.hub.wait_for_answers();
let Engine { core, backend, .. } = engine;
let leftovers = core.close();
drop(backend);
Some(leftovers)
}
}
fn flush_request<E: EpochId + 'static>(
request: ProviderRequest<WindowsIds<E>>,
home: &Weak<dyn Answer>,
) -> FlushRequest<WindowsIds<E>> {
let ProviderRequest {
lineage,
seal,
domains,
} = request;
let domains = domains
.into_iter()
.map(|ProviderPart { domain, targets }| DomainRequest {
completion: DomainCompletion::new(
Weak::clone(home),
DomainCall {
lineage,
seal,
name: domain.clone(),
},
),
domain,
files: targets,
})
.collect();
FlushRequest { domains }
}
fn region(span: RegisteredSpan) -> RegisteredRegion {
RegisteredRegion {
index: span.buffer_index,
offset: span.offset,
length: span.len,
}
}
#[cfg(feature = "fault-injection")]
impl<B: IoBuf, E: EpochId + 'static, C, R: IoBufMut, K: StampClock> Dioring<B, E, C, R, K> {
pub fn fail_next_flush(&mut self, file: IoTargetKey, code: u32) {
self.engine().backend.fail_next_commit(file, code);
}
}
impl<B, E, C, R, K> RegisteredBufferRing for Dioring<B, E, C, R, K>
where
B: IoBuf + Send + 'static,
E: EpochId + Send + Sync + 'static,
C: Send + 'static,
R: IoBufMut + Send + 'static,
K: TimeBase,
{
type Registered = R;
fn with_registered_buffer<T>(&mut self, i: u32, f: impl FnOnce(&[u8]) -> T) -> io::Result<T> {
self.engine().backend.registered_buffer(i).map(f)
}
fn with_registered_buffer_mut<T>(
&mut self,
i: u32,
f: impl FnOnce(&mut [u8]) -> T,
) -> io::Result<T> {
self.engine().backend.registered_buffer_mut(i).map(f)
}
fn write_registered_with(
&mut self,
file: IoTargetKey,
offset: u64,
span: RegisteredSpan,
tag: Tag<'_, Self::Ids>,
context: C,
options: WriteOptions<Self::Ids>,
) -> PushResult<Self> {
self.engine().write(
file,
offset,
SubmissionData::Registered(region(span)),
tag.epoch(),
context,
options,
)
}
fn read_registered_with(
&mut self,
file: IoTargetKey,
offset: u64,
span: RegisteredSpan,
context: C,
options: ReadOptions<Self::Ids>,
) -> PushResult<Self> {
self.engine().read(
file,
offset,
SubmissionData::Registered(region(span)),
context,
options,
)
}
}
impl<B: IoBuf, E: EpochId + 'static, C, R: IoBufMut, K: StampClock> Drop
for Dioring<B, E, C, R, K>
{
fn drop(&mut self) {
let _ = self.end();
}
}
pub(crate) fn register_files<B, S>(
ring: &mut IoRing<B, S>,
handles: Vec<SharedHandle>,
bound: Duration,
) -> io::Result<Option<RegisteredFiles>> {
if handles.is_empty() {
return Ok(None);
}
let mut batch = Batch::new(ring);
let pending = batch
.register_shared_files(handles)
.map_err(PushRefused::into_error)?;
batch.submit()?;
let completion = registration_completion(ring, bound)?;
match pending.claim_if(&completion) {
Ok(result) => result.map(Some),
Err(_) => Err(io::Error::other(
"the registration's completion named another operation",
)),
}
}
pub(crate) type BufferRegistration<R> =
Result<Option<RegisteredBuffers<R>>, (io::Error, Option<Vec<R>>)>;
pub(crate) fn register_buffers<B, S, R: IoBufMut>(
ring: &mut IoRing<B, S>,
buffers: Vec<R>,
bound: Duration,
) -> BufferRegistration<R> {
if buffers.is_empty() {
return Ok(None);
}
let mut batch = Batch::new(ring);
let pending = batch
.register_buffers(buffers)
.map_err(|refused| (refused.error, refused.payload))?;
batch.submit().map_err(|error| (error, None))?;
let completion = registration_completion(ring, bound).map_err(|error| (error, None))?;
match pending.claim_if(&completion) {
Ok(result) => result.map(Some).map_err(|error| (error, None)),
Err(_) => Err((
io::Error::other("the registration's completion named another operation"),
None,
)),
}
}
fn registration_completion<B, S>(
ring: &mut IoRing<B, S>,
bound: Duration,
) -> io::Result<Completion> {
match ring.pop_within(bound)? {
Some((completion, _)) => Ok(completion),
None => Err(io::Error::new(
io::ErrorKind::TimedOut,
format!("the ring did not complete a registration within {bound:?}"),
)),
}
}