Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/commands/agent_settings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use tauri::{AppHandle, Manager, State};
use tauri::{AppHandle, Manager};

use crate::{
app_state::AppState,
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/commands/channels_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn directory_cursor_keeps_same_second_tiebreaker() {
let event = ev_at(39000, "{}", vec![], timestamp);
let mut filter = serde_json::json!({"kinds": [39000], "limit": DIRECTORY_PAGE_SIZE});

advance_directory_cursor(&mut filter, &[event.clone()]);
advance_directory_cursor(&mut filter, std::slice::from_ref(&event));

assert_eq!(filter["until"], serde_json::json!(timestamp.as_secs()));
assert_eq!(filter["before_id"], serde_json::json!(event.id.to_hex()));
Expand Down
101 changes: 29 additions & 72 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,13 +349,19 @@ fn resolve_pgids_and_kill(candidate_pids: &[i32]) {
// PID-recycling guard: if a resolved PGID is alive but isn't one of our
// orphan candidates, the old harness PID was recycled by a new process
// that called setsid() — skip it to avoid killing an unrelated group.
let candidate_groups = pgids.len();
pgids.retain(|&pgid| {
if candidate_set.contains(&pgid) {
return true;
}
let alive = unsafe { libc::kill(pgid, 0) } == 0;
!alive
});
if pgids.is_empty() && candidate_groups > 0 {
eprintln!(
"buzz-desktop: orphan sweep: skipped all {candidate_groups} candidate group(s) (live foreign group leader or candidate already exited); nothing signalled"
);
}
let unique: Vec<i32> = pgids.into_iter().collect();
sigterm_then_sigkill(&unique);
}
Expand All @@ -377,13 +383,19 @@ fn resolve_pgids_and_kill(candidate_pids: &[i32]) {
// PID-recycling guard: if a resolved PGID is alive but isn't one of our
// orphan candidates, the old harness PID was recycled by a new process
// that called setsid() — skip it to avoid killing an unrelated group.
let candidate_groups = pgids.len();
pgids.retain(|&pgid| {
if candidate_set.contains(&pgid) {
return true;
}
let alive = unsafe { libc::kill(pgid, 0) } == 0;
!alive
});
if pgids.is_empty() && candidate_groups > 0 {
eprintln!(
"buzz-desktop: orphan sweep: skipped all {candidate_groups} candidate group(s) (live foreign group leader or candidate already exited); nothing signalled"
);
}
let unique: Vec<i32> = pgids.into_iter().collect();
sigterm_then_sigkill(&unique);
}
Expand Down Expand Up @@ -515,18 +527,11 @@ pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32])
if info.pbi_uid != my_uid {
continue;
}
// Live child of a tracked harness — not an orphan.
if skip_pids.contains(&info.pbi_ppid) {
continue;
}
// Grandchild check: the harness is spawned with process_group(0), so
// all descendants share its PGID. If this process's PGID matches a
// tracked harness PID, it's a live descendant — not an orphan.
let pgid = unsafe { libc::getpgid(pid) };
if pgid > 0 && skip_pids.contains(&(pgid as u32)) {
if !process_has_buzz_marker(upid, instance_id) {
continue;
}
if !process_has_buzz_marker(upid, instance_id) {
// Live descendants of a tracked harness are exempt — see sweep::is_live_descendant_*.
if sweep::is_live_descendant_macos(upid, info.pbi_ppid, skip_pids) {
continue;
}
orphans.push(pid);
Expand All @@ -541,27 +546,12 @@ pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32])
}
}

/// Read the parent PID of a process from /proc/<pid>/stat.
/// The comm field (field 2) may contain spaces and parens, so we find the last
/// ')' and parse fields after it. Field 1 after ')' is state, field 2 is PPID.
#[cfg(all(unix, not(target_os = "macos")))]
fn read_ppid_linux(pid: u32) -> Option<u32> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
let after_comm = stat.rsplit_once(')')?.1;
// Fields after ')': " S ppid pgid ..."
let ppid_str = after_comm.split_whitespace().nth(1)?;
ppid_str.parse::<u32>().ok()
}

/// Read the process group ID from /proc/<pid>/stat. Same parsing strategy as
/// `read_ppid_linux` — field 3 after the closing ')' is the PGID.
/// Read the process group ID from /proc/<pid>/stat by delegating to the shared
/// stat parser in `sweep`. Keeps a single parse site for the `/proc/<pid>/stat`
/// field layout.
#[cfg(all(unix, not(target_os = "macos")))]
fn read_pgid_linux(pid: u32) -> Option<u32> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
let after_comm = stat.rsplit_once(')')?.1;
// Fields after ')': " S ppid pgid ..."
let pgid_str = after_comm.split_whitespace().nth(2)?;
pgid_str.parse::<u32>().ok()
sweep::proc_stat_ppid_pgid_linux(pid).map(|(_, pgid)| pgid)
}

#[cfg(all(unix, not(target_os = "macos")))]
Expand Down Expand Up @@ -599,23 +589,9 @@ pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32])
if !process_belongs_to_us(upid) || !process_has_buzz_marker(upid, instance_id) {
continue;
}
// Live child of a tracked harness — not an orphan. If /proc/<pid>/stat
// is unreadable (process exiting, transient I/O error), we treat the
// process as orphaned — safe because an exiting process will disappear
// shortly, and the two-tick grace in the periodic path prevents acting
// on transient failures.
if let Some(ppid) = read_ppid_linux(upid) {
if skip_pids.contains(&ppid) {
continue;
}
}
// Grandchild check: the harness is spawned with process_group(0), so
// all descendants share its PGID. If this process's PGID matches a
// tracked harness PID, it's a live descendant — not an orphan.
if let Some(pgid) = read_pgid_linux(upid) {
if skip_pids.contains(&pgid) {
continue;
}
// Live descendants of a tracked harness are exempt — see sweep::is_live_descendant_*.
if sweep::is_live_descendant_linux(upid, skip_pids) {
continue;
}
orphans.push(pid);
}
Expand Down Expand Up @@ -714,20 +690,14 @@ pub(crate) fn collect_same_instance_orphans(
if info.pbi_uid != my_uid {
continue;
}
// Live child of a tracked harness — not an orphan.
if skip_pids.contains(&info.pbi_ppid) {
if !process_has_buzz_marker(upid, instance_id) {
continue;
}
// Grandchild check: the harness is spawned with process_group(0), so
// all descendants share its PGID. If this process's PGID matches a
// tracked harness PID, it's a live descendant — not an orphan.
let pgid = unsafe { libc::getpgid(pid) };
if pgid > 0 && skip_pids.contains(&(pgid as u32)) {
// Live descendants of a tracked harness are exempt — see sweep::is_live_descendant_*.
if sweep::is_live_descendant_macos(upid, info.pbi_ppid, skip_pids) {
continue;
}
if process_has_buzz_marker(upid, instance_id) {
orphans.insert(upid);
}
orphans.insert(upid);
}
orphans
}
Expand Down Expand Up @@ -769,22 +739,9 @@ pub(crate) fn collect_same_instance_orphans(
if !process_belongs_to_us(upid) || !process_has_buzz_marker(upid, instance_id) {
continue;
}
// Live child of a tracked harness — not an orphan. If /proc/<pid>/stat
// is unreadable (process exiting, transient I/O error), we treat the
// process as orphaned — safe because an exiting process will disappear
// shortly, and the two-tick grace prevents acting on transient failures.
if let Some(ppid) = read_ppid_linux(upid) {
if skip_pids.contains(&ppid) {
continue;
}
}
// Grandchild check: the harness is spawned with process_group(0), so
// all descendants share its PGID. If this process's PGID matches a
// tracked harness PID, it's a live descendant — not an orphan.
if let Some(pgid) = read_pgid_linux(upid) {
if skip_pids.contains(&pgid) {
continue;
}
// Live descendants of a tracked harness are exempt — see sweep::is_live_descendant_*.
if sweep::is_live_descendant_linux(upid, skip_pids) {
continue;
}
orphans.insert(upid);
}
Expand Down
Loading
Loading