use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use systemprompt_identifiers::{AdvisoryLockKey, InstanceId, LeaseKey};
use crate::repository::RepositoryError;
#[derive(Debug, thiserror::Error)]
pub enum CoordinationError {
#[error("{seam} is not available in this context")]
Unavailable { seam: &'static str },
#[error("lease ttl must be between one second and one day, got {ttl:?}")]
InvalidTtl { ttl: Duration },
#[error(transparent)]
Repository(#[from] RepositoryError),
}
pub type CoordinationResult<T> = Result<T, CoordinationError>;
#[async_trait]
pub trait InstanceRegistry: Send + Sync {
async fn is_alive(&self, instance: &InstanceId) -> CoordinationResult<bool>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lease {
pub key: LeaseKey,
pub owner: InstanceId,
pub acquired_at: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
}
#[async_trait]
pub trait LeaseRepository: Send + Sync {
async fn claim(&self, key: &LeaseKey, ttl: Duration) -> CoordinationResult<Option<Lease>>;
async fn release(&self, key: &LeaseKey) -> CoordinationResult<bool>;
async fn holder(&self, key: &LeaseKey) -> CoordinationResult<Option<Lease>>;
}
#[async_trait]
pub trait HeldAdvisoryLock: Send + Sync + std::fmt::Debug {
fn key(&self) -> &AdvisoryLockKey;
async fn release(self: Box<Self>);
}
pub type AdvisoryLockGuard = Box<dyn HeldAdvisoryLock>;
#[async_trait]
pub trait AdvisoryLocks: Send + Sync {
async fn try_acquire(
&self,
key: &AdvisoryLockKey,
) -> CoordinationResult<Option<AdvisoryLockGuard>>;
async fn acquire_within(
&self,
key: &AdvisoryLockKey,
wait: Duration,
) -> CoordinationResult<Option<AdvisoryLockGuard>>;
}
pub type DynInstanceRegistry = Arc<dyn InstanceRegistry>;
pub type DynLeaseRepository = Arc<dyn LeaseRepository>;
pub type DynAdvisoryLocks = Arc<dyn AdvisoryLocks>;
#[derive(Debug, Clone, Copy, Default)]
pub struct UnavailableCoordination;
#[async_trait]
impl InstanceRegistry for UnavailableCoordination {
async fn is_alive(&self, _instance: &InstanceId) -> CoordinationResult<bool> {
Err(CoordinationError::Unavailable {
seam: "instance registry",
})
}
}
#[async_trait]
impl LeaseRepository for UnavailableCoordination {
async fn claim(&self, _key: &LeaseKey, _ttl: Duration) -> CoordinationResult<Option<Lease>> {
Err(CoordinationError::Unavailable { seam: "leases" })
}
async fn release(&self, _key: &LeaseKey) -> CoordinationResult<bool> {
Err(CoordinationError::Unavailable { seam: "leases" })
}
async fn holder(&self, _key: &LeaseKey) -> CoordinationResult<Option<Lease>> {
Err(CoordinationError::Unavailable { seam: "leases" })
}
}
#[async_trait]
impl AdvisoryLocks for UnavailableCoordination {
async fn try_acquire(
&self,
_key: &AdvisoryLockKey,
) -> CoordinationResult<Option<AdvisoryLockGuard>> {
Err(CoordinationError::Unavailable {
seam: "advisory locks",
})
}
async fn acquire_within(
&self,
_key: &AdvisoryLockKey,
_wait: Duration,
) -> CoordinationResult<Option<AdvisoryLockGuard>> {
Err(CoordinationError::Unavailable {
seam: "advisory locks",
})
}
}