Skip to main content

HlcClock

Struct HlcClock 

Source
pub struct HlcClock<C = SystemClock> { /* private fields */ }
Expand description

A hybrid logical clock for one node.

The clock holds a wall time and a logical counter, and it follows the algorithm in Kulkarni et al., Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases (OPODIS 2014). HlcClock::now is the paper’s send event and HlcClock::observe is its receive event. The pair keeps two guarantees. The stamps one node mints strictly increase. A stamp that a node received is always below every stamp that the node mints after it, so the order the stamps give agrees with cause and effect.

Every method takes &self. The state sits behind a Mutex, and the critical section only compares and assigns integers, so a producer task, a consumer task, and an observability agent can share one clock.

The physical clock is a type parameter, so a test drives the whole state machine with a ManualClock and no sleep.

Implementations§

Source§

impl HlcClock<SystemClock>

Source

pub fn new(node: NodeId) -> Self

A clock for node that reads the host’s wall clock and fences at DEFAULT_MAX_OFFSET.

Source§

impl<C: PhysicalClock> HlcClock<C>

Source

pub fn with_clock(node: NodeId, physical: C) -> Self

A clock for node that reads physical and fences at DEFAULT_MAX_OFFSET.

Source

pub fn with_max_offset(node: NodeId, physical: C, max_offset: Time) -> Self

A clock for node that reads physical and fences at max_offset.

The clock starts at the current reading of physical, with the counter at zero. A negative max_offset has the same effect as zero: the clock then refuses every stamp whose wall time is above its physical clock.

Source

pub const fn node(&self) -> NodeId

The node this clock stamps for.

Source

pub fn max_offset(&self) -> Time

The maximum offset the self-fence allows.

Source

pub fn now(&self) -> Result<Hlc, HlcError>

The next stamp for a local event, such as a record this node produces.

This is the paper’s send event. The new wall time is the later of the current logical wall time and the physical clock. The counter goes up by one when the wall time did not move, and back to zero when it did. Two calls never return the same stamp, whatever the physical clock does.

§Errors

Returns HlcError::CounterOverflow when the counter reaches u32::MAX inside one microsecond. That needs more than four billion stamps in one microsecond, and the clock reports it rather than let the counter wrap.

Source

pub fn observe(&self, peer: Hlc) -> Result<Hlc, HlcError>

Merges a peer’s stamp into this clock, and returns the stamp for the receive event.

This is the paper’s receive event. The new wall time is the latest of the local logical wall time, the peer’s wall time, and the physical clock. The counter then follows the source of that wall time: it is one above the larger of the two counters when the local clock and the peer agree, one above the counter of whichever side supplied the wall time when only one side did, and zero when the physical clock overtook both.

The returned stamp is always above peer, so the record this node writes next sorts after the record it read.

The clock also fences itself. It refuses a stamp whose wall time is more than HlcClock::max_offset ahead of the local physical clock. It returns HlcError::PeerTooFarAhead, and it does not move. A hybrid logical clock adopts the highest wall time it sees, so one host with a clock set to the year 2098 would drag every node that reads its records into 2098, and then every node that reads those nodes’ records. Nothing brings the fleet back, because logical time never goes backwards. The fence keeps the damage inside the broken host.

A stamp behind the local physical clock is always safe, and the clock always accepts it. The fence is one-sided for that reason.

§Errors

Returns HlcError::PeerTooFarAhead when the self-fence refuses the stamp, and HlcError::CounterOverflow when the merged counter reaches u32::MAX inside one microsecond.

Source

pub fn observation(&self) -> ClockObservation

What this clock has learned about clock skew.

The snapshot is consistent: it reads the physical clock and the whole state together.

Trait Implementations§

Source§

impl<C: Debug> Debug for HlcClock<C>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<C = SystemClock> !Freeze for HlcClock<C>

§

impl<C> RefUnwindSafe for HlcClock<C>
where C: RefUnwindSafe,

§

impl<C> Send for HlcClock<C>
where C: Send,

§

impl<C> Sync for HlcClock<C>
where C: Sync,

§

impl<C> Unpin for HlcClock<C>
where C: Unpin,

§

impl<C> UnsafeUnpin for HlcClock<C>
where C: UnsafeUnpin,

§

impl<C> UnwindSafe for HlcClock<C>
where C: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more