use crate::adapter::net::behavior::fold::{
ApplyOutcome, EnvelopeMeta, Fold, FoldError, FoldKind, NodeId, ReservationAnnouncement,
ReservationFold, ReservationQuery, ReservationState, SignedAnnouncement, WireError,
};
use crate::adapter::net::identity::EntityKeypair;
use super::types::TaskId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaskLeaseOutcome {
Acquired,
Contended,
}
#[derive(Debug)]
pub enum TaskLeaseError {
Sign(WireError),
Apply(FoldError),
}
impl std::fmt::Display for TaskLeaseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TaskLeaseError::Sign(e) => write!(f, "sign task-lease announcement: {e}"),
TaskLeaseError::Apply(e) => write!(f, "apply task-lease announcement: {e}"),
}
}
}
impl std::error::Error for TaskLeaseError {}
impl From<WireError> for TaskLeaseError {
fn from(e: WireError) -> Self {
TaskLeaseError::Sign(e)
}
}
impl From<FoldError> for TaskLeaseError {
fn from(e: FoldError) -> Self {
TaskLeaseError::Apply(e)
}
}
pub struct TaskLease<'a> {
reservations: &'a Fold<ReservationFold>,
keypair: &'a EntityKeypair,
node_id: NodeId,
generation: u64,
}
impl<'a> TaskLease<'a> {
pub fn new(
reservations: &'a Fold<ReservationFold>,
keypair: &'a EntityKeypair,
node_id: NodeId,
) -> Self {
Self {
reservations,
keypair,
node_id,
generation: 1,
}
}
pub fn node_id(&self) -> NodeId {
self.node_id
}
pub fn acquire(
&mut self,
task: TaskId,
until_unix_us: u64,
) -> Result<TaskLeaseOutcome, TaskLeaseError> {
let outcome = self.apply(
task,
ReservationState::Reserved {
holder: self.node_id,
until_unix_us,
},
)?;
Ok(match outcome {
ApplyOutcome::Inserted | ApplyOutcome::Replaced => TaskLeaseOutcome::Acquired,
ApplyOutcome::Rejected => TaskLeaseOutcome::Contended,
})
}
pub fn release(&mut self, task: TaskId) -> Result<bool, TaskLeaseError> {
let outcome = self.apply(task, ReservationState::Free)?;
Ok(matches!(
outcome,
ApplyOutcome::Inserted | ApplyOutcome::Replaced
))
}
pub fn current_holder(&self, task: TaskId) -> Option<NodeId> {
self.reservations
.query(ReservationQuery::State(task))
.first()
.and_then(|(_, state)| state.holder())
}
fn apply(
&mut self,
task: TaskId,
state: ReservationState,
) -> Result<ApplyOutcome, TaskLeaseError> {
let gen = self.generation;
self.generation += 1;
let ann = SignedAnnouncement::sign(
self.keypair,
ReservationFold::KIND_ID,
0,
self.node_id,
gen,
EnvelopeMeta::default(),
ReservationAnnouncement {
resource_id: task,
state,
},
)?;
Ok(self.reservations.apply(ann)?)
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use crate::adapter::net::current_timestamp_micros;
use crate::adapter::net::identity::EntityKeypair;
fn new_reservations() -> Fold<ReservationFold> {
Fold::with_sweep_interval(Duration::ZERO)
}
fn fresh() -> u64 {
current_timestamp_micros() + 60_000_000
}
#[test]
fn acquire_then_renew_then_release() {
let fold = new_reservations();
let kp = EntityKeypair::generate();
let node = kp.entity_id().node_id();
let mut lease = TaskLease::new(&fold, &kp, node);
assert_eq!(
lease.acquire(0x1A, fresh()).unwrap(),
TaskLeaseOutcome::Acquired
);
assert_eq!(lease.current_holder(0x1A), Some(node));
assert_eq!(
lease.acquire(0x1A, fresh()).unwrap(),
TaskLeaseOutcome::Acquired
);
assert!(lease.release(0x1A).unwrap());
assert_eq!(lease.current_holder(0x1A), None);
}
#[test]
fn second_owner_is_contended_while_lease_is_live() {
let fold = new_reservations();
let a = EntityKeypair::generate();
let b = EntityKeypair::generate();
let (na, nb) = (a.entity_id().node_id(), b.entity_id().node_id());
let mut la = TaskLease::new(&fold, &a, na);
let mut lb = TaskLease::new(&fold, &b, nb);
assert_eq!(la.acquire(5, fresh()).unwrap(), TaskLeaseOutcome::Acquired);
assert_eq!(lb.acquire(5, fresh()).unwrap(), TaskLeaseOutcome::Contended);
assert_eq!(la.current_holder(5), Some(na));
assert!(!lb.release(5).unwrap());
}
#[test]
fn expired_lease_fails_over_to_a_new_owner() {
let fold = new_reservations();
let a = EntityKeypair::generate();
let b = EntityKeypair::generate();
let (na, nb) = (a.entity_id().node_id(), b.entity_id().node_id());
let mut la = TaskLease::new(&fold, &a, na);
let mut lb = TaskLease::new(&fold, &b, nb);
let expired = current_timestamp_micros().saturating_sub(60_000_000);
assert_eq!(la.acquire(9, expired).unwrap(), TaskLeaseOutcome::Acquired);
assert_eq!(lb.acquire(9, fresh()).unwrap(), TaskLeaseOutcome::Acquired);
assert_eq!(lb.current_holder(9), Some(nb));
}
}