-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Expand file tree
/
Copy pathrestore.rs
More file actions
165 lines (145 loc) · 5.79 KB
/
Copy pathrestore.rs
File metadata and controls
165 lines (145 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use super::{
find_managed_agent_mut, kill_stale_tracked_processes, load_managed_agents, save_managed_agents,
spawn_agent_child, sync_managed_agent_processes, BackendKind, ManagedAgentProcess,
};
use crate::app_state::AppState;
use crate::util;
use std::sync::atomic::{AtomicBool, Ordering};
use tauri::Manager;
type SpawnResult = Result<(std::process::Child, std::path::PathBuf), String>;
type AgentSpawnResult = (String, SpawnResult);
/// Restore managed agents that were running before the app was closed.
///
/// Split into three phases to minimise lock contention with the frontend:
/// A (under lock): sync process state, cleanup, collect agents to start
/// B (no locks): resolve commands and spawn processes in parallel
/// C (re-lock): write back PIDs and status to records on disk
pub fn restore_managed_agents_on_launch(
app: &tauri::AppHandle,
shutdown_started: &AtomicBool,
) -> Result<(), String> {
if shutdown_started.load(Ordering::SeqCst) {
return Ok(());
}
let state = app.state::<AppState>();
// ── Phase A (under lock): housekeeping + collect agents to restore ──
let agents_to_start: Vec<super::ManagedAgentRecord>;
{
let _store_guard = state
.managed_agents_store_lock
.lock()
.map_err(|error| error.to_string())?;
if shutdown_started.load(Ordering::SeqCst) {
return Ok(());
}
let mut records = load_managed_agents(app)?;
let mut runtimes = state
.managed_agent_processes
.lock()
.map_err(|error| error.to_string())?;
let mut changed = sync_managed_agent_processes(&mut records, &mut runtimes);
changed |= kill_stale_tracked_processes(&mut records, &runtimes);
let tracked_pids: Vec<u32> = records
.iter()
.filter_map(|r| r.runtime_pid)
.chain(runtimes.values().map(|rt| rt.child.id()))
.collect();
super::sweep_orphaned_agent_processes(app, &tracked_pids);
// System-wide sweep: enumerate all user processes and kill any known
// agent binaries not tracked by this session. Catches orphans whose
// PID files were already cleaned up (e.g. agent workers in their own
// process group whose parent harness exited).
super::sweep_system_agent_processes(&tracked_pids);
let candidates: Vec<String> = records
.iter()
.filter(|record| record.start_on_app_launch && record.backend == BackendKind::Local)
.map(|record| record.pubkey.clone())
.collect();
let mut to_start = Vec::new();
for pubkey in &candidates {
if let Some(runtime) = runtimes.get_mut(pubkey) {
if runtime.child.try_wait().ok().flatten().is_none() {
continue;
}
}
if let Some(record) = records.iter().find(|r| r.pubkey == *pubkey) {
if let Some(pid) = record.runtime_pid {
if super::process_is_running(pid) {
continue;
}
}
to_start.push(record.clone());
}
}
agents_to_start = to_start;
if changed {
save_managed_agents(app, &records)?;
}
}
if agents_to_start.is_empty() {
return Ok(());
}
// Snapshot the workspace owner pubkey once for the legacy auth_tag fallback.
// Read outside the per-agent spawn loop so all parallel spawns see the same
// value and we don't lock `state.keys` repeatedly.
let owner_hex: Option<String> = state
.keys
.lock()
.map_err(|e| e.to_string())
.ok()
.map(|k| k.public_key().to_hex());
// ── Phase B (no locks): resolve commands and spawn processes in parallel ──
let spawn_results: Vec<AgentSpawnResult> = std::thread::scope(|scope| {
let owner_hex_ref = owner_hex.as_deref();
let handles: Vec<_> = agents_to_start
.iter()
.filter(|_| !shutdown_started.load(Ordering::SeqCst))
.map(|record| {
let pubkey = record.pubkey.clone();
let handle = scope.spawn(move || {
let result = spawn_agent_child(app, record, owner_hex_ref);
(pubkey, result)
});
handle
})
.collect();
handles.into_iter().map(|h| h.join().unwrap()).collect()
});
if spawn_results.is_empty() {
return Ok(());
}
// ── Phase C (re-acquire lock): write back PIDs and status to records ──
let _store_guard = state
.managed_agents_store_lock
.lock()
.map_err(|error| error.to_string())?;
let mut records = load_managed_agents(app)?;
let mut runtimes = state
.managed_agent_processes
.lock()
.map_err(|error| error.to_string())?;
for (pubkey, result) in spawn_results {
let record = match find_managed_agent_mut(&mut records, &pubkey) {
Ok(r) => r,
Err(_) => continue,
};
match result {
Ok((child, log_path)) => {
let now = util::now_iso();
record.updated_at = now.clone();
record.runtime_pid = Some(child.id());
record.last_started_at = Some(now);
record.last_stopped_at = None;
record.last_exit_code = None;
record.last_error = None;
runtimes.insert(pubkey, ManagedAgentProcess { child, log_path });
}
Err(error) => {
record.updated_at = util::now_iso();
record.last_error = Some(error);
}
}
}
save_managed_agents(app, &records)?;
Ok(())
}