fix(launcher): harden updater crash recovery
This commit is contained in:
parent
2d2a5b5046
commit
1955ca8ab5
27 changed files with 2714 additions and 544 deletions
|
|
@ -5,15 +5,176 @@ using AcDream.Launcher.Core.Installation;
|
|||
using AcDream.Launcher.Core.Updates;
|
||||
using AcDream.Platform;
|
||||
|
||||
return args.FirstOrDefault() switch
|
||||
const string SelfUpdateDataEnvironment = "ACDREAM_SELF_UPDATE_FIXTURE_DATA";
|
||||
const string SelfUpdateTargetEnvironment = "ACDREAM_SELF_UPDATE_FIXTURE_TARGET";
|
||||
const string SelfUpdateHelperPidEnvironment = "ACDREAM_SELF_UPDATE_FIXTURE_HELPER_PID";
|
||||
|
||||
string[] effectiveArgs = args;
|
||||
string? selfUpdateData = Environment.GetEnvironmentVariable(SelfUpdateDataEnvironment);
|
||||
string? selfUpdateTarget = Environment.GetEnvironmentVariable(SelfUpdateTargetEnvironment);
|
||||
if (!string.IsNullOrWhiteSpace(selfUpdateData)
|
||||
&& !string.IsNullOrWhiteSpace(selfUpdateTarget)
|
||||
&& IsBootstrapInvocation(effectiveArgs))
|
||||
{
|
||||
"hold-install-lease" => await HoldInstallLeaseAsync(args[1..]),
|
||||
"hold-update-lease" => await HoldUpdateLeaseAsync(args[1..]),
|
||||
"orphan-parent" => await RunOrphanParentAsync(args[1..]),
|
||||
"orphan-child" => RunOrphanChild(args[1..]),
|
||||
if (effectiveArgs[0] == LauncherSelfUpdateBootstrap.HelperArgument
|
||||
&& Environment.GetEnvironmentVariable(SelfUpdateHelperPidEnvironment) is string helperPid
|
||||
&& !string.IsNullOrWhiteSpace(helperPid))
|
||||
{
|
||||
File.WriteAllText(
|
||||
Path.GetFullPath(helperPid),
|
||||
Environment.ProcessId.ToString(
|
||||
System.Globalization.CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
using var http = new HttpClient();
|
||||
var manager = new LauncherSelfUpdateManager(Paths(selfUpdateData), http);
|
||||
SelfUpdateStartupResult startup = await LauncherSelfUpdateBootstrap.HandleAsync(
|
||||
effectiveArgs,
|
||||
manager,
|
||||
Path.GetFullPath(selfUpdateTarget),
|
||||
Path.GetFullPath(
|
||||
Environment.ProcessPath
|
||||
?? throw new InvalidOperationException("Process path is unavailable.")));
|
||||
if (startup.ShouldExit)
|
||||
{
|
||||
return startup.ExitCode;
|
||||
}
|
||||
|
||||
effectiveArgs = startup.RemainingArguments;
|
||||
}
|
||||
|
||||
return effectiveArgs.FirstOrDefault() switch
|
||||
{
|
||||
"hold-install-lease" => await HoldInstallLeaseAsync(effectiveArgs[1..]),
|
||||
"hold-update-lease" => await HoldUpdateLeaseAsync(effectiveArgs[1..]),
|
||||
"orphan-parent" => await RunOrphanParentAsync(effectiveArgs[1..]),
|
||||
"orphan-child" => RunOrphanChild(effectiveArgs[1..]),
|
||||
"crash-self-update" => await CrashSelfUpdateAsync(effectiveArgs[1..]),
|
||||
"stage-self-update" => await StageSelfUpdateAsync(effectiveArgs[1..]),
|
||||
"bootstrap-probe" => await BootstrapProbeAsync(effectiveArgs[1..]),
|
||||
"canonical-probe" => CanonicalProbe(effectiveArgs[1..]),
|
||||
_ => 2,
|
||||
};
|
||||
|
||||
static bool IsBootstrapInvocation(string[] arguments) =>
|
||||
arguments.Length > 0
|
||||
&& arguments[0] is LauncherSelfUpdateBootstrap.HelperArgument
|
||||
or LauncherSelfUpdateBootstrap.ConfirmArgument
|
||||
or LauncherSelfUpdateBootstrap.DeferredArgument
|
||||
or "canonical-probe";
|
||||
|
||||
static ApplicationPathSet Paths(string dataDirectory)
|
||||
{
|
||||
string data = Path.GetFullPath(dataDirectory);
|
||||
return new ApplicationPathSet(
|
||||
Path.Combine(data, "fixture-config"),
|
||||
data,
|
||||
Path.Combine(data, "fixture-cache"),
|
||||
null);
|
||||
}
|
||||
|
||||
static async Task<int> CrashSelfUpdateAsync(string[] arguments)
|
||||
{
|
||||
if (arguments.Length != 4)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
string dataDirectory = Path.GetFullPath(arguments[0]);
|
||||
string targetDirectory = Path.GetFullPath(arguments[1]);
|
||||
string readyPath = Path.GetFullPath(arguments[2]);
|
||||
string canonicalName = arguments[3];
|
||||
using var http = new HttpClient();
|
||||
var manager = new LauncherSelfUpdateManager(
|
||||
Paths(dataDirectory),
|
||||
http,
|
||||
null,
|
||||
observation =>
|
||||
{
|
||||
if (observation.Boundary == SelfUpdateApplyBoundary.AfterTargetMutation
|
||||
&& string.Equals(
|
||||
observation.Path,
|
||||
canonicalName,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
if (!File.Exists(Path.Combine(targetDirectory, canonicalName)))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The canonical launcher vanished at the apply boundary.");
|
||||
}
|
||||
|
||||
File.WriteAllText(readyPath, Environment.ProcessId.ToString(
|
||||
System.Globalization.CultureInfo.InvariantCulture));
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
});
|
||||
using UpdateSessionBarrier.ExclusiveLease lease = manager.Barrier.AcquireExclusive();
|
||||
_ = await manager.ApplyPendingAsync(targetDirectory);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static async Task<int> StageSelfUpdateAsync(string[] arguments)
|
||||
{
|
||||
if (arguments.Length != 7
|
||||
|| !long.TryParse(
|
||||
arguments[6],
|
||||
System.Globalization.NumberStyles.None,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out long size))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
string dataDirectory = Path.GetFullPath(arguments[0]);
|
||||
using var http = new HttpClient();
|
||||
var manager = new LauncherSelfUpdateManager(Paths(dataDirectory), http);
|
||||
_ = await manager.StageAsync(
|
||||
LauncherVersion.Parse(arguments[2]),
|
||||
arguments[3],
|
||||
new ReleaseArtifact(new Uri(arguments[4]), arguments[5], size),
|
||||
Path.GetFullPath(arguments[1]),
|
||||
progress: null,
|
||||
CancellationToken.None);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static async Task<int> BootstrapProbeAsync(string[] arguments)
|
||||
{
|
||||
if (arguments.Length != 4)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
using var http = new HttpClient();
|
||||
var manager = new LauncherSelfUpdateManager(Paths(arguments[0]), http);
|
||||
SelfUpdateStartupResult result = await LauncherSelfUpdateBootstrap.HandleAsync(
|
||||
["ordinary"],
|
||||
manager,
|
||||
Path.GetFullPath(arguments[1]),
|
||||
Path.GetFullPath(arguments[2]));
|
||||
File.WriteAllText(
|
||||
Path.GetFullPath(arguments[3]),
|
||||
result.ShouldExit ? "exit" : string.Join("\n", result.RemainingArguments));
|
||||
return result.ShouldExit ? 3 : 0;
|
||||
}
|
||||
|
||||
static int CanonicalProbe(string[] arguments)
|
||||
{
|
||||
if (arguments.Length != 1)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
File.WriteAllText(
|
||||
Path.GetFullPath(arguments[0]),
|
||||
Environment.ProcessId.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||
+ "|"
|
||||
+ Path.GetFullPath(
|
||||
Environment.ProcessPath
|
||||
?? throw new InvalidOperationException("Process path is unavailable.")));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static async Task<int> HoldUpdateLeaseAsync(string[] arguments)
|
||||
{
|
||||
if (arguments.Length != 4
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue