mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
fix(windows): tighten the install-dir clearing per review
- An image that fails to canonicalize stays in the lock check instead of
being silently skipped; only a positive match against the caller's own
running image is excluded.
- reap_recorded_daemon shares one deadline across the whole tree via a
new winproc::terminate_and_wait_all, which stop_for_update and
reap_descendants_of now use too — one implementation of "terminate,
then wait, bounded overall" instead of three.
- [UninstallRun] passes --update-install-dir "{app}" like
PrepareToInstall, so uninstalling after a daemon crash gets the same
orphaned-ConPTY-host cleanup as upgrading.
This commit is contained in:
@@ -130,12 +130,16 @@ Filename: "{app}\tty7-app.exe"; Description: "{cm:LaunchProgram,tty7}"; Flags: n
|
||||
|
||||
[UninstallRun]
|
||||
; Stop the daemon before the uninstaller deletes tty7-app.exe — the running daemon
|
||||
; is the locked image of that file, so removing it fails otherwise. This runs at
|
||||
; the start of uninstallation, before any files are removed. The installed binary
|
||||
; is this version, which understands the flag; runhidden suppresses any flash and
|
||||
; the call returns without opening a window. RunOnceId keys the entry so a repeated
|
||||
; uninstall doesn't run it twice.
|
||||
Filename: "{app}\tty7-app.exe"; Parameters: "--stop-daemon"; Flags: runhidden waituntilterminated; RunOnceId: "StopDaemon"
|
||||
; is the locked image of that file, so removing it fails otherwise. Naming {app}
|
||||
; widens the stop the same way PrepareToInstall's does: ConPTY hosts orphaned by
|
||||
; a daemon that never got to shut down keep the installed images open, and their
|
||||
; DeleteFile fails an uninstall exactly as it fails an upgrade. (The stop excludes
|
||||
; the calling process itself, so the binary running this step is safe.) This runs
|
||||
; at the start of uninstallation, before any files are removed. The installed
|
||||
; binary is this version, which understands the flags; runhidden suppresses any
|
||||
; flash and the call returns without opening a window. RunOnceId keys the entry
|
||||
; so a repeated uninstall doesn't run it twice.
|
||||
Filename: "{app}\tty7-app.exe"; Parameters: "--stop-daemon --update-install-dir ""{app}"""; Flags: runhidden waituntilterminated; RunOnceId: "StopDaemon"
|
||||
; Unconditional, and deliberately not gated on the task: an install that had the
|
||||
; menu registered and was later upgraded without the box ticked still holds the
|
||||
; keys, and verbs pointing at a deleted exe are worse than a no-op. Removing keys
|
||||
|
||||
@@ -419,12 +419,12 @@ fn reap_recorded_daemon() {
|
||||
.is_some_and(|entry| is_reapable_daemon_name(&entry.name));
|
||||
if matches {
|
||||
log::warn!("reaping unreachable daemon (pid {pid}); its sessions will be hung up");
|
||||
for descendant in winproc::descendants(&procs, pid) {
|
||||
winproc::terminate(descendant);
|
||||
winproc::wait_for_exit(descendant, REAP_WAIT_TIMEOUT);
|
||||
}
|
||||
winproc::terminate(pid);
|
||||
winproc::wait_for_exit(pid, REAP_WAIT_TIMEOUT);
|
||||
// One deadline across the whole tree: this runs synchronously before
|
||||
// the first window exists, and a crash that left many hosts behind
|
||||
// must not multiply the wait by their count.
|
||||
let mut targets = winproc::descendants(&procs, pid);
|
||||
targets.push(pid);
|
||||
winproc::terminate_and_wait_all(&targets, Instant::now() + REAP_WAIT_TIMEOUT);
|
||||
}
|
||||
pidfile::remove();
|
||||
}
|
||||
@@ -455,12 +455,8 @@ pub fn stop_for_update(install_dir: &Path) -> Result<(), String> {
|
||||
"terminating pid {pid} still running from {}",
|
||||
install_dir.display()
|
||||
);
|
||||
winproc::terminate(pid);
|
||||
}
|
||||
for pid in holdouts {
|
||||
let left = deadline.saturating_duration_since(Instant::now());
|
||||
winproc::wait_for_exit(pid, left);
|
||||
}
|
||||
winproc::terminate_and_wait_all(&holdouts, deadline);
|
||||
|
||||
wait_until_images_unlocked(install_dir, deadline)
|
||||
}
|
||||
@@ -491,8 +487,11 @@ fn wait_until_images_unlocked(dir: &Path, deadline: Instant) -> Result<(), Strin
|
||||
})
|
||||
})
|
||||
.filter(|path| {
|
||||
own.as_deref()
|
||||
.is_none_or(|own| std::fs::canonicalize(path).is_ok_and(|path| path != own))
|
||||
// Excluding takes a positive identification: a candidate that
|
||||
// cannot be canonicalized (delete-pending, held by a scanner) is
|
||||
// a lock to wait out, not ours to skip.
|
||||
!own.as_deref()
|
||||
.is_some_and(|own| std::fs::canonicalize(path).is_ok_and(|path| path == own))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -135,21 +135,29 @@ pub(crate) fn wait_for_exit(pid: u32, timeout: std::time::Duration) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Terminates every pid in order and waits for each to release its image,
|
||||
/// all bounded by the one `deadline` — never a fresh timeout per process.
|
||||
/// Callers pass parents after children when a parent could respawn one.
|
||||
pub(crate) fn terminate_and_wait_all(pids: &[u32], deadline: std::time::Instant) {
|
||||
for &pid in pids {
|
||||
terminate(pid);
|
||||
}
|
||||
for &pid in pids {
|
||||
let left = deadline.saturating_duration_since(std::time::Instant::now());
|
||||
if !wait_for_exit(pid, left) {
|
||||
log::warn!("process {pid} did not exit in time");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Terminates every descendant of `root` and waits for them to release their
|
||||
/// images, bounded by `timeout` overall. Deepest-first, like the per-pane
|
||||
/// kill, so a parent never respawns a child we already visited.
|
||||
pub(crate) fn reap_descendants_of(root: u32, timeout: std::time::Duration) {
|
||||
let deadline = std::time::Instant::now() + timeout;
|
||||
let targets = descendants(&snapshot(), root);
|
||||
for &pid in &targets {
|
||||
terminate(pid);
|
||||
}
|
||||
for pid in targets {
|
||||
let left = deadline.saturating_duration_since(std::time::Instant::now());
|
||||
if !wait_for_exit(pid, left) {
|
||||
log::warn!("descendant process {pid} did not exit in time");
|
||||
}
|
||||
}
|
||||
terminate_and_wait_all(
|
||||
&descendants(&snapshot(), root),
|
||||
std::time::Instant::now() + timeout,
|
||||
);
|
||||
}
|
||||
|
||||
/// Pids (other than the caller's own) whose executable image lives under
|
||||
|
||||
Reference in New Issue
Block a user