fix(installer): retry stashing app/worker through transient file locks on update
The update pipeline moved app/ and worker/ to .bak before extraction with no retry, so a just-killed worker whose file handles had not been released yet (WaitForExit returns before the OS flushes them) caused Directory.Move to throw ERROR_SHARING_VIOLATION, stopping the pipeline on a cryptic error screen. - DownloadAndExtractStep: stash/rollback moves+deletes now retry through transient IO/access errors (~5s); a persistent lock returns an actionable message instead of the raw error. - StopWorkerStep: reading MainModule no longer skips Kill on failure, and a short settle follows the kill so handles are released before extraction.
This commit is contained in:
@@ -19,22 +19,33 @@ public sealed class StopWorkerStep : IInstallStep
|
||||
{
|
||||
progress.Report("Stopping ClaudeDo processes (if running)...");
|
||||
var installDir = ctx.InstallDirectory;
|
||||
var killedAny = false;
|
||||
foreach (var name in ProcessNames)
|
||||
{
|
||||
foreach (var p in Process.GetProcessesByName(name))
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = p.MainModule?.FileName;
|
||||
// Scope to THIS install when the module path is readable; if it
|
||||
// can't be read (access race / exiting process), fall through and
|
||||
// kill anyway — a survivor would lock the install dir during
|
||||
// extraction. Reading MainModule must not skip the Kill.
|
||||
string? path = null;
|
||||
try { path = p.MainModule?.FileName; } catch { /* unreadable — kill anyway */ }
|
||||
if (path is not null && !IsUnder(path, installDir)) continue;
|
||||
p.Kill(entireProcessTree: true);
|
||||
p.WaitForExit(10000);
|
||||
killedAny = true;
|
||||
}
|
||||
catch { /* process may have exited or be inaccessible */ }
|
||||
finally { p.Dispose(); }
|
||||
}
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
|
||||
// WaitForExit returns before the OS releases the process's file handles.
|
||||
// Give it a moment so DownloadAndExtractStep's Directory.Move doesn't race
|
||||
// a still-open handle. (That step also retries, this just avoids the churn.)
|
||||
if (killedAny) await Task.Delay(1500, ct);
|
||||
return StepResult.Ok();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user