mj_controller/controller/checkpoint/
workspace_lease.rs1use super::*;
2
3pub struct IdleWorkspaceLease {
7 pub(super) lease: ManagedSessionLease,
8 pub(super) command_id: String,
9 pub(super) harness: HarnessKind,
10}
11
12impl IdleWorkspaceLease {
13 pub async fn acquire(handle: &ManagedSessionHandle, harness: HarnessKind) -> Result<Self> {
14 tokio::time::timeout(Duration::from_secs(30), async {
15 let mut lease = handle.lease_connection().await?;
16 let snapshot = lease.connection_mut().sync().await?;
17 ensure!(
18 snapshot.operational.safe_to_replace(harness),
19 "session must be live and idle with no queued or background work"
20 );
21 let command_id = new_command_id("workspace-write")?;
22 lease
23 .connection_mut()
24 .submit(
25 command_id.clone(),
26 RelayCommand::BeginCheckpoint {
27 reason: Some("API workspace file write".into()),
28 },
29 )
30 .await?;
31 loop {
32 let snapshot = lease.connection_mut().sync().await?;
33 if checkpoint_barrier_is_ready(&snapshot, &command_id) {
34 let mut operation = Self {
35 lease,
36 command_id,
37 harness,
38 };
39 operation.verify().await?;
40 return Ok(operation);
41 }
42 ensure!(
43 snapshot.operational.execution != RelayExecutionState::Running,
44 "session started work before the file barrier was ready"
45 );
46 tokio::time::sleep(Duration::from_millis(25)).await;
47 }
48 })
49 .await
50 .context("session did not become available for a file write within 30 seconds")?
51 }
52
53 pub async fn verify(&mut self) -> Result<()> {
54 let mut snapshot = self.lease.connection_mut().sync().await?;
55 ensure!(
56 checkpoint_barrier_is_ready(&snapshot, &self.command_id),
57 "file write lost its workspace barrier"
58 );
59 snapshot.operational.checkpoint_barrier = None;
60 snapshot.operational.queued_prompts.clear();
63 ensure!(
64 snapshot.operational.safe_to_replace(self.harness),
65 "session is no longer idle for the file write"
66 );
67 Ok(())
68 }
69
70 pub async fn release(mut self) -> Result<()> {
71 tokio::time::timeout(Duration::from_secs(30), async {
72 self.lease
73 .connection_mut()
74 .submit(
75 new_command_id("workspace-release")?,
76 RelayCommand::ReleaseCheckpoint {
77 barrier_command_id: self.command_id.clone(),
78 },
79 )
80 .await?;
81 loop {
82 let snapshot = self.lease.connection_mut().sync().await?;
83 if snapshot.operational.checkpoint_barrier.as_deref() != Some(&self.command_id) {
84 return Ok::<_, anyhow::Error>(());
85 }
86 tokio::time::sleep(Duration::from_millis(25)).await;
87 }
88 })
89 .await
90 .context("release file write barrier timed out")??;
91 self.lease.release();
92 Ok(())
93 }
94}
95
96pub(super) fn checkpoint_barrier_is_ready(
97 snapshot: &ManagedSessionSnapshot,
98 command_id: &str,
99) -> bool {
100 snapshot.operational.checkpoint_barrier.as_deref() == Some(command_id)
101 && snapshot.operational.checkpoint_ready.is_some()
102}