feat: implement launcher orchestrator and startup monitoring infrastructure for host lifecycle management

This commit is contained in:
lincube
2026-06-14 12:59:36 +08:00
parent 13895e0f43
commit 2793be68d4
8 changed files with 239 additions and 30 deletions

View File

@@ -87,31 +87,68 @@ internal sealed class DeploymentLocator
var explicitAppRoot = context.ExplicitAppRoot; var explicitAppRoot = context.ExplicitAppRoot;
var devModeConfigIgnored = !context.IsDebugMode && Views.ErrorWindow.CheckDevModeEnabled(); var devModeConfigIgnored = !context.IsDebugMode && Views.ErrorWindow.CheckDevModeEnabled();
Logger.Info($"=== HOST RESOLUTION START ===");
Logger.Info($" AppRoot: {_appRoot}");
Logger.Info($" Executable: {executable}");
Logger.Info($" IsDebugMode: {context.IsDebugMode}");
Logger.Info($" ExplicitAppRoot: {explicitAppRoot ?? "<none>"}");
Logger.Info($" LauncherBaseDirectory: {AppContext.BaseDirectory}");
string? resolvedPath; string? resolvedPath;
string? source; string? source;
if (!string.IsNullOrWhiteSpace(explicitAppRoot)) if (!string.IsNullOrWhiteSpace(explicitAppRoot))
{ {
Logger.Info($"Trying explicit app root: {explicitAppRoot}");
var explicitRoot = Path.GetFullPath(explicitAppRoot); var explicitRoot = Path.GetFullPath(explicitAppRoot);
resolvedPath = TryResolveExplicitAppRoot(explicitRoot, executable, searchedPaths, out source); resolvedPath = TryResolveExplicitAppRoot(explicitRoot, executable, searchedPaths, out source);
} }
else else
{ {
Logger.Info("Trying published or portable host...");
resolvedPath = TryResolvePublishedOrPortableHost(executable, searchedPaths, out source); resolvedPath = TryResolvePublishedOrPortableHost(executable, searchedPaths, out source);
} }
if (resolvedPath is null && context.IsDebugMode) if (resolvedPath is null && context.IsDebugMode)
{ {
Logger.Info("Debug mode: trying debug host paths...");
resolvedPath = TryResolveDebugHost(executable, searchedPaths, out source); resolvedPath = TryResolveDebugHost(executable, searchedPaths, out source);
} }
if (resolvedPath is null) if (resolvedPath is null)
{ {
Logger.Warn("Standard resolution failed, trying legacy fallback...");
resolvedPath = ResolveHostExecutablePathLegacy(); resolvedPath = ResolveHostExecutablePathLegacy();
if (!string.IsNullOrWhiteSpace(resolvedPath)) if (!string.IsNullOrWhiteSpace(resolvedPath))
{ {
searchedPaths.Add(Path.GetFullPath(resolvedPath)); searchedPaths.Add(Path.GetFullPath(resolvedPath));
source = "legacy_fallback"; source = "legacy_fallback";
Logger.Info($"Legacy fallback found: {resolvedPath}");
}
}
Logger.Info($"=== HOST RESOLUTION RESULT ===");
Logger.Info($" Success: {!string.IsNullOrWhiteSpace(resolvedPath)}");
Logger.Info($" ResolvedPath: {resolvedPath ?? "<NOT FOUND>"}");
Logger.Info($" Source: {source ?? "<none>"}");
Logger.Info($" SearchedPaths ({searchedPaths.Count}):");
foreach (var path in searchedPaths.Take(10))
{
Logger.Info($" - {path}");
}
if (searchedPaths.Count > 10)
{
Logger.Info($" ... and {searchedPaths.Count - 10} more");
}
if (string.IsNullOrWhiteSpace(resolvedPath))
{
Logger.Error("CRITICAL: Could not resolve host executable path!");
Console.Error.WriteLine("[CRITICAL] Could not find main application executable!");
Console.Error.WriteLine($"[CRITICAL] Searched {searchedPaths.Count} locations:");
foreach (var path in searchedPaths.Take(5))
{
Console.Error.WriteLine($"[CRITICAL] - {path}");
} }
} }

View File

@@ -19,12 +19,15 @@ internal sealed class AirAppRuntimeBridge
public async Task EnsureStartedAsync() public async Task EnsureStartedAsync()
{ {
Logger.Info($"AIRAPP: Checking if AirApp Runtime is available. AppRoot='{_appRoot}'");
if (await TryGetStatusAsync().ConfigureAwait(false) is not null) if (await TryGetStatusAsync().ConfigureAwait(false) is not null)
{ {
Logger.Info("AirApp Runtime is already available."); Logger.Info("AIRAPP: AirApp Runtime is already available.");
return; return;
} }
Logger.Info("AIRAPP: Starting AirApp Runtime...");
Process? process; Process? process;
try try
{ {
@@ -36,24 +39,28 @@ internal sealed class AirAppRuntimeBridge
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Warn($"AirApp Runtime start request failed. AppRoot='{_appRoot}'; Error='{ex.Message}'."); Logger.Warn($"AIRAPP: AirApp Runtime start request failed. AppRoot='{_appRoot}'; Error='{ex.Message}'");
return; return;
} }
Logger.Info($"AirApp Runtime start requested. Pid={(process is null ? -1 : process.Id)}; AppRoot='{_appRoot}'."); Logger.Info($"AIRAPP: AirApp Runtime start requested. Pid={(process is null ? -1 : process.Id)}; AppRoot='{_appRoot}'.");
for (var attempt = 1; attempt <= ConnectAttempts; attempt++) for (var attempt = 1; attempt <= ConnectAttempts; attempt++)
{ {
Logger.Info($"AIRAPP: Attempt {attempt}/{ConnectAttempts} - Checking IPC connection...");
if (await TryGetStatusAsync().ConfigureAwait(false) is not null) if (await TryGetStatusAsync().ConfigureAwait(false) is not null)
{ {
Logger.Info("AirApp Runtime IPC is ready."); Logger.Info("AIRAPP: AirApp Runtime IPC is ready.");
return; return;
} }
await Task.Delay(TimeSpan.FromMilliseconds(250 * attempt)).ConfigureAwait(false); var delayMs = 250 * attempt;
Logger.Info($"AIRAPP: IPC not ready, waiting {delayMs}ms before retry...");
await Task.Delay(TimeSpan.FromMilliseconds(delayMs)).ConfigureAwait(false);
} }
Logger.Warn("AirApp Runtime did not become ready after pre-start; Host fallback remains available."); Logger.Warn("AIRAPP: AirApp Runtime did not become ready after pre-start; Host fallback remains available.");
} }
public async Task AttachHostAsync(int hostProcessId) public async Task AttachHostAsync(int hostProcessId)
@@ -65,10 +72,15 @@ internal sealed class AirAppRuntimeBridge
try try
{ {
using var cts = new CancellationTokenSource();
using var client = new LanMountainDesktopIpcClient(); using var client = new LanMountainDesktopIpcClient();
await client.ConnectAsync(IpcConstants.AirAppRuntimePipeName).ConfigureAwait(false);
var connectTask = client.ConnectAsync(IpcConstants.AirAppRuntimePipeName);
await connectTask.WaitAsync(TimeSpan.FromSeconds(3), cts.Token).ConfigureAwait(false);
var proxy = client.CreateProxy<IAirAppRuntimeControlService>(); var proxy = client.CreateProxy<IAirAppRuntimeControlService>();
var result = await proxy.AttachHostAsync(hostProcessId).ConfigureAwait(false); var attachTask = proxy.AttachHostAsync(hostProcessId);
var result = await attachTask.WaitAsync(TimeSpan.FromSeconds(3), cts.Token).ConfigureAwait(false);
Logger.Info($"AirApp Runtime host attach completed. Accepted={result.Accepted}; Code='{result.Code}'; HostPid={hostProcessId}."); Logger.Info($"AirApp Runtime host attach completed. Accepted={result.Accepted}; Code='{result.Code}'; HostPid={hostProcessId}.");
} }
catch (Exception ex) catch (Exception ex)
@@ -81,13 +93,29 @@ internal sealed class AirAppRuntimeBridge
{ {
try try
{ {
using var cts = new CancellationTokenSource();
using var client = new LanMountainDesktopIpcClient(); using var client = new LanMountainDesktopIpcClient();
await client.ConnectAsync(IpcConstants.AirAppRuntimePipeName).ConfigureAwait(false);
var connectTask = client.ConnectAsync(IpcConstants.AirAppRuntimePipeName);
await connectTask.WaitAsync(TimeSpan.FromSeconds(2), cts.Token).ConfigureAwait(false);
var proxy = client.CreateProxy<IAirAppRuntimeControlService>(); var proxy = client.CreateProxy<IAirAppRuntimeControlService>();
return await proxy.GetStatusAsync().ConfigureAwait(false); var statusTask = proxy.GetStatusAsync();
return await statusTask.WaitAsync(TimeSpan.FromSeconds(2), cts.Token).ConfigureAwait(false);
} }
catch catch (TimeoutException)
{ {
Logger.Info("AIRAPP: TryGetStatusAsync timed out (2s).");
return null;
}
catch (OperationCanceledException)
{
Logger.Info("AIRAPP: TryGetStatusAsync cancelled.");
return null;
}
catch (Exception ex)
{
Logger.Info($"AIRAPP: TryGetStatusAsync failed: {ex.GetType().Name} - {ex.Message}");
return null; return null;
} }
} }

View File

@@ -144,8 +144,18 @@ internal sealed class LauncherOrchestrator
return; return;
} }
if (!softTimeoutShown)
{
// 用户在软超时前关闭窗口,提示确认
Logger.Info("Splash window was closed manually before soft timeout. Cancelling startup attempt.");
_startupAttemptRegistry.MarkOwnedFailed(lastStage, "User cancelled startup before soft timeout.");
// 取消后续监控
successTcs.TrySetCanceled();
return;
}
_startupAttemptRegistry.MarkOwnedDetachedWaiting(); _startupAttemptRegistry.MarkOwnedDetachedWaiting();
Logger.Warn("Splash window was closed manually. Launcher will continue monitoring the current startup attempt."); Logger.Warn("Splash window was closed manually after soft timeout. Launcher will continue monitoring the current startup attempt in detached mode.");
}; };
splashWindow.Closed += splashClosedHandler; splashWindow.Closed += splashClosedHandler;

View File

@@ -208,13 +208,15 @@ internal sealed class HostLaunchService
private static async Task EnsureAirAppRuntimeStartedAsync(string appRoot, string? dataRoot) private static async Task EnsureAirAppRuntimeStartedAsync(string appRoot, string? dataRoot)
{ {
Logger.Info("HOST LAUNCH: Attempting to pre-start AirApp Runtime...");
try try
{ {
await new AirAppRuntimeBridge(appRoot, dataRoot).EnsureStartedAsync().ConfigureAwait(false); await new AirAppRuntimeBridge(appRoot, dataRoot).EnsureStartedAsync().ConfigureAwait(false);
Logger.Info("HOST LAUNCH: AirApp Runtime pre-start completed.");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Warn($"AirApp Runtime pre-start failed; Host fallback remains available. Error='{ex.Message}'."); Logger.Warn($"HOST LAUNCH: AirApp Runtime pre-start failed; Host fallback remains available. Error='{ex.Message}'");
} }
} }
@@ -249,6 +251,11 @@ internal sealed class HostLaunchService
try try
{ {
Logger.Info($"ATTEMPTING HOST START: Path='{plan.HostPath}'; WorkingDir='{plan.WorkingDirectory}'; Mode='{startMode}'");
Logger.Info($" Arguments: {HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments)}");
Logger.Info($" File exists: {File.Exists(plan.HostPath)}");
Logger.Info($" Working dir exists: {Directory.Exists(plan.WorkingDirectory)}");
var process = Process.Start(startInfo); var process = Process.Start(startInfo);
Logger.Info( Logger.Info(
$"Host launch requested. Mode='{startMode}'; RetryTag='{retryTag ?? "<none>"}'; Path='{plan.HostPath}'; " + $"Host launch requested. Mode='{startMode}'; RetryTag='{retryTag ?? "<none>"}'; Path='{plan.HostPath}'; " +
@@ -257,15 +264,30 @@ internal sealed class HostLaunchService
if (process is null) if (process is null)
{ {
Logger.Error($"CRITICAL: Process.Start returned null! Path='{plan.HostPath}'; Mode='{startMode}'");
Console.Error.WriteLine($"[CRITICAL] Process.Start returned null for path: {plan.HostPath}");
return HostStartAttempt.StartFailed(startMode, "process_start_returned_null", plan); return HostStartAttempt.StartFailed(startMode, "process_start_returned_null", plan);
} }
await Task.Yield(); // 等待一小段时间,检查进程是否立即退出
await Task.Delay(500).ConfigureAwait(false);
if (process.HasExited)
{
Logger.Error($"CRITICAL: Host process exited immediately! ExitCode={process.ExitCode}; Path='{plan.HostPath}'");
Console.Error.WriteLine($"[CRITICAL] Host process exited immediately with code {process.ExitCode}");
return HostStartAttempt.StartFailed(startMode, $"process_exited_immediately_code_{process.ExitCode}", plan);
}
Logger.Info($"Host process started successfully and is running. PID={process.Id}");
return HostStartAttempt.Started(startMode, process, plan); return HostStartAttempt.Started(startMode, process, plan);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error($"Host start failed. Mode='{startMode}'.", ex); Logger.Error($"CRITICAL: Host start exception! Path='{plan.HostPath}'; Mode='{startMode}'; Exception={ex.GetType().Name}; Message='{ex.Message}'", ex);
Console.Error.WriteLine($"[CRITICAL] Host start failed: {ex.Message}");
Console.Error.WriteLine($"[CRITICAL] Path: {plan.HostPath}");
Console.Error.WriteLine($"[CRITICAL] Exception: {ex}");
return HostStartAttempt.StartFailed(startMode, ex.GetType().Name, plan); return HostStartAttempt.StartFailed(startMode, ex.GetType().Name, plan);
} }
} }

View File

@@ -86,7 +86,7 @@ internal sealed class HostStartupMonitor
]).ConfigureAwait(false); ]).ConfigureAwait(false);
if (!connected) if (!connected)
{ {
Logger.Info("Host public IPC is not ready yet. Launcher will keep monitoring the host process and retry."); Logger.Info("Host public IPC is not ready yet after initial connection attempts. Launcher will keep monitoring the host process and retry periodically.");
} }
else else
{ {
@@ -106,6 +106,8 @@ internal sealed class HostStartupMonitor
var nextShellStatusPollAt = DateTimeOffset.UtcNow + StartupTimeoutPolicy.ShellStatusPollInterval; var nextShellStatusPollAt = DateTimeOffset.UtcNow + StartupTimeoutPolicy.ShellStatusPollInterval;
var ipcReconnectAttemptIndex = 0; var ipcReconnectAttemptIndex = 0;
var activationRetryAttempted = false; var activationRetryAttempted = false;
var lastIpcConnectionFailureReported = DateTimeOffset.MinValue;
var ipcConnectionFailureCount = 0;
while (true) while (true)
{ {
@@ -224,6 +226,7 @@ internal sealed class HostStartupMonitor
if (connected) if (connected)
{ {
ipcConnected = true; ipcConnected = true;
Logger.Info($"Host public IPC reconnected successfully after {ipcConnectionFailureCount} failed attempts.");
var shellSuccess = await RefreshShellStatusAsync("Host public IPC reconnected; waiting for desktop shell.") var shellSuccess = await RefreshShellStatusAsync("Host public IPC reconnected; waiting for desktop shell.")
.ConfigureAwait(false); .ConfigureAwait(false);
if (shellSuccess is not null) if (shellSuccess is not null)
@@ -232,6 +235,18 @@ internal sealed class HostStartupMonitor
continue; continue;
} }
} }
else
{
ipcConnectionFailureCount++;
// 每 30 秒报告一次 IPC 连接失败
if ((now - lastIpcConnectionFailureReported).TotalSeconds >= 30)
{
lastIpcConnectionFailureReported = now;
var elapsed = (now - startedAt).TotalSeconds;
Logger.Warn($"Host public IPC connection still unavailable after {elapsed:0}s and {ipcConnectionFailureCount} reconnect attempts. Host process is alive (PID={request.HostProcess.Id}).");
request.Reporter.Report("diagnostic", $"正在等待主应用响应... (已尝试 {ipcConnectionFailureCount} 次)");
}
}
nextReconnectAttemptAt = DateTimeOffset.UtcNow + StartupTimeoutPolicy.IpcReconnectInterval; nextReconnectAttemptAt = DateTimeOffset.UtcNow + StartupTimeoutPolicy.IpcReconnectInterval;
} }
@@ -263,6 +278,16 @@ internal sealed class HostStartupMonitor
nextCheckpointAt = softTimeoutAt; nextCheckpointAt = softTimeoutAt;
} }
if (!ipcConnected && nextReconnectAttemptAt < nextCheckpointAt)
{
nextCheckpointAt = nextReconnectAttemptAt;
}
if (ipcConnected && nextShellStatusPollAt < nextCheckpointAt)
{
nextCheckpointAt = nextShellStatusPollAt;
}
var delay = nextCheckpointAt - now; var delay = nextCheckpointAt - now;
if (delay > TimeSpan.FromSeconds(1)) if (delay > TimeSpan.FromSeconds(1))
{ {
@@ -351,11 +376,11 @@ internal sealed class HostStartupMonitor
if (!connected && !request.HostProcess.HasExited) if (!connected && !request.HostProcess.HasExited)
{ {
request.AttemptRegistry.MarkOwnedWaitingForShell("Host process is still running, but public IPC is not ready yet."); request.AttemptRegistry.MarkOwnedWaitingForShell("Host process is still running, but public IPC is not ready yet.");
request.PublishCoordinatorStatus(true, false, true); request.PublishCoordinatorStatus(true, true, false);
return new Outcome( return new Outcome(
true, false,
"startup_pending", "ipc_connection_failed",
"Host process is still running; Launcher will not start another process while public IPC finishes startup.", $"Host process is still running after {StartupTimeoutPolicy.HardTimeout.TotalSeconds:0} seconds, but public IPC connection could not be established. This may indicate the host is stuck during initialization.",
recoveryActivationAttempted, recoveryActivationAttempted,
request.ComposeLaunchDetails(true, recoveryActivationAttempted)); request.ComposeLaunchDetails(true, recoveryActivationAttempted));
} }

View File

@@ -89,6 +89,14 @@ internal sealed class StartupAttemptRegistry
ExecuteWithLock(() => ExecuteWithLock(() =>
{ {
var existing = LoadUnsafe(); var existing = LoadUnsafe();
// 清理过期的记录
if (existing is not null && IsStaleAttempt(existing))
{
Logger.Info($"Cleaning up stale startup attempt record. AttemptId='{existing.AttemptId}'; State='{existing.State}'; Age={(DateTimeOffset.UtcNow - existing.UpdatedAtUtc).TotalMinutes:0.1}min.");
existing = null;
}
if (existing is not null && IsCoordinatorLive(existing)) if (existing is not null && IsCoordinatorLive(existing))
{ {
active = Clone(existing); active = Clone(existing);
@@ -145,6 +153,34 @@ internal sealed class StartupAttemptRegistry
return reserved is not null; return reserved is not null;
} }
private static bool IsStaleAttempt(StartupAttemptRecord record)
{
// 记录超过 10 分钟且状态为终结或非活跃状态
if (DateTimeOffset.UtcNow - record.UpdatedAtUtc > TimeSpan.FromMinutes(10))
{
return true;
}
// 进程已死且协调器心跳超时
if (record.CoordinatorPid > 0 &&
!TryGetLiveProcess(record.CoordinatorPid, out _) &&
DateTimeOffset.UtcNow - record.HeartbeatAtUtc > TimeSpan.FromMinutes(2))
{
return true;
}
// 主进程已死且协调器已死
if (record.HostPid > 0 &&
!TryGetLiveProcess(record.HostPid, out _) &&
record.CoordinatorPid > 0 &&
!TryGetLiveProcess(record.CoordinatorPid, out _))
{
return true;
}
return false;
}
public StartupAttemptRecord? GetOwnedAttempt() public StartupAttemptRecord? GetOwnedAttempt()
{ {
StartupAttemptRecord? result = null; StartupAttemptRecord? result = null;

View File

@@ -2,22 +2,26 @@ namespace LanMountainDesktop.Launcher.Startup;
internal static class StartupTimeoutPolicy internal static class StartupTimeoutPolicy
{ {
public static readonly TimeSpan SoftTimeout = TimeSpan.FromSeconds(30); public static readonly TimeSpan SoftTimeout = TimeSpan.FromSeconds(45);
public static readonly TimeSpan HardTimeout = TimeSpan.FromSeconds(120); public static readonly TimeSpan HardTimeout = TimeSpan.FromSeconds(180);
/// <summary>Initial Public IPC connect attempt (AOT cold start may be slower).</summary> /// <summary>Initial Public IPC connect attempt (AOT cold start is significantly slower).</summary>
public static readonly TimeSpan InitialIpcConnectTimeout = TimeSpan.FromMilliseconds(1200); public static readonly TimeSpan InitialIpcConnectTimeout = TimeSpan.FromMilliseconds(3000);
/// <summary>Subsequent reconnect attempts use increasing per-try timeouts.</summary> /// <summary>Subsequent reconnect attempts use increasing per-try timeouts.</summary>
public static readonly TimeSpan[] IpcReconnectAttemptTimeouts = public static readonly TimeSpan[] IpcReconnectAttemptTimeouts =
[ [
TimeSpan.FromMilliseconds(800),
TimeSpan.FromMilliseconds(1500), TimeSpan.FromMilliseconds(1500),
TimeSpan.FromMilliseconds(3000), TimeSpan.FromMilliseconds(3000),
TimeSpan.FromMilliseconds(5000) TimeSpan.FromMilliseconds(5000),
TimeSpan.FromMilliseconds(8000),
TimeSpan.FromMilliseconds(10000)
]; ];
public static readonly TimeSpan ExistingHostProbeTimeout = TimeSpan.FromMilliseconds(900); public static readonly TimeSpan ExistingHostProbeTimeout = TimeSpan.FromMilliseconds(1500);
public static readonly TimeSpan ShellStatusPollInterval = TimeSpan.FromSeconds(1); public static readonly TimeSpan ShellStatusPollInterval = TimeSpan.FromSeconds(1);
public static readonly TimeSpan IpcReconnectInterval = TimeSpan.FromSeconds(2); public static readonly TimeSpan IpcReconnectInterval = TimeSpan.FromSeconds(3);
/// <summary>Maximum time to wait for host process exit after it starts (for early-exit detection).</summary>
public static readonly TimeSpan HostEarlyExitWindow = TimeSpan.FromSeconds(5);
} }

View File

@@ -246,6 +246,9 @@ public partial class App : Application
ReportStartupProgress(StartupStage.Initializing, 10, "Initializing application..."); ReportStartupProgress(StartupStage.Initializing, 10, "Initializing application...");
ReportStartupProgress(StartupStage.LoadingSettings, 20, "Loading settings..."); ReportStartupProgress(StartupStage.LoadingSettings, 20, "Loading settings...");
} }
// 启动心跳线程,确保启动器能检测到主应用的活跃状态
_ = StartLauncherHeartbeatAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -253,6 +256,39 @@ public partial class App : Application
} }
} }
private async Task StartLauncherHeartbeatAsync()
{
try
{
// 每 5 秒发送一次心跳,防止启动器认为主应用已卡死
while (!IsShutdownInProgress && _publicIpcHostService is not null)
{
await Task.Delay(TimeSpan.FromSeconds(5));
// 如果还未报告 Ready发送心跳进度
if (!_mainWindowOpened && !IsShutdownInProgress)
{
// 静默心跳,不记录日志
QueueOrSendLauncherProgress(new StartupProgressMessage
{
Stage = StartupStage.Initializing,
ProgressPercent = 15,
Message = "Application is initializing...",
Timestamp = DateTimeOffset.UtcNow
}, logSuccess: false);
}
else
{
break; // 主窗口已打开,停止心跳
}
}
}
catch (Exception ex)
{
AppLogger.Warn("LauncherIpc", $"Heartbeat thread failed: {ex.Message}");
}
}
private void ReportStartupProgress(StartupStage stage, int percent, string message) private void ReportStartupProgress(StartupStage stage, int percent, string message)
{ {
QueueOrSendLauncherProgress(new StartupProgressMessage QueueOrSendLauncherProgress(new StartupProgressMessage
@@ -1824,11 +1860,22 @@ public partial class App : Application
_publicIpcHostService.Start(); _publicIpcHostService.Start();
AppLogger.Info( AppLogger.Info(
"PublicIpc", "PublicIpc",
$"Public IPC host started. PipeName='{IpcConstants.DefaultPipeName}'; Version='{versionInfo.Version}'; Codename='{versionInfo.Codename}'."); $"Public IPC host started successfully. PipeName='{IpcConstants.DefaultPipeName}'; Version='{versionInfo.Version}'; Codename='{versionInfo.Codename}'.");
} }
catch (Exception ex) catch (Exception ex)
{ {
AppLogger.Warn("PublicIpc", "Failed to initialize public IPC host.", ex); AppLogger.Error("PublicIpc", "CRITICAL: Failed to initialize public IPC host. Launcher will not be able to connect to this process.", ex);
// 尝试通过标准错误输出告知启动器
try
{
Console.Error.WriteLine($"[CRITICAL] Public IPC host initialization failed: {ex.Message}");
Console.Error.WriteLine("[CRITICAL] The launcher will not be able to connect to this process.");
}
catch
{
// 忽略控制台写入失败
}
} }
} }