mod error;
mod fence;
mod layout;
mod protocol;
mod state;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use bytes::Bytes;
use orbit_rs::fleet::FleetLaneCursor;
use orbit_rs::{Fleet, NetId64, RingLoss};
pub use error::{Error, Result};
pub use fence::{Fence, FenceToken};
pub use layout::{
DefaultLockLayout, LOCK_EVENT_RING_KIND, LOCK_EVENT_RING_SPEC, LOCK_STATE_KIND, LockLayout,
};
pub use state::{LOCK_STATE_CAPACITY, LOCK_STATE_PAYLOAD_MAX};
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub use orbit_rs::RingEventFd;
use layout::LockEventRecord;
use state::LockStateStore;
pub trait LockType {
const NAMESPACE: &'static str;
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LockKey {
namespace: Bytes,
label: Bytes,
}
impl LockKey {
pub fn new<T: LockType>(label: impl Into<Bytes>) -> Self {
Self {
namespace: Bytes::from_static(T::NAMESPACE.as_bytes()),
label: label.into(),
}
}
pub fn from_parts(namespace: impl Into<Bytes>, label: impl Into<Bytes>) -> Self {
Self {
namespace: namespace.into(),
label: label.into(),
}
}
pub fn namespace(&self) -> &[u8] {
&self.namespace
}
pub fn label(&self) -> &[u8] {
&self.label
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LockOwner(Bytes);
impl LockOwner {
pub fn new(value: impl Into<Bytes>) -> Self {
Self(value.into())
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Bytes> for LockOwner {
fn from(value: Bytes) -> Self {
Self(value)
}
}
impl From<String> for LockOwner {
fn from(value: String) -> Self {
Self(Bytes::from(value))
}
}
impl From<&str> for LockOwner {
fn from(value: &str) -> Self {
Self(Bytes::copy_from_slice(value.as_bytes()))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LockLease {
pub lock_id: NetId64,
pub key: LockKey,
pub owner: LockOwner,
pub acquired_at_ms: u64,
pub expires_at_ms: u64,
pub state_revision: u64,
}
impl LockLease {
pub fn fencing_token(&self) -> FenceToken {
FenceToken::new(self.lock_id.counter())
}
pub fn is_expired_at(&self, now_ms: u64) -> bool {
self.expires_at_ms <= now_ms
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LockAcquire {
Acquired(LockLease),
Occupied(LockLease),
}
impl LockAcquire {
pub fn acquired(self) -> Option<LockLease> {
match self {
Self::Acquired(lease) => Some(lease),
Self::Occupied(_) => None,
}
}
pub fn holder(&self) -> &LockLease {
match self {
Self::Acquired(lease) | Self::Occupied(lease) => lease,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LockTransition {
Acquired(LockLease),
Renewed(LockLease),
Released {
lock_id: NetId64,
key: LockKey,
owner: LockOwner,
state_revision: u64,
},
}
impl LockTransition {
pub fn lock_id(&self) -> NetId64 {
match self {
Self::Acquired(lease) | Self::Renewed(lease) => lease.lock_id,
Self::Released { lock_id, .. } => *lock_id,
}
}
pub fn key(&self) -> &LockKey {
match self {
Self::Acquired(lease) | Self::Renewed(lease) => &lease.key,
Self::Released { key, .. } => key,
}
}
pub fn owner(&self) -> &LockOwner {
match self {
Self::Acquired(lease) | Self::Renewed(lease) => &lease.owner,
Self::Released { owner, .. } => owner,
}
}
pub fn state_revision(&self) -> u64 {
match self {
Self::Acquired(lease) | Self::Renewed(lease) => lease.state_revision,
Self::Released { state_revision, .. } => *state_revision,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LockEvent {
pub event_id: NetId64,
pub transition: LockTransition,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LockCursor {
inner: FleetLaneCursor,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LockPoll {
pub events: Vec<LockEvent>,
pub loss: RingLoss,
pub malformed: u64,
}
impl LockPoll {
pub fn is_empty(&self) -> bool {
self.events.is_empty() && self.loss.is_empty() && self.malformed == 0
}
}
#[derive(Clone)]
pub struct Lock<L: LockLayout = DefaultLockLayout> {
fleet: Arc<Fleet>,
state: LockStateStore<L>,
cursor: Arc<Mutex<LockCursor>>,
}
impl Lock<DefaultLockLayout> {
pub fn new(fleet: Arc<Fleet>) -> Result<Self> {
Self::open(fleet, false)
}
pub fn replay_retained(fleet: Arc<Fleet>) -> Result<Self> {
Self::open(fleet, true)
}
}
impl<L: LockLayout> Lock<L> {
pub fn with_layout(fleet: Arc<Fleet>) -> Result<Self> {
Self::open(fleet, false)
}
pub fn replay_layout(fleet: Arc<Fleet>) -> Result<Self> {
Self::open(fleet, true)
}
fn open(fleet: Arc<Fleet>, replay_retained: bool) -> Result<Self> {
layout::validate::<L>()?;
let state = LockStateStore::new(&fleet);
let cursor = LockCursor {
inner: if replay_retained {
fleet.lane_cursor_from_start::<LockEventRecord<L>>()
} else {
fleet.lane_cursor_at_head::<LockEventRecord<L>>()
},
};
Ok(Self {
fleet,
state,
cursor: Arc::new(Mutex::new(cursor)),
})
}
pub fn try_acquire(
&self,
key: &LockKey,
owner: &LockOwner,
ttl: Duration,
) -> Result<LockAcquire> {
let now_ms = monotonic_ms()?;
let expires_at_ms = deadline(now_ms, ttl)?;
self.state
.acquire(key, owner, now_ms, expires_at_ms, |transition| {
self.publish(transition).map(|_| ())
})
}
pub fn renew(&self, lease: &LockLease, ttl: Duration) -> Result<Option<LockLease>> {
let now_ms = monotonic_ms()?;
let expires_at_ms = deadline(now_ms, ttl)?;
self.state
.renew_id(lease, now_ms, expires_at_ms, |transition| {
self.publish(transition).map(|_| ())
})
}
pub fn renew_owned(
&self,
key: &LockKey,
owner: &LockOwner,
ttl: Duration,
) -> Result<Option<LockLease>> {
let now_ms = monotonic_ms()?;
let expires_at_ms = deadline(now_ms, ttl)?;
self.state
.renew_owner(key, owner, now_ms, expires_at_ms, |transition| {
self.publish(transition).map(|_| ())
})
}
pub fn release(&self, lease: &LockLease) -> Result<bool> {
let now_ms = monotonic_ms()?;
self.state.release_id(lease, now_ms, |transition| {
self.publish(transition).map(|_| ())
})
}
pub fn release_owned(&self, key: &LockKey, owner: &LockOwner) -> Result<bool> {
let now_ms = monotonic_ms()?;
self.state.release_owner(key, owner, now_ms, |transition| {
self.publish(transition).map(|_| ())
})
}
pub fn force_release(&self, key: &LockKey) -> Result<bool> {
let now_ms = monotonic_ms()?;
self.state.force_release(key, now_ms, |transition| {
self.publish(transition).map(|_| ())
})
}
pub fn current(&self, key: &LockKey) -> Result<Option<LockLease>> {
self.state.current(key, monotonic_ms()?)
}
pub fn restore(&self, key: &LockKey, owner: &LockOwner) -> Result<Option<LockLease>> {
Ok(self
.current(key)?
.filter(|lease| lease.owner.as_bytes() == owner.as_bytes()))
}
pub fn poll(&self) -> LockPoll {
let raw = {
let mut cursor = self
.cursor
.lock()
.unwrap_or_else(|error| error.into_inner());
self.fleet
.poll_lanes::<LockEventRecord<L>>(&mut cursor.inner)
};
let mut events = Vec::with_capacity(raw.frames.len());
let mut malformed = 0;
for frame in raw.frames {
match protocol::decode(&frame) {
Some(event) => events.push(event),
None => malformed += 1,
}
}
events.sort_by_key(|event| event.transition.state_revision());
LockPoll {
events,
loss: raw.loss,
malformed,
}
}
pub fn reset_transport(&self) -> Result<()> {
let mut cursor = self
.cursor
.lock()
.unwrap_or_else(|error| error.into_inner());
self.state.reset()?;
self.fleet.reset_ring::<LockEventRecord<L>>()?;
cursor.inner = self.fleet.lane_cursor_at_head::<LockEventRecord<L>>();
Ok(())
}
#[cfg(unix)]
pub fn unlink(&self) -> Result<()> {
let ring = self.fleet.shm_ring::<LockEventRecord<L>>()?;
let ring_result = ring.unlink();
let state_result = self.state.unlink();
ring_result?;
state_result
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub fn event_fd(&self) -> Result<RingEventFd> {
self.fleet
.ring_event_fd::<LockEventRecord<L>>()
.map_err(Error::Io)
}
fn publish(&self, transition: &LockTransition) -> Result<LockEvent> {
let (kind, payload) = protocol::encode::<L>(transition)?;
let event_id = {
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
self.fleet.publish_notified::<LockEventRecord<L>>(
kind,
transition.state_revision(),
payload,
)?
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
{
self.fleet
.publish::<LockEventRecord<L>>(kind, transition.state_revision(), payload)
}
};
Ok(LockEvent {
event_id,
transition: transition.clone(),
})
}
}
fn deadline(now_ms: u64, ttl: Duration) -> Result<u64> {
if ttl.is_zero() {
return Err(Error::TtlZero);
}
let ttl_ms = u64::try_from(ttl.as_millis().max(1)).map_err(|_| Error::TtlOverflow)?;
now_ms.checked_add(ttl_ms).ok_or(Error::TtlOverflow)
}
#[cfg(unix)]
fn monotonic_ms() -> Result<u64> {
let mut value = std::mem::MaybeUninit::<libc::timespec>::uninit();
let result = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, value.as_mut_ptr()) };
if result != 0 {
return Err(Error::Io(std::io::Error::last_os_error()));
}
let value = unsafe { value.assume_init() };
let seconds = u64::try_from(value.tv_sec).map_err(|_| Error::TtlOverflow)?;
let nanos = u64::try_from(value.tv_nsec).map_err(|_| Error::TtlOverflow)?;
Ok(seconds
.saturating_mul(1_000)
.saturating_add(nanos / 1_000_000))
}
#[cfg(not(unix))]
fn monotonic_ms() -> Result<u64> {
use std::sync::LazyLock;
use std::time::Instant;
static START: LazyLock<Instant> = LazyLock::new(Instant::now);
Ok(START.elapsed().as_millis().min(u128::from(u64::MAX)) as u64)
}