Skip to content

Commit ed75374

Browse files
committed
refactor: improve kill daemon decision logic for interactive prompts
1 parent 2919458 commit ed75374

2 files changed

Lines changed: 31 additions & 13 deletions

File tree

src/commands/memory.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -706,15 +706,28 @@ fn snapshots_share_no_node_ids(
706706
return false;
707707
}
708708
// Overlap check is intentionally class-agnostic: an object can change
709-
// class name across snapshots, but its ID cannot.
710-
let base_nodes_count: usize = base.values().map(|a| a.nodes.len()).sum();
711-
let mut base_ids: std::collections::HashSet<u64> =
712-
std::collections::HashSet::with_capacity(base_nodes_count);
713-
base_ids.extend(base.values().flat_map(|a| a.nodes.keys().copied()));
714-
!current
715-
.values()
716-
.flat_map(|a| a.nodes.keys())
717-
.any(|id| base_ids.contains(id))
709+
// class name across snapshots, but its ID cannot. Build the HashSet from
710+
// whichever side has fewer IDs, then scan the other — this bounds the
711+
// peak allocation to the smaller set for large snapshots.
712+
let base_count: usize = base.values().map(|a| a.nodes.len()).sum();
713+
let current_count: usize = current.values().map(|a| a.nodes.len()).sum();
714+
if base_count <= current_count {
715+
let mut ids: std::collections::HashSet<u64> =
716+
std::collections::HashSet::with_capacity(base_count);
717+
ids.extend(base.values().flat_map(|a| a.nodes.keys().copied()));
718+
!current
719+
.values()
720+
.flat_map(|a| a.nodes.keys())
721+
.any(|id| ids.contains(id))
722+
} else {
723+
let mut ids: std::collections::HashSet<u64> =
724+
std::collections::HashSet::with_capacity(current_count);
725+
ids.extend(current.values().flat_map(|a| a.nodes.keys().copied()));
726+
!base
727+
.values()
728+
.flat_map(|a| a.nodes.keys())
729+
.any(|id| ids.contains(id))
730+
}
718731
}
719732

720733
/// Offline implementation of `compare-heapsnapshots`. Parses both files,

src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,10 @@ enum KillDaemonDecision {
485485
RefuseNonInteractive,
486486
}
487487

488-
fn kill_daemon_decision(force: bool, is_tty: bool) -> KillDaemonDecision {
488+
fn kill_daemon_decision(force: bool, can_prompt: bool) -> KillDaemonDecision {
489489
if force {
490490
KillDaemonDecision::Proceed
491-
} else if is_tty {
491+
} else if can_prompt {
492492
KillDaemonDecision::PromptUser
493493
} else {
494494
KillDaemonDecision::RefuseNonInteractive
@@ -882,7 +882,11 @@ pub async fn run() -> Result<()> {
882882
// field.
883883
if let Commands::KillDaemon { force } = &cli.command {
884884
use std::io::IsTerminal;
885-
match kill_daemon_decision(*force, std::io::stdin().is_terminal()) {
885+
// A prompt is only useful if the user can both type (stdin) and see it
886+
// (stderr). If stderr is not a TTY (e.g. `2>file`) while stdin still is,
887+
// prompting would block silently — treat that as non-interactive.
888+
let can_prompt = std::io::stdin().is_terminal() && std::io::stderr().is_terminal();
889+
match kill_daemon_decision(*force, can_prompt) {
886890
KillDaemonDecision::RefuseNonInteractive => {
887891
return Err(error::CliError::new(
888892
error::ErrorCode::InvalidInput,
@@ -1480,7 +1484,8 @@ mod tests {
14801484
}
14811485

14821486
#[test]
1483-
fn test_kill_daemon_decision_tty_prompts() {
1487+
fn test_kill_daemon_decision_can_prompt_prompts() {
1488+
// Both stdin and stderr are terminals: ask the human.
14841489
assert_eq!(
14851490
kill_daemon_decision(false, true),
14861491
KillDaemonDecision::PromptUser

0 commit comments

Comments
 (0)