use crate::txn::participant::ClaimKind;
use crate::unique::{UniqueOwner, UniqueRow};
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ClaimDecision {
Write(UniqueRow),
Delete,
NoOp,
Conflict(UniqueOwner),
}
pub(crate) fn decide(
current: Option<UniqueRow>,
claimant: &UniqueOwner,
kind: ClaimKind,
) -> ClaimDecision {
match (current.map(|r| r.owner()), kind) {
(None, ClaimKind::Claim) => ClaimDecision::Write(UniqueRow::new(claimant.with_deadline(0))),
(None, ClaimKind::Reserve { deadline_ms }) => {
ClaimDecision::Write(UniqueRow::new(claimant.with_deadline(deadline_ms)))
}
(None, ClaimKind::Release) => ClaimDecision::NoOp,
(Some(cur), _) if !cur.same_holder(claimant) => ClaimDecision::Conflict(cur),
(Some(_), ClaimKind::Release) => ClaimDecision::Delete,
(Some(cur), ClaimKind::Claim) => {
if cur.is_reservation() {
ClaimDecision::Write(UniqueRow::new(claimant.with_deadline(0)))
} else {
ClaimDecision::NoOp
}
}
(Some(cur), ClaimKind::Reserve { deadline_ms }) => {
if cur.is_reservation() {
ClaimDecision::Write(UniqueRow::new(claimant.with_deadline(deadline_ms)))
} else {
ClaimDecision::NoOp
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn owner(tag: u64, key: &[u8]) -> UniqueOwner {
UniqueOwner::new(tag, key, 0).unwrap()
}
#[test]
fn absent_row_claim_writes_a_permanent_owner() {
let me = owner(1, b"a");
let decision = decide(None, &me, ClaimKind::Claim);
match decision {
ClaimDecision::Write(row) => {
assert_eq!(row.owner(), me);
assert!(!row.owner().is_reservation());
assert_eq!(row.deadline(), None);
}
other => panic!("expected Write, got {other:?}"),
}
}
#[test]
fn claim_ignores_a_deadline_the_claimant_itself_happens_to_carry() {
let reservation_owner = owner(1, b"a").with_deadline(999);
assert!(
reservation_owner.is_reservation(),
"fixture must actually carry a deadline, or this test cannot tell \
'decide clears it' from 'there was nothing to clear'"
);
match decide(None, &reservation_owner, ClaimKind::Claim) {
ClaimDecision::Write(row) => {
assert_eq!(
row.deadline(),
None,
"Claim must always write a permanent row, regardless of what \
deadline the claimant itself carries"
);
}
other => panic!("expected Write, got {other:?}"),
}
}
#[test]
fn absent_row_reserve_writes_a_reservation_with_the_deadline() {
let me = owner(1, b"a");
let decision = decide(None, &me, ClaimKind::Reserve { deadline_ms: 42 });
match decision {
ClaimDecision::Write(row) => {
assert_eq!(row.deadline(), Some(42));
assert!(row.owner().same_holder(&me));
}
other => panic!("expected Write, got {other:?}"),
}
}
#[test]
fn absent_row_release_is_a_noop() {
let me = owner(1, b"a");
assert_eq!(decide(None, &me, ClaimKind::Release), ClaimDecision::NoOp);
}
#[test]
fn my_own_permanent_row_claim_is_a_noop() {
let me = owner(1, b"a");
let current = UniqueRow::new(me);
assert_eq!(
decide(Some(current), &me, ClaimKind::Claim),
ClaimDecision::NoOp
);
}
#[test]
fn my_own_reservation_claim_rewrites_it_permanent() {
let me = owner(1, b"a");
let reserved = me.with_deadline(99);
let current = UniqueRow::new(reserved);
match decide(Some(current), &me, ClaimKind::Claim) {
ClaimDecision::Write(row) => {
assert_eq!(
row.deadline(),
None,
"a claim clears the reservation deadline"
);
assert_eq!(row.owner(), me);
}
other => panic!("expected Write, got {other:?}"),
}
}
#[test]
fn my_own_row_reserve_refreshes_the_deadline() {
let me = owner(1, b"a");
let current = UniqueRow::new(me.with_deadline(10));
match decide(Some(current), &me, ClaimKind::Reserve { deadline_ms: 20 }) {
ClaimDecision::Write(row) => assert_eq!(row.deadline(), Some(20)),
other => panic!("expected Write, got {other:?}"),
}
}
#[test]
fn my_own_permanent_row_reserve_is_a_noop_not_a_demotion() {
let me = owner(1, b"a");
let current = UniqueRow::new(me); assert_eq!(
decide(Some(current), &me, ClaimKind::Reserve { deadline_ms: 999 }),
ClaimDecision::NoOp,
"a permanent claim must outrank a reservation, not be overwritten by one"
);
}
#[test]
fn my_own_row_release_deletes_it() {
let me = owner(1, b"a");
let current = UniqueRow::new(me);
assert_eq!(
decide(Some(current), &me, ClaimKind::Release),
ClaimDecision::Delete
);
}
#[test]
fn a_foreign_owner_conflicts_on_claim() {
let mine = owner(1, b"me");
let foreign = owner(1, b"someone-else");
let current = UniqueRow::new(foreign);
match decide(Some(current), &mine, ClaimKind::Claim) {
ClaimDecision::Conflict(cur) => assert_eq!(cur, foreign),
other => panic!("expected Conflict, got {other:?}"),
}
}
#[test]
fn a_foreign_owner_conflicts_on_release_rather_than_being_deleted() {
let mine = owner(1, b"me");
let foreign = owner(1, b"someone-else");
let current = UniqueRow::new(foreign);
match decide(Some(current), &mine, ClaimKind::Release) {
ClaimDecision::Conflict(cur) => assert_eq!(cur, foreign),
other => panic!("expected Conflict, not a delete: got {other:?}"),
}
}
#[test]
fn a_foreign_owner_conflicts_on_reserve() {
let mine = owner(1, b"me");
let foreign = owner(1, b"someone-else");
let current = UniqueRow::new(foreign);
match decide(Some(current), &mine, ClaimKind::Reserve { deadline_ms: 5 }) {
ClaimDecision::Conflict(cur) => assert_eq!(cur, foreign),
other => panic!("expected Conflict, got {other:?}"),
}
}
#[test]
fn a_different_collection_tag_with_the_same_key_bytes_still_conflicts() {
let a = owner(1, b"same-key");
let b = owner(2, b"same-key");
let current = UniqueRow::new(a);
match decide(Some(current), &b, ClaimKind::Claim) {
ClaimDecision::Conflict(cur) => assert_eq!(cur, a),
other => panic!("expected Conflict, got {other:?}"),
}
}
}