Skip to main content

MultiController

Struct MultiController 

Source
pub struct MultiController { /* private fields */ }
Expand description

Several sources, one clock loop.

This is the structural fix for multi-source selection, and it is worth stating why the obvious alternative does not work. The daemon used to give every source its own SyncController — its own register and its own frequency, drain and budget — and let only the selected one reach the clock. That leaves every unselected source having produced a plan that never happened, and seven different ways of cleaning up after that plan were measured on the three-server rig. Every one was worse than leaving the wrong books in place, which is the signature of a wrong model rather than a wrong patch.

The model was wrong. A frequency correction, a drain and its budget are properties of THE CLOCK, of which there is one; only the sample history is a property of a source. So the registers are per-source and everything else is shared, and an unselected source never produces a plan in the first place — there is nothing to revert, confirm or adopt, because nothing was ever booked. The entire class of bug is gone rather than patched.

With one source this is arithmetically identical to what shipped before it, which is checked by running the corpus against the previous binary.

Implementations§

Source§

impl MultiController

Source

pub fn new(config: DisciplineConfig, sources: usize) -> Self

Source

pub fn revert_last_plan(&mut self) -> bool

Undo the bookkeeping of the last plan, because the driver refused it.

The loop’s arithmetic has to describe what the clock actually did. A plan books its own effects the moment it is produced: the frequency change tilts every stored sample, a step shifts them, and the drain budget starts counting down. The caller then hands the command to the platform — which can refuse it. clock_adjtime returns EPERM the moment CAP_SYS_TIME goes away, and a seccomp policy or a container with a read-only clock refuses it too.

Without this, a refusal is silent and cumulative. The register carries corrections that never happened, the regression reads that history as truth, and the daemon reports itself synchronised while the clock free runs — the worst failure a time daemon has, because nothing looks wrong.

Returns whether there was a plan to revert.

Source

pub fn confirm_last_plan(&mut self)

Confirm the last plan reached the clock, so it can no longer be undone.

Source

pub fn freq_ppm(&self) -> f64

Source

pub fn drain_ppm(&self) -> f64

Source

pub fn applied_ppm(&self) -> f64

Source

pub fn poll_log2(&self) -> i8

Source

pub fn poll_interval_s(&self) -> f64

The loop’s current poll interval, in seconds.

A source that is not steering still has to be scheduled, and the poll interval belongs to the loop rather than to any one source.

Source

pub fn samples(&self) -> usize

Source

pub fn samples_from(&self, index: usize) -> usize

Source

pub fn sources(&self) -> usize

Source

pub fn preload_frequency(&mut self, freq_ppm: f64)

Seed the frequency estimate from persisted drift, so a restart does not re-learn what was already known.

Source

pub fn retry_interval_s(&self) -> f64

The interval to use when an exchange is lost — no plan is produced, but the caller still needs to know when to try again.

Source

pub fn drain_completes_at(&self) -> Option<f64>

When the running drain will have spent its budget, if one is running.

Source

pub fn poll_drain(&mut self, mono_now_s: f64) -> Option<ClockCommand>

End the drain if its budget is spent, returning the command that leaves the clock running at the frequency term alone.

Callers must invoke this as they advance time — the daemon by waking for it, the simulator at each substep — or the drain runs on past its budget and overshoots, which is the behaviour this exists to end.

Source

pub fn observe( &mut self, index: usize, mono_now_s: f64, sample: Sample, ) -> Estimate

Record one exchange from one source. Measurement only — this never touches the clock, so calling it for a source that is not selected costs nothing and leaves nothing to undo.

Source

pub fn estimate(&mut self, index: usize, mono_now_s: f64) -> Estimate

This source’s current view of the clock.

Regression once it has enough spread; before that the lowest-delay single sample, which is the least contaminated reading available.

Source

pub fn steer( &mut self, est: Estimate, mono_now_s: f64, leap_pending: bool, ) -> ControllerStep

Steer the clock from one source’s estimate.

Call this for the SELECTED source only, passing the Estimate that MultiController::observe just returned. Its effects are booked against every register, because the correction lands on the one clock they all measure.

Taking the estimate rather than re-deriving it is not tidiness: the regression is the expensive part of a step, and computing it in observe and again here doubled the cost of the whole discipline — measured 13,614 to 26,169 Ir per step.

Auto Trait Implementations§

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, 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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

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.