Skip to main content

WorkspaceManager

Struct WorkspaceManager 

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

Owns every loaded workspace plus the admission-accounting state.

Construction spawns the retention reaper task (§G.3). The handle is stored so Drop can abort it cleanly — on daemon shutdown the reaper is aborted, then the admission state drops, dropping every retained Arc<CodeGraph> in one pass. No accounting leak, no dangling Arc.

Implementations§

Source§

impl WorkspaceManager

Source

pub fn new(config: Arc<DaemonConfig>) -> Arc<Self>

Construct a fresh manager and spawn the retention reaper.

The reaper is spawned on the current Tokio runtime. Callers must therefore construct the manager from a Tokio context (#[tokio::main], an async block driven by Runtime::block_on, etc.). Tests that don’t need the reaper can use Self::new_without_reaper.

Source

pub fn set_hook(&self, hook: SharedHook)

Install a post-publish hook. Task 9’s daemon binary calls this once at startup after constructing the shared QueryDb; unit tests call it to install a recording hook. The old hook is dropped immediately; no retention semantics apply.

Source

pub fn memory_limit_bytes(&self) -> u64

Memory budget in bytes (derived from config.memory_limit_mb).

Source

pub fn lookup(&self, key: &WorkspaceKey) -> Option<Arc<LoadedWorkspace>>

Look up a loaded workspace by key without acquiring rebuild_lane or admission.

Returns Some(Arc<LoadedWorkspace>) if a workspace is currently registered under key, or None otherwise. The workspaces read guard is acquired and released inside the call — callers never observe it nested with any other lock.

Added for the Task 7 crate::rebuild::RebuildDispatcher which needs a cheap handle on Arc<LoadedWorkspace> as a precondition before entering the canonical §J.4 ordered sequence (rebuild_laneadmission). This is not part of the ordered sequence itself — the §J.4 contract only constrains paths that hold more than one lock simultaneously. Here, the workspaces guard is dropped before the caller takes rebuild_lane, so there is no nesting. Shared lookup: returns the Arc<LoadedWorkspace> keyed by key if present. Used by RebuildDispatcher::handle_changes (inside the crate) and by external test harnesses (Task 7 Phase 7b1 rebuild_runner_gate.rs) that need to inspect workspace-level atomics (rebuild_in_flight, rebuild_cancelled) or the rebuild_lane mutex directly.

This is NOT a JSON-RPC surface — the IPC layer should use status() for point-in-time workspace state. Direct lookup access bypasses the LRU touch that status() performs.

Source

pub fn reap_once(&self)

Retention reaper: a single pass over retained_old.

Removes entries whose Arc::strong_count has dropped to 1 — meaning the admission map is the last holder. Emits a one-shot WARN log line when an entry exceeds rebuild_drain_timeout_ms without dropping.

This is the only code path that removes tokens from retained_old. Any other code that mutates the retention map is a violation of §G.3.

Source

pub fn reserve_rebuild( self: &Arc<Self>, for_key: &WorkspaceKey, working_set_estimate: u64, ) -> Result<RebuildReservation, DaemonError>

Amendment 2 §G.1 two-phase reservation protocol.

Phase 1 (workspaces read + admission read):
    project_total + estimate ≤ limit?  → commit
    otherwise                          → pick LRU non-pinned
                                         victims (`for_key` is
                                         exempt — a workspace
                                         cannot evict itself)
Phase 2 (no locks held):
    for each victim: execute_eviction()
Phase 3 (admission alone):
    re-check projected vs limit     → authoritative commit
    reserved_bytes += estimate     → return RebuildReservation

Lock order is workspaces → admission in Phase 1, nothing in Phase 2, admission alone in Phase 3. No nesting of rebuild_lane — Task 7 adds that layer outside this function.

Returns a RebuildReservation RAII guard on success. On Err, the admission state is exactly pre-call — either no eviction happened (headroom already available) or the eviction cleared retained entries but could not fit.

Source

pub fn get_or_load( self: &Arc<Self>, key: &WorkspaceKey, builder: &dyn WorkspaceBuilder, working_set_estimate: u64, ) -> Result<Arc<CodeGraph>, DaemonError>

Load the workspace’s graph, building it via builder if not already present.

Lifecycle gate:

  1. Cache-hit fast path — if the workspace is present AND in WorkspaceState::Loaded, touch + return.
  2. CAS Unloaded/Evicted/FailedLoading. Exactly one caller wins. If another caller already holds the gate (Loading/Rebuilding), return an error — Phase 6c / Task 7 will introduce a wait-for-done notify channel.
  3. The winner arms a [LoadingGuard] RAII wrapper that transitions the workspace into WorkspaceState::Failed on any non-success exit (Err, early return, or panic). This covers the Codex iter-1 MAJOR that a panic from builder.build() would leave the workspace stuck in Loading.
  4. Reserve admission headroom (§G.1 three-phase).
  5. Build the graph via the injected builder.
  6. Re-check rebuild_cancelled + workspace map membership before publishing. If eviction ran during the build, the reservation refunds via RAII and no graph is published.
  7. Publish via publish_and_retain. Disarm the LoadingGuard
    • record success + touch.
  8. Release workspaces_guard, THEN dispatch the post-publish SqrydHook. The hook fires outside every outer manager lock so a hook impl is free to call back into unload / get_or_load / set_hook / status without deadlocking against the loader that fired it.

Codex Task 6 Phase 6b iter-1 MAJOR (×2): the pre-fix version clobbered a concurrent eviction’s rebuild_cancelled signal and could publish into a workspace already removed from the map. The CAS + post-build re-check + LoadingGuard together close both holes.

Codex Task 6 Phase 6c iter-2 MAJOR: the pre-fix version dispatched the hook from inside publish_and_retain while the caller still held workspaces.read(), giving a hook impl that needed workspaces.write() (e.g. via unload) a guaranteed re-entrancy deadlock. Splitting publish and hook dispatch into Steps 7 and 8 closes that hole.

§Errors
Source

pub fn evict_lru(&self) -> Option<WorkspaceKey>

Evict the least-recently-accessed non-pinned workspace, if any. Returns the evicted key on success, None if there are no eligible candidates.

Source

pub fn unload(&self, key: &WorkspaceKey) -> bool

Explicitly unload a workspace. Equivalent to Self::execute_eviction but callable by the IPC daemon/unload method and sqry daemon unload <path> CLI. Returns true if the workspace was present, false if it was already absent.

Source

pub fn find_key_and_workspace_by_path( &self, path: &Path, ) -> Option<(WorkspaceKey, Arc<LoadedWorkspace>)>

Find a loaded workspace by its directory path.

Linear scan over all registered workspaces comparing each workspace’s index_root against path. Callers (e.g. daemon/rebuild) supply a canonicalised path but not the full WorkspaceKey. O(n) in the number of loaded workspaces; in practice n is small.

Returns None if no workspace with a matching root is found.

Source

pub fn status(&self) -> DaemonStatus

Snapshot of daemon-wide status. Point-in-time, non-transactional.

Source

pub fn classify_for_serve( &self, key: &WorkspaceKey, now: SystemTime, ) -> Result<ServeVerdict, DaemonError>

Classify a workspace’s readiness to serve a query.

Task 7 Phase 7c. Used by the Task 8 IPC router on every query dispatch. Pure-read: no mutations, no .await (sync).

§Returns
Workspace stateMap presentResult
Loaded or RebuildingyesOk(ServeVerdict::Fresh { graph, state })
Failed, age < cap (or cap == 0)yesOk(ServeVerdict::Stale { graph, age_hours, last_good_at, last_error })
Failed, age >= capyesErr(WorkspaceStaleExpired { age_hours, cap_hours, last_good_at, last_error }) (→ JSON-RPC -32002)
Failed, no prior goodyesErr(WorkspaceBuildFailed { reason }) (→ -32001)
Unloaded or LoadingyesOk(ServeVerdict::NotReady { state })
Evictedyes (transient window)Err(WorkspaceEvicted) (→ -32004)
anynoErr(WorkspaceEvicted) (→ -32004)
§Lock order

Task 7 Phase 7c feat iter-1 Codex BLOCKER fix: takes workspaces.read() across the FULL snapshot — state, graph, last_good, and last_error_text are all captured inside the read critical section. Dropping the read lock before reading the graph would allow execute_eviction (which needs workspaces.write() for the full graph-swap + state-store + map-remove sequence) to interleave, surfacing the empty post-eviction placeholder graph as a Fresh verdict.

Does not acquire admission or rebuild_lane; only workspaces + per-workspace field locks. §J.4 order preserved.

§Errors

Returns the variants listed in the table above.

Source

pub fn publish_and_retain( self: &Arc<Self>, reservation: RebuildReservation, workspace: &LoadedWorkspace, new_graph: CodeGraph, ) -> (OldGraphToken, Arc<CodeGraph>)

Consume a RebuildReservation plus a freshly-built CodeGraph and atomically publish it to the workspace.

Implements Amendment 2 §G.2:

  • Captures the prior Arc<CodeGraph> and memory_bytes into a [RollbackGuard] before any swap — so a panic at any point before the admission update reverts cleanly.
  • Swaps the ArcSwap<CodeGraph> to the new graph.
  • Swaps the per-workspace memory_bytes to the new size.
  • Under the admission mutex: moves bytes_delta from reserved_bytes into loaded_bytes, inserts a RetainedEntry holding the old Arc until the retention reaper frees it.
  • Disarms the [RollbackGuard] on success.

Sync fn. There is no .await between the first swap and the admission insert — tokio task cancellation can only interrupt at .await points, so this sequence is atomic with respect to cancellation per §G.2.

Returns the minted OldGraphToken for tracing / integration tests, together with an Arc<CodeGraph> handle to the freshly published graph. Per Codex Task 6 Phase 6c iter-2 MAJOR the post-publish SqrydHook dispatch is NOT performed here — firing on_publish under the workspaces.read() guard get_or_load holds across this call would nest self.hook.read() inside workspaces, giving hook impls a re-entrancy deadlock hole if they call back into manager methods needing workspaces.write(). The caller is responsible for dispatching the hook after dropping every outer workspaces-lock holder.

Trait Implementations§

Source§

impl Debug for WorkspaceManager

Source§

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

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

impl Drop for WorkspaceManager

Source§

fn drop(&mut self)

Executes the destructor for this 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> 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> 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
Source§

impl<D> OwoColorize for D

Source§

fn fg<C>(&self) -> FgColorDisplay<'_, C, Self>
where C: Color,

Set the foreground color generically Read more
Source§

fn bg<C>(&self) -> BgColorDisplay<'_, C, Self>
where C: Color,

Set the background color generically. Read more
Source§

fn black(&self) -> FgColorDisplay<'_, Black, Self>

Change the foreground color to black
Source§

fn on_black(&self) -> BgColorDisplay<'_, Black, Self>

Change the background color to black
Source§

fn red(&self) -> FgColorDisplay<'_, Red, Self>

Change the foreground color to red
Source§

fn on_red(&self) -> BgColorDisplay<'_, Red, Self>

Change the background color to red
Source§

fn green(&self) -> FgColorDisplay<'_, Green, Self>

Change the foreground color to green
Source§

fn on_green(&self) -> BgColorDisplay<'_, Green, Self>

Change the background color to green
Source§

fn yellow(&self) -> FgColorDisplay<'_, Yellow, Self>

Change the foreground color to yellow
Source§

fn on_yellow(&self) -> BgColorDisplay<'_, Yellow, Self>

Change the background color to yellow
Source§

fn blue(&self) -> FgColorDisplay<'_, Blue, Self>

Change the foreground color to blue
Source§

fn on_blue(&self) -> BgColorDisplay<'_, Blue, Self>

Change the background color to blue
Source§

fn magenta(&self) -> FgColorDisplay<'_, Magenta, Self>

Change the foreground color to magenta
Source§

fn on_magenta(&self) -> BgColorDisplay<'_, Magenta, Self>

Change the background color to magenta
Source§

fn purple(&self) -> FgColorDisplay<'_, Magenta, Self>

Change the foreground color to purple
Source§

fn on_purple(&self) -> BgColorDisplay<'_, Magenta, Self>

Change the background color to purple
Source§

fn cyan(&self) -> FgColorDisplay<'_, Cyan, Self>

Change the foreground color to cyan
Source§

fn on_cyan(&self) -> BgColorDisplay<'_, Cyan, Self>

Change the background color to cyan
Source§

fn white(&self) -> FgColorDisplay<'_, White, Self>

Change the foreground color to white
Source§

fn on_white(&self) -> BgColorDisplay<'_, White, Self>

Change the background color to white
Source§

fn default_color(&self) -> FgColorDisplay<'_, Default, Self>

Change the foreground color to the terminal default
Source§

fn on_default_color(&self) -> BgColorDisplay<'_, Default, Self>

Change the background color to the terminal default
Source§

fn bright_black(&self) -> FgColorDisplay<'_, BrightBlack, Self>

Change the foreground color to bright black
Source§

fn on_bright_black(&self) -> BgColorDisplay<'_, BrightBlack, Self>

Change the background color to bright black
Source§

fn bright_red(&self) -> FgColorDisplay<'_, BrightRed, Self>

Change the foreground color to bright red
Source§

fn on_bright_red(&self) -> BgColorDisplay<'_, BrightRed, Self>

Change the background color to bright red
Source§

fn bright_green(&self) -> FgColorDisplay<'_, BrightGreen, Self>

Change the foreground color to bright green
Source§

fn on_bright_green(&self) -> BgColorDisplay<'_, BrightGreen, Self>

Change the background color to bright green
Source§

fn bright_yellow(&self) -> FgColorDisplay<'_, BrightYellow, Self>

Change the foreground color to bright yellow
Source§

fn on_bright_yellow(&self) -> BgColorDisplay<'_, BrightYellow, Self>

Change the background color to bright yellow
Source§

fn bright_blue(&self) -> FgColorDisplay<'_, BrightBlue, Self>

Change the foreground color to bright blue
Source§

fn on_bright_blue(&self) -> BgColorDisplay<'_, BrightBlue, Self>

Change the background color to bright blue
Source§

fn bright_magenta(&self) -> FgColorDisplay<'_, BrightMagenta, Self>

Change the foreground color to bright magenta
Source§

fn on_bright_magenta(&self) -> BgColorDisplay<'_, BrightMagenta, Self>

Change the background color to bright magenta
Source§

fn bright_purple(&self) -> FgColorDisplay<'_, BrightMagenta, Self>

Change the foreground color to bright purple
Source§

fn on_bright_purple(&self) -> BgColorDisplay<'_, BrightMagenta, Self>

Change the background color to bright purple
Source§

fn bright_cyan(&self) -> FgColorDisplay<'_, BrightCyan, Self>

Change the foreground color to bright cyan
Source§

fn on_bright_cyan(&self) -> BgColorDisplay<'_, BrightCyan, Self>

Change the background color to bright cyan
Source§

fn bright_white(&self) -> FgColorDisplay<'_, BrightWhite, Self>

Change the foreground color to bright white
Source§

fn on_bright_white(&self) -> BgColorDisplay<'_, BrightWhite, Self>

Change the background color to bright white
Source§

fn bold(&self) -> BoldDisplay<'_, Self>

Make the text bold
Source§

fn dimmed(&self) -> DimDisplay<'_, Self>

Make the text dim
Source§

fn italic(&self) -> ItalicDisplay<'_, Self>

Make the text italicized
Source§

fn underline(&self) -> UnderlineDisplay<'_, Self>

Make the text underlined
Make the text blink
Make the text blink (but fast!)
Source§

fn reversed(&self) -> ReversedDisplay<'_, Self>

Swap the foreground and background colors
Source§

fn hidden(&self) -> HiddenDisplay<'_, Self>

Hide the text
Source§

fn strikethrough(&self) -> StrikeThroughDisplay<'_, Self>

Cross out the text
Source§

fn color<Color>(&self, color: Color) -> FgDynColorDisplay<'_, Color, Self>
where Color: DynColor,

Set the foreground color at runtime. Only use if you do not know which color will be used at compile-time. If the color is constant, use either OwoColorize::fg or a color-specific method, such as OwoColorize::green, Read more
Source§

fn on_color<Color>(&self, color: Color) -> BgDynColorDisplay<'_, Color, Self>
where Color: DynColor,

Set the background color at runtime. Only use if you do not know what color to use at compile-time. If the color is constant, use either OwoColorize::bg or a color-specific method, such as OwoColorize::on_yellow, Read more
Source§

fn fg_rgb<const R: u8, const G: u8, const B: u8>( &self, ) -> FgColorDisplay<'_, CustomColor<R, G, B>, Self>

Set the foreground color to a specific RGB value.
Source§

fn bg_rgb<const R: u8, const G: u8, const B: u8>( &self, ) -> BgColorDisplay<'_, CustomColor<R, G, B>, Self>

Set the background color to a specific RGB value.
Source§

fn truecolor(&self, r: u8, g: u8, b: u8) -> FgDynColorDisplay<'_, Rgb, Self>

Sets the foreground color to an RGB value.
Source§

fn on_truecolor(&self, r: u8, g: u8, b: u8) -> BgDynColorDisplay<'_, Rgb, Self>

Sets the background color to an RGB value.
Source§

fn style(&self, style: Style) -> Styled<&Self>

Apply a runtime-determined style
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

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

Initializes a with the given initializer. Read more
Source§

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

Dereferences the given pointer. Read more
Source§

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

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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