pub struct TaskDriver { /* private fields */ }Expand description
Manages in-flight task runs for a single camp.
Wrap in Arc to share across tasks; internal state is mutex-protected.
Implementations§
Source§impl TaskDriver
impl TaskDriver
Sourcepub async fn new(store: Arc<TaskStore>) -> Result<Self, DriverError>
pub async fn new(store: Arc<TaskStore>) -> Result<Self, DriverError>
Create a driver backed by store, with no side-channels.
Immediately scans the store for Running runs left over from a prior
daemon process and marks them Lost (“Lost-on-disappear”).
Sourcepub async fn with_channels(
store: Arc<TaskStore>,
channels: DriverChannels,
) -> Result<Self, DriverError>
pub async fn with_channels( store: Arc<TaskStore>, channels: DriverChannels, ) -> Result<Self, DriverError>
Like new but wires the optional DriverChannels side-channels
(completion notifications, live output tap).
Sourcepub async fn with_config(
store: Arc<TaskStore>,
channels: DriverChannels,
stale_policy: StaleRunPolicy,
) -> Result<Self, DriverError>
pub async fn with_config( store: Arc<TaskStore>, channels: DriverChannels, stale_policy: StaleRunPolicy, ) -> Result<Self, DriverError>
Full constructor: side-channels plus the StaleRunPolicy applied to
Running rows already in the store.
R617-F6 — annotation in this file’s header. Splitting the sweep out of the constructor’s fixed behaviour is what lets a store be shared: a process that is not the run’s owner can now attach without declaring the owner’s live work dead.
Sourcepub async fn spawn_run(
&self,
cmd: &str,
opts: SpawnOpts,
) -> Result<TaskRunId, DriverError>
pub async fn spawn_run( &self, cmd: &str, opts: SpawnOpts, ) -> Result<TaskRunId, DriverError>
Spawn cmd in a PTY and start capturing its output. Returns immediately
with the new TaskRunId.
A beholder is selected via opts.beholder_select (default Auto). When
a Rewriter beholder matches, its adjust_argv is applied to the
command before spawning and the diff is recorded on beholder_status.
When opts.verbatim_output is true, Rewriter beholders decline in
Auto mode, because something downstream renders these bytes and a
rewrite would change them.
Output is written to the store as Stream::Stdout chunks (the PTY
kernel merges stdout and stderr). Signal handling and status updates
run in background tasks.
Sourcepub async fn resize_run(
&self,
id: &TaskRunId,
cols: u16,
rows: u16,
) -> Result<(), DriverError>
pub async fn resize_run( &self, id: &TaskRunId, cols: u16, rows: u16, ) -> Result<(), DriverError>
Resize a running task’s PTY and deliver SIGWINCH to the foreground
process group (portable-pty’s resize does the ioctl, which is what
signals the child).
Returns DriverError::NotFound when the run is not active on this
driver instance — the same contract as TaskDriver::send_stdin — and
also when it is active but was spawned in SpawnOpts::pipe mode, which
has no terminal to resize.
Sourcepub fn foreground_pid(&self, id: &TaskRunId) -> Option<u32>
pub fn foreground_pid(&self, id: &TaskRunId) -> Option<u32>
The pid of the run’s foreground process — the leader of the process group the PTY currently gives the keyboard to.
For a shell tile that is the shell itself while it sits at a prompt, and the command the operator is running while one is in flight. That distinction is the whole point: asking the spawned child would report the shell forever, so anything derived from this pid (a live cwd probe, a “what is this pane doing” label) would answer for the wrong process.
None when the run is not active on this driver instance, when it was
spawned in SpawnOpts::pipe mode (no controlling terminal, so no
foreground process group to read), or when the platform has no notion of
a foreground process group.
Sourcepub async fn kill_run(
&self,
id: &TaskRunId,
signal: Option<i32>,
) -> Result<(), DriverError>
pub async fn kill_run( &self, id: &TaskRunId, signal: Option<i32>, ) -> Result<(), DriverError>
Send signal to a running task. Defaults to SIGTERM (15).
For SIGTERM, the driver waits up to 5 seconds for the process to exit
before escalating to SIGKILL. Returns DriverError::NotFound if the
run is not active (already exited or launched on a different driver
instance).
Sourcepub async fn send_stdin(
&self,
id: &TaskRunId,
bytes: Vec<u8>,
) -> Result<(), DriverError>
pub async fn send_stdin( &self, id: &TaskRunId, bytes: Vec<u8>, ) -> Result<(), DriverError>
Write bytes to the stdin of a running task (requires stdin_enabled).
Sourcepub fn note_attached(&self, id: &TaskRunId)
pub fn note_attached(&self, id: &TaskRunId)
R739-B12 — record that a client just looked at this run.
A no-op for a run this driver does not own (already finished, or spawned by another process against the same store): attachment only means anything for a run something here could still signal.
Sourcepub fn attached_age(&self, id: &TaskRunId) -> Option<Duration>
pub fn attached_age(&self, id: &TaskRunId) -> Option<Duration>
How long ago a client last looked at id, or None when this driver
does not own the run. The observable half of Self::note_attached.
Sourcepub async fn reap_unattached(
&self,
idle: Duration,
origins: &[String],
) -> Vec<TaskRunId>
pub async fn reap_unattached( &self, idle: Duration, origins: &[String], ) -> Vec<TaskRunId>
R739-B12 — SIGTERM every run of an opted-in origin that no client has
looked at for idle. Returns the runs it signalled.
This exists because a run outlives the client that asked for it. When
yah build run is SIGKILLed — its harness dies, the terminal goes away
— the cargo it relocated into the daemon keeps compiling with nobody
attached, holding the build-directory lock until a human finds the pid.
That happened on 2026-08-28 and stalled a whole camp for ~30 minutes.
R739-B9 closed every give-up the client is alive to make; this closes
the one it is not.
Not StaleRunPolicy, and not that policy on a timer. The policy
is a construction-time reconciliation of rows a previous process
left behind: it decides on host_pid, only ever calls
store.update_status, and tombstones any run outside its origin list
outright — so running it periodically would mark every in-flight run of
an un-adopted origin Lost while it compiles perfectly well, and would
still never signal the process that is the actual problem. This is the
opposite shape: it decides on attachment, it signals, and it touches
nothing outside origins.
origins is an opt-in list precisely because most runs must never be
reaped on this rule. An interactive terminal tile is legitimately
unpolled for hours, and killing one would be a far worse bug than the
orphan this prevents — so an empty list reaps nothing at all, rather
than meaning “every origin” the way StaleRunPolicy’s list does.
Auto Trait Implementations§
impl !RefUnwindSafe for TaskDriver
impl !UnwindSafe for TaskDriver
impl Freeze for TaskDriver
impl Send for TaskDriver
impl Sync for TaskDriver
impl Unpin for TaskDriver
impl UnsafeUnpin for TaskDriver
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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§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.Source§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.Source§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.Source§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.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§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>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§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>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§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.Source§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.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<T> ErasedDestructor for Twhere
T: 'static,
impl<T> Fruit for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> 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