Skip to main content

Lease

Struct Lease 

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

The lease this process holds. Dropping it removes the file (when the file is still this process’s), so a failed start never leaves a stale lease behind.

Implementations§

Source§

impl Lease

Source

pub fn path(&self) -> &Path

The file.

Source

pub fn root(&self) -> &Path

The repository root the lease is for.

Source

pub fn started_at(&self) -> &str

When this process took the lease, RFC 3339: the identity of this server’s generation, the same value the lease file carries as started_at.

Source

pub fn keep_alive(&self)

Keep the lease young while the layer loads. A peer that finds a lease without a URL waits for it only as long as the file is younger than BIND_GRACE; a cold start that takes longer than that — a large layer, a machine under load — would otherwise be taken for an abandoned one and taken over while its owner is still binding. So until Lease::publish, a thread rewrites the same document every third of the grace, under the lock publish also takes, so that a touch can never land after the URL. It writes only while the file is still this process’s: a lease that was taken over anyway is somebody else’s to write.

use majordomus_cli::lease::{elect, held, LeaseFile, Role};
use majordomus_cli::Repository;
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join(".ai/repo")).unwrap();
std::fs::write(dir.path().join(".ai/manifest.yaml"),
    "schema: ai-repository/v1\nrepo:\n  path: repo\nlocal:\n  path: local\n  tracked: false\n  implicit_context: false\nsections:\n  policy: repo/policy.yaml\n").unwrap();
let repo = Repository::discover(dir.path()).unwrap();
let Role::Server(lease) = elect(&repo).unwrap() else { panic!("nobody else serves a fresh directory") };
lease.keep_alive();                         // returns at once; the touching runs beside the load
assert!(held().is_none(), "nothing is published yet");
lease.publish("http://127.0.0.1:1").unwrap();  // stops the touching, under the same lock
assert_eq!(held().unwrap().url.as_deref(), Some("http://127.0.0.1:1"));
assert!(matches!(LeaseFile::read(lease.path()), LeaseFile::Document(d) if d.url.is_some()));
lease.release();
assert_eq!(LeaseFile::read(&dir.path().join(".ai/local/state/mcp/server.json")), LeaseFile::Absent);
Source

pub fn publish(&self, url: &str) -> Result<(), Error>

Record the URL the server is listening on, atomically, so that a peer reading the file sees either no URL or the whole one. Refused when the file is no longer this process’s: a lease taken over while its owner was binding belongs to whoever took it, and writing over it would leave two servers claiming one address.

The document is written to a temporary file named for this lease’s token and synced before anything else happens; then, under the lock every change to the lease takes (take_over_with says why there is one), the file is checked to be still this process’s and the temporary file is renamed over it. No take-over can land between the check and the rename, and no other writer shares the temporary file.

Source

pub fn release(self)

Remove the lease: the server has stopped. Only while the file is still this process’s, checked under the lock a take-over takes, so that a lease taken over since is never removed by the process it was taken from.

Trait Implementations§

Source§

impl Debug for Lease

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Drop for Lease

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

§

impl Freeze for Lease

§

impl RefUnwindSafe for Lease

§

impl Send for Lease

§

impl Sync for Lease

§

impl Unpin for Lease

§

impl UnsafeUnpin for Lease

§

impl UnwindSafe for Lease

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
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

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

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

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.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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.
§

impl<T> WithSubscriber for T

§

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
§

fn with_current_subscriber(self) -> WithDispatch<Self>

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