feat.airapp剥离启动器

This commit is contained in:
lincube
2026-05-31 19:41:10 +08:00
parent 21e970c5b6
commit c351a8e7f3
78 changed files with 1957 additions and 1250 deletions

View File

@@ -0,0 +1,83 @@
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Launcher.Shell;
internal sealed class AirAppRuntimeBridge
{
private const int ConnectAttempts = 8;
private readonly string _appRoot;
private readonly string? _dataRoot;
public AirAppRuntimeBridge(string appRoot, string? dataRoot)
{
_appRoot = appRoot;
_dataRoot = dataRoot;
}
public async Task EnsureStartedAsync()
{
if (await TryGetStatusAsync().ConfigureAwait(false) is not null)
{
Logger.Info("AirApp Runtime is already available.");
return;
}
var process = AirAppRuntimeProcessStarter.Start(new AirAppRuntimeStartRequest(
_appRoot,
Environment.ProcessId,
0,
_dataRoot));
Logger.Info($"AirApp Runtime start requested. Pid={(process is null ? -1 : process.Id)}; AppRoot='{_appRoot}'.");
for (var attempt = 1; attempt <= ConnectAttempts; attempt++)
{
if (await TryGetStatusAsync().ConfigureAwait(false) is not null)
{
Logger.Info("AirApp Runtime IPC is ready.");
return;
}
await Task.Delay(TimeSpan.FromMilliseconds(250 * attempt)).ConfigureAwait(false);
}
Logger.Warn("AirApp Runtime did not become ready after pre-start; Host fallback remains available.");
}
public async Task AttachHostAsync(int hostProcessId)
{
if (hostProcessId <= 0)
{
return;
}
try
{
using var client = new LanMountainDesktopIpcClient();
await client.ConnectAsync(IpcConstants.AirAppRuntimePipeName).ConfigureAwait(false);
var proxy = client.CreateProxy<IAirAppRuntimeControlService>();
var result = await proxy.AttachHostAsync(hostProcessId).ConfigureAwait(false);
Logger.Info($"AirApp Runtime host attach completed. Accepted={result.Accepted}; Code='{result.Code}'; HostPid={hostProcessId}.");
}
catch (Exception ex)
{
Logger.Warn($"Failed to attach Host to AirApp Runtime: {ex.Message}");
}
}
private static async Task<AirAppRuntimeStatus?> TryGetStatusAsync()
{
try
{
using var client = new LanMountainDesktopIpcClient();
await client.ConnectAsync(IpcConstants.AirAppRuntimePipeName).ConfigureAwait(false);
var proxy = client.CreateProxy<IAirAppRuntimeControlService>();
return await proxy.GetStatusAsync().ConfigureAwait(false);
}
catch
{
return null;
}
}
}

View File

@@ -1,6 +1,4 @@
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Views;
namespace LanMountainDesktop.Launcher.Shell.EntryHandlers;
@@ -30,52 +28,3 @@ internal static class LaunchEntryHandler
SplashWindow splashWindow) =>
LauncherCompositionRoot.RunOrchestratorWithSplashAsync(desktop, context, splashWindow);
}
internal static class AirAppBrokerEntryHandler
{
public static async Task RunAsync(IClassicDesktopStyleApplicationLifetime desktop, CommandContext context)
{
var appRoot = Commands.ResolveAppRoot(context);
var requesterPid = context.GetIntOption("requester-pid", 0);
var dataLocationResolver = new DataLocationResolver(appRoot);
Logger.Info($"Air APP broker starting. AppRoot='{appRoot}'; RequesterPid={requesterPid}.");
using var airAppIpcHost = new LauncherAirAppLifecycleIpcHost(
new LauncherAirAppLifecycleService(
new AirAppProcessStarter(
new AirAppHostLocator(),
() => appRoot,
() => null,
() => dataLocationResolver.ResolveDataRoot())));
airAppIpcHost.Start();
while (ShouldKeepAlive(requesterPid, airAppIpcHost.LifecycleService))
{
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
}
Logger.Info("Air APP broker exiting.");
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0), DispatcherPriority.Background);
}
internal static bool ShouldKeepAirAppBrokerAlive(int requesterPid, LauncherAirAppLifecycleService lifecycleService)
{
if (requesterPid <= 0)
{
return lifecycleService.HasLiveAirApps();
}
try
{
using var process = System.Diagnostics.Process.GetProcessById(requesterPid);
return !process.HasExited || lifecycleService.HasLiveAirApps();
}
catch
{
return lifecycleService.HasLiveAirApps();
}
}
private static bool ShouldKeepAlive(int requesterPid, LauncherAirAppLifecycleService lifecycleService) =>
ShouldKeepAirAppBrokerAlive(requesterPid, lifecycleService);
}

View File

@@ -24,6 +24,9 @@ internal static class LauncherGuiCoordinator
var startupAttemptRegistry = new StartupAttemptRegistry();
var coordinatorPipeName = LauncherCoordinatorIpcServer.CreatePipeName();
var successPolicy = LauncherOrchestrator.ResolveSuccessPolicyKey(context);
var airAppRuntimeBridge = new AirAppRuntimeBridge(appRoot, dataLocationResolver.ResolveDataRoot());
await airAppRuntimeBridge.EnsureStartedAsync().ConfigureAwait(false);
if (!startupAttemptRegistry.TryReserveCoordinator(
context.LaunchSource,
successPolicy,
@@ -44,15 +47,6 @@ internal static class LauncherGuiCoordinator
return;
}
using var airAppIpcHost = new LauncherAirAppLifecycleIpcHost(
new LauncherAirAppLifecycleService(
new AirAppProcessStarter(
new AirAppHostLocator(),
() => appRoot,
() => null,
() => dataLocationResolver.ResolveDataRoot())));
airAppIpcHost.Start();
using var coordinatorServer = new LauncherCoordinatorIpcServer(
coordinatorPipeName,
BuildCoordinatorStatusFromAttempt(reservedAttempt),
@@ -129,7 +123,8 @@ internal static class LauncherGuiCoordinator
if (result.Success)
{
var hostPid = ResolveManagedHostPid(result, startupAttemptRegistry.GetOwnedAttempt()?.HostPid ?? 0);
await WaitForManagedProcessesToExitAsync(hostPid, airAppIpcHost.LifecycleService).ConfigureAwait(false);
await airAppRuntimeBridge.AttachHostAsync(hostPid).ConfigureAwait(false);
await WaitForHostProcessToExitAsync(hostPid).ConfigureAwait(false);
}
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background);
@@ -173,17 +168,15 @@ internal static class LauncherGuiCoordinator
return fallbackHostPid;
}
private static async Task WaitForManagedProcessesToExitAsync(
int hostPid,
LauncherAirAppLifecycleService airAppLifecycleService)
private static async Task WaitForHostProcessToExitAsync(int hostPid)
{
Logger.Info($"Launcher entering managed background lifetime. HostPid={hostPid}.");
while (TryGetLiveProcess(hostPid) || airAppLifecycleService.HasLiveAirApps())
Logger.Info($"Launcher entering host background lifetime. HostPid={hostPid}.");
while (TryGetLiveProcess(hostPid))
{
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
}
Logger.Info("Launcher managed background lifetime completed; no host or Air APP process remains.");
Logger.Info("Launcher host background lifetime completed; host process is gone.");
}
private static async Task<LauncherResult> AttachToExistingCoordinatorAsync(