pub struct ProcessBroker {
cache: Mutex<HashMap<String, ProcessStatus>>,
mps_broker: Option<Arc<Broker>>,
}Fields§
§cache: Mutex<HashMap<String, ProcessStatus>>Broker-owned cache — the single source future callers read from,
rather than each composing blockcontroller/process_tracker
themselves. parking_lot::Mutex, not an async mutex: unlike the
credential broker’s Entry::lock (which guards an actual network
refresh call and must not block a tokio worker thread while held),
this cache’s critical section is two cheap in-memory map reads plus
a struct build — synchronous and fast, the same reasoning
process_tracker::registry::AgentProcessRegistry already uses
parking_lot::Mutex for. Coarse-grained (one mutex, not
per-block_id): contention is expected to be negligible for the same
reason; narrow the granularity later if profiling says otherwise.
If a later phase adds a genuinely blocking liveness check (report
§3.0.3 point 1 — e.g. a real health-probe round-trip), that phase
should revisit this as an async lock at the same time, not before.
mps_broker: Option<Arc<Broker>>Implementations§
Source§impl ProcessBroker
impl ProcessBroker
pub fn new(mps_broker: Option<Arc<WpsBroker>>) -> Self
Sourcepub fn status(&self, block_id: &str) -> ProcessStatus
pub fn status(&self, block_id: &str) -> ProcessStatus
Recompute and return one block’s status. Single-flight-guarded by
holding the cache lock across the entire read-compare-write
sequence, not just the write — compute_status must run inside the
critical section, not before it. reagent P1 on #2273 caught this:
with the read outside the lock, two concurrent callers (e.g. an
Agent pane mount and Swarm’s refresh landing near-simultaneously)
could interleave so the thread that read first but wrote
second overwrites a fresher value the other thread already wrote —
the cache regresses to a stale snapshot even though a newer read
already happened. Locking around the whole sequence (safe here
because compute_status is synchronous and cheap — see the cache
field’s own doc comment) makes “last writer wins” and “last reader
wins” the same thing, which is what single-flight is supposed to
guarantee.
Sourcepub fn list(&self) -> Vec<ProcessStatus>
pub fn list(&self) -> Vec<ProcessStatus>
The missing batch query (report §5.4) — replaces both
agent.tracked-blocks’s .chain() of two independent registries
and the N sequential single-block GetControllerStatus calls
Swarm’s subscribeToBlockStatuses issues today with one call.
Discovery source is blockcontroller::get_all_controllers —
authoritative for every controller type, closing the coverage gap
process_tracker alone has for shell/acp blocks (report §1).
Returns EVERY controller-backed block — plain shell/cmd
terminals included, not just agent panes. Use list_agent_panes()
for the agent-scoped subset (which is what agent.tracked-blocks
actually needs — see that method’s doc comment).
Sourcepub fn list_agent_panes(&self) -> Vec<ProcessStatus>
pub fn list_agent_panes(&self) -> Vec<ProcessStatus>
Agent-scoped subset of list(). agent.tracked-blocks’s original
(pre-broker) implementation was implicitly agent-only — the two
registries it unioned (process_tracker, populated only by
agent-CLI controller types, and reactive’s registration list,
populated only via register_agent) never included a plain
terminal. list() alone loses that scoping because
get_all_controllers() is controller-type-agnostic by design
(reagent/codex P1 on #2273 — an earlier version of this PR fed
list() straight into agent.tracked-blocks, which made every
open terminal pane show up in Swarm mislabeled as an “Agent”).
See ProcessStatus::is_agent for the exact classification rule.
Sourcepub fn forget(&self, block_id: &str)
pub fn forget(&self, block_id: &str)
Drop a block from the cache. Call on pane close so list() doesn’t
keep serving a stale entry for a block whose controller is gone —
compute_status would report Lifecycle::Unknown for it on the
next status() call regardless, but a closed pane shouldn’t appear
in list() (sourced from get_all_controllers(), which already
won’t include it) while a stale cache entry lingers unreachable.
fn emit_changed(&self, status: &ProcessStatus)
Sourcepub fn emit_tracked_blocks_changed(&self)
pub fn emit_tracked_blocks_changed(&self)
Announce that list()/list_agent_panes() membership may have
changed — i.e. blockcontroller::register_controller/
delete_controller just ran. Unscoped (no block:<id>, unlike
emit_changed above): this is “recompute your list,” not a status
update for one already-known block, so there is no single block
scope to attach it to.
Exists because agent.tracked-blocks’s only data source
(CONTROLLER_REGISTRY) never announced its own writes — Swarm’s
agent.tracked-blocks list was refreshed only by two different,
independently-timed mechanisms standing in as proxies
(process_tracker’s 2s poll-and-diff loop, and reactive‘s own
registration events), neither directly coupled to when a controller
actually got registered or removed. That gap let restored agent
panes’ controllers register successfully (confirmed via
resync_controller’s own logging) while Swarm’s client-side list —
fetched once at mount, refreshed only by those two proxies — stayed
stale indefinitely for any block whose proxy signal didn’t happen to
fire. See docs/reports/REPORT_SWARM_MOUNT_DEPENDENT_TRACKING_GAP_2026_09_15.md.
Auto Trait Implementations§
impl !Freeze for ProcessBroker
impl !RefUnwindSafe for ProcessBroker
impl Send for ProcessBroker
impl Sync for ProcessBroker
impl Unpin for ProcessBroker
impl UnsafeUnpin for ProcessBroker
impl UnwindSafe for ProcessBroker
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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