Skip to main content

PluginBudget

Struct PluginBudget 

Source
pub struct PluginBudget {
    pub fuel: u64,
    pub epoch_deadline: u64,
}
Expand description

Per-call resource budget. Both limits are hard: whichever is hit first traps the call cleanly.

Fields§

§fuel: u64

Fuel (≈ units of work) a single lifecycle call may consume before it traps with TrapKind::Fuel.

§epoch_deadline: u64

Milliseconds of WALL CLOCK a single call may run before it traps with TrapKind::Epoch.

OA.0b made that literal. It used to be enforced by counting deadline- callback firings, which equals elapsed milliseconds only while the guest executes guest code continuously — one epoch checkpoint crossed per [EPOCH_TICK_INTERVAL]. A guest that calls host imports in a loop is suspended for most of its wall clock and crosses far fewer, so the budget stretched by the ratio of host time to guest time: an org agenda scan ran 6.8 s against this set to 1_000 and never tripped.

The new accounting is stricter, and one consequence is worth knowing. Time the OS spends descheduling the thread now counts against the call, where firing-count quietly forgave it. That is the correct measure for the thing this guards — the editor was stalled either way — but if a sync grammar contribution ever false-trips under load, the fix is to raise Self::grammar’s deadline, NOT to go back to counting firings. Fuel is the primary bound on that path by design.

Implementations§

Source§

impl PluginBudget

Source

pub fn grammar() -> Self

The Reflex-class budget for the synchronous grammar trampoline (PH7.7c; plugin-host.md §7 + audit F1). Grammar apply / parse_args run on the keystroke path (the PH7.7 fork), so — unlike the generous lifecycle/async default (~1s epoch) — a plugin contribution must not stall the keystroke.

Fuel is the primary Reflex bound. A grammar guest runs on the sync linker with no async host import, so it cannot block (no I/O to await) — it can only compute or spin, and both are fuel-bounded. 10M fuel is ~one display frame of compute (Cranelift-compiled), ample for a real motion’s arithmetic but a hard cap on a runaway loop; that is what keeps a plugin motion off the keystroke’s critical path.

Epoch is a jitter-proof wall-clock backstop, not the tripwire. The ticker granularity is 1ms ([EPOCH_TICK_INTERVAL]); a 2-tick deadline false-positives when the OS deschedules the dispatch thread mid-call (observed under a criterion warmup’s millions of iterations). So the epoch is set generously — it must never trip on scheduling jitter, and only catch the pathological case fuel somehow misses (near-impossible for a sync compute guest). A trap of either kind is caught by the trampoline → the contribution is a no-op with a warn (CommandError::Plugin), never a hang. Armed before every guest call — distinct from the lifecycle/producer budget by design (audit F1).

50ms was not jitter-proof, so this is 250. A trivial org :org-clock-in — read a line, rewrite it, emit an event — overran 50ms and trapped whenever the machine was saturated (reproduced with one busy loop per core). That is precisely the false-trip this deadline’s own prose said must not happen, and the fix it prescribed: raise the deadline, never go back to counting firings.

250ms costs nothing real. Fuel remains the actual bound at ~one frame of compute, so a runaway still dies on fuel in milliseconds; the epoch only has to exceed the worst descheduling window, and 5× the observed failure is that with room. The number is also no longer a cliff: since the strike count in [Quarantine::trip], one overrun costs that call rather than the plugin.

Source

pub fn event() -> Self

The budget for a plugin event handler (on-event, PH7.8c; §7 “major- mode event handler”). Unlike grammar’s Reflex budget, event delivery is off the keystroke path (async, on the plugin’s own actor task), and a handler runs on the async linker — so it may legitimately await a capability-gated host-services call. A tight sub-frame epoch would false-trip such a suspend, so the epoch is a generous backstop (~1s, the lifecycle default) and fuel is the primary bound: 100M ≈ ~10 frames of compute, ample for a real hook (recolour a gutter, index a symbol) yet a hard cap on a runaway loop. A trap is caught per-delivery by the EventActor → the delivery is skipped with a warn, the plugin stays subscribed, other subscribers are untouched (§8). The marshalling+dispatch overhead itself is the CI-gated < 250µs p99 row (PH7.8d), distinct from this runaway guard.

5s, not 1s, and the reason is what the epoch measures. Since OA.0b the deadline counts wall time including the time the guest sits BLOCKED in a host import — correct, because a guest looping over imports was otherwise unbounded (an agenda scan ran 6.8 s against a 1 s deadline without tripping). The cost is that a handler is now charged for host work it cannot make faster: org’s roam scan opens with one walk of the corpus, and under a saturated machine that single import took 1106 ms — so a healthy plugin trapped, and since a trap kills the instance ([Quarantine::trip]) the whole index died with it.

Raising it does not weaken what the guard is for. Fuel still caps guest COMPUTE at ~10 frames, so a spinning handler dies in milliseconds regardless; the epoch’s remaining job is bounding a guest that blocks in imports forever, and 5 s bounds that just as surely as 1 s. What it buys is that legitimate work — which a guest already self-limits to 250 ms per delivery — is 20× inside the budget instead of within noise of it.

Source

pub fn decoration() -> Self

The budget for a plugin decoration producer (gutter-decorations, PH7.9). Like the event budget, decoration production is off the render path (async, on the plugin’s actor task, triggered by an edit / scroll / diagnostic change) and the producer runs on the async linker (it may await a host-services call — a git-gutter source reading the repo). So the epoch is a generous ~1 s backstop and fuel is the primary bound (100M ≈ ~10 frames of compute — ample for a real diff/annotate pass, a hard cap on a runaway). A trap is caught per-call by the DecorationActor → the trigger yields no decorations and the cached snapshot keeps its prior value (§8, no flicker). The §7 < 50 µs p99 “segment update” gate is on the marshalling

  • dispatch overhead, distinct from this runaway guard.
Source

pub fn context() -> Self

TC.2: budget for a context-scopes produce call. Deliberately the same shape as Self::decoration and for the same reason — both are async producers the host drives off the render path, so the guard is against a runaway, not against latency. Context is the more expensive of the two (a whole-buffer tree-sitter query rather than a per-line walk), and the budget is generous enough that a legitimate query on a large file finishes well inside it; context.max-file-lines is what bounds the intended work, this is what bounds the unintended.

Trait Implementations§

Source§

impl Clone for PluginBudget

Source§

fn clone(&self) -> PluginBudget

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for PluginBudget

Source§

impl Debug for PluginBudget

Source§

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

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

impl Default for PluginBudget

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
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.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> Pointee for T

§

type Pointer = u32

§

fn debug( pointer: <T as Pointee>::Pointer, f: &mut Formatter<'_>, ) -> Result<(), Error>

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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