Skip to content
Open
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Entries before the rename below shipped under the project's former name, Conduit

### Added

- Old gateway binaries are now cleaned up instead of accumulating forever. Every
published gateway stayed on disk (about 18 MB a release, roughly 200 MB on a
long-lived install). Toolport now deletes the ones nothing can still be using,
and keeps any binary that is running, that a client config still names, that a
client is known to be relaunching, or that is recent enough to plausibly be
cached, so cleanup can never turn an app that runs old code into one that
cannot start the gateway at all. (SOU-484)
- Toolport now names the apps that keep launching an obsolete gateway. Stopping a
stale gateway process is not always enough: an app caches its spawn command when
it starts, so one pinned to a path an upgrade never rewrites simply launches the
Expand Down
48 changes: 48 additions & 0 deletions src-tauri/src/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4482,6 +4482,54 @@ fn read_gateway_profile(client_id: &str) -> Option<String> {
/// Only entries we still own are ever rewritten. Ownership is the registry record
/// when present, else the SOU-405 command-basename heuristic (issue #487 / SOU-406).
/// A Customized entry is left byte-identical and reported in [`RepointOutcome::customized`].
/// Every gateway binary path a detected client would spawn.
///
/// Used to decide which published gateway binaries are safe to delete (SOU-484).
/// Deleting one a client still names turns "runs old code" into "cannot start the
/// gateway at all", so this is the authoritative do-not-delete set.
///
/// Returns `None` when any client's config could not be read. That client's
/// reference set is then unknown, and an unknown reference must not be treated as
/// an absent one: pruning is never urgent, so the caller skips the pass entirely
/// and retries on the next launch.
///
/// Unlike [`repoint_stale_gateways`], customized entries are **included**. Repoint
/// leaves them alone, but they still name a binary the user's client will spawn,
/// which is exactly what must survive.
///
/// `plugin_servers` are included for the same reason, and more strongly: they live
/// outside the main config, are managed by the client rather than by us, and so can
/// never be re-pointed onto a current binary. Deleting one out from under a plugin
/// entry leaves a reference nothing will ever repair.
pub fn referenced_gateway_paths() -> Option<Vec<PathBuf>> {
let mut out: Vec<PathBuf> = Vec::new();
for client in detect_clients() {
if client.error.is_some() {
return None;
}
if !client.config_exists {
continue;
}
for server in client.servers.iter().chain(client.plugin_servers.iter()) {
if !gateway_identity_matches(&server.name, &server.name, server.command.as_deref()) {
continue;
}
let Some(command) = server.command.as_deref() else {
continue;
};
let command = command.trim();
if command.is_empty() {
continue;
}
let path = PathBuf::from(command);
if !out.iter().any(|p| p == &path) {
out.push(path);
}
}
}
Some(out)
}

pub fn repoint_stale_gateways(managed: &HashMap<String, ManagedEntry>) -> RepointOutcome {
let mut outcome = RepointOutcome::default();
let Some(current) = resolve_gateway_path().map(|p| p.to_string_lossy().into_owned()) else {
Expand Down
18 changes: 18 additions & 0 deletions src-tauri/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3577,6 +3577,24 @@ pub fn run() {
log_reap_outcome("delayed reaper", &again);
let newly_found = unannounced(&already_announced, again.needs_restart);
announce_restart_needed(&migrate_handle, &newly_found);

// Only now delete old gateway binaries (SOU-484). Both reaper
// passes have run, so clients that were going to respawn an
// obsolete image have done so and are recorded in the advice;
// deleting one a client still spawns would break it outright
// rather than leave it on old code. Last, and deliberately
// after the delay, because every input this needs is evidence
// the passes above produced.
let advised = migrate_handle
.state::<RestartAdvice>()
.current()
.into_iter()
.map(|c| c.gateway)
.collect();
crate::gateway_publish::prune_published_gateways(
clients::referenced_gateway_paths(),
advised,
);
});
});

Expand Down
Loading
Loading