#![cfg_attr(
not(target_os = "windows"),
allow(dead_code, reason = "only the Windows store takes locks")
)]
pub(crate) const LF_SYNC: u32 = 0x1;
pub(crate) const LF_READ: u32 = 0x2;
pub(crate) const LF_READWRITE: u32 = 0x6;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LockKind {
Read,
ReadWrite,
}
impl LockKind {
pub(crate) fn of(flags: u32) -> Self {
if flags & LF_READWRITE == LF_READWRITE {
Self::ReadWrite
} else {
Self::Read
}
}
pub(crate) fn flags(self) -> u32 {
match self {
Self::Read => LF_READ,
Self::ReadWrite => LF_READWRITE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LockDecision {
Grant(LockKind),
Queued,
Refused,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct LockArbiter {
held: Option<LockKind>,
pending: Option<LockKind>,
deferred: Option<LockKind>,
}
impl LockArbiter {
pub(crate) fn held(&self) -> Option<LockKind> {
self.held
}
pub(crate) fn can_read(&self) -> bool {
self.held.is_some()
}
pub(crate) fn can_write(&self) -> bool {
self.held == Some(LockKind::ReadWrite)
}
pub(crate) fn request(&mut self, flags: u32) -> LockDecision {
let kind = LockKind::of(flags);
if self.held.is_none() {
self.held = Some(kind);
return LockDecision::Grant(kind);
}
if flags & LF_SYNC != 0 {
return LockDecision::Refused;
}
self.pending = Some(coalesce(self.pending, kind));
LockDecision::Queued
}
pub(crate) fn defer(&mut self, flags: u32) -> LockDecision {
if flags & LF_SYNC != 0 {
return LockDecision::Refused;
}
self.deferred = Some(coalesce(self.deferred, LockKind::of(flags)));
LockDecision::Queued
}
pub(crate) fn take_deferred(&mut self) -> Option<LockKind> {
if self.held.is_some() {
return None;
}
self.held = self.deferred.take();
self.held
}
pub(crate) fn release(&mut self) -> Option<LockKind> {
self.held = self.pending.take();
self.held
}
}
fn coalesce(kept: Option<LockKind>, kind: LockKind) -> LockKind {
match (kept, kind) {
(Some(LockKind::ReadWrite), _) | (_, LockKind::ReadWrite) => LockKind::ReadWrite,
_ => LockKind::Read,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_free_store_grants_at_once() {
let mut locks = LockArbiter::default();
assert_eq!(
locks.request(LF_READ | LF_SYNC),
LockDecision::Grant(LockKind::Read)
);
assert!(locks.can_read());
assert!(!locks.can_write());
assert_eq!(locks.release(), None);
assert!(!locks.can_read());
assert_eq!(
locks.request(LF_READWRITE),
LockDecision::Grant(LockKind::ReadWrite)
);
assert!(locks.can_write());
}
#[test]
fn a_held_lock_refuses_a_synchronous_request_and_queues_others() {
let mut locks = LockArbiter::default();
locks.request(LF_READ);
assert_eq!(locks.request(LF_READWRITE | LF_SYNC), LockDecision::Refused);
assert_eq!(locks.request(LF_READ), LockDecision::Queued);
assert_eq!(locks.request(LF_READWRITE), LockDecision::Queued);
assert_eq!(locks.request(LF_READ), LockDecision::Queued);
assert_eq!(
locks.release(),
Some(LockKind::ReadWrite),
"coalesced, read-write since one asked"
);
assert!(locks.can_write(), "held by the pending grant");
assert_eq!(locks.release(), None);
}
#[test]
fn a_request_that_cannot_be_granted_yet_is_deferred_until_taken() {
let mut locks = LockArbiter::default();
assert_eq!(locks.defer(LF_READ | LF_SYNC), LockDecision::Refused);
assert_eq!(locks.take_deferred(), None, "nothing deferred");
assert_eq!(locks.defer(LF_READ), LockDecision::Queued);
assert_eq!(locks.defer(LF_READWRITE), LockDecision::Queued);
assert!(!locks.can_read(), "nothing held yet");
assert_eq!(locks.take_deferred(), Some(LockKind::ReadWrite));
assert!(locks.can_write());
assert_eq!(locks.take_deferred(), None, "taken once");
assert_eq!(locks.release(), None);
}
#[test]
fn kinds_and_flags() {
assert_eq!(LockKind::of(LF_READ), LockKind::Read);
assert_eq!(LockKind::of(LF_READWRITE | LF_SYNC), LockKind::ReadWrite);
assert_eq!(LockKind::Read.flags(), 0x2);
assert_eq!(LockKind::ReadWrite.flags(), 0x6);
}
}