Skip to content
Closed
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
41 changes: 31 additions & 10 deletions Ink Canvas/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@
LogHelper.WriteLogToFile("启动画面对象创建成功,准备显示...");
_splashScreen.Show();
_isSplashScreenShown = true;
splashScreenStartTime = DateTime.Now;
Interlocked.Exchange(ref splashScreenStartTimeTicksUtc, DateTime.UtcNow.Ticks);
LogHelper.WriteLogToFile("启动画面已显示");
}
catch (Exception ex)
Expand Down Expand Up @@ -694,7 +694,7 @@
async void App_Startup(object sender, StartupEventArgs e)
{
appStartTime = DateTime.Now;
appStartupStartTime = DateTime.Now;
Interlocked.Exchange(ref appStartupStartTimeTicksUtc, DateTime.UtcNow.Ticks);

// 根据设置决定是否显示启动画面
if (ShouldShowSplashScreen() && !IsLaunchByFileOrUri(e.Args))
Expand Down Expand Up @@ -831,7 +831,7 @@
LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
}

Task.Run(async () =>

Check warning on line 834 in Ink Canvas/App.xaml.cs

View workflow job for this annotation

GitHub Actions / Build & Package

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
{
try
{
Expand Down Expand Up @@ -1078,7 +1078,12 @@
mainWindow.Loaded += (s, args) =>
{
isStartupComplete = true;
startupCompleteHeartbeat = DateTime.Now;
var startupCompleteHeartbeat = DateTime.UtcNow;
Interlocked.Exchange(ref startupCompleteHeartbeatTicksUtc, startupCompleteHeartbeat.Ticks);
Comment on lines 1080 to +1082

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Record startup heartbeat before marking startup complete

The Loaded handler publishes isStartupComplete before updating startupCompleteHeartbeatTicksUtc/lastHeartbeatTicksUtc, but watchdogTimer reads these values from another thread. In the small window after isStartupComplete becomes true, the watchdog can still see an old heartbeat and a MinValue startup-complete timestamp, which bypasses the new grace-period check and can trigger the same immediate post-startup restart this change is trying to prevent. Write the timestamps first and only then mark startup complete (or enforce ordering with synchronization).

Useful? React with 👍 / 👎.

Interlocked.Exchange(ref lastHeartbeatTicksUtc, startupCompleteHeartbeat.Ticks);

var splashScreenStartTime = ReadUtcTicks(ref splashScreenStartTimeTicksUtc);
var appStartupStartTime = ReadUtcTicks(ref appStartupStartTimeTicksUtc);
if (_isSplashScreenShown && splashScreenStartTime != DateTime.MinValue)
{
LogHelper.WriteLogToFile($"启动完成心跳已记录,启动画面显示时长: {(startupCompleteHeartbeat - splashScreenStartTime).TotalSeconds:F2}秒");
Expand Down Expand Up @@ -1117,7 +1122,7 @@
{
LogHelper.WriteLogToFile($"App | 处理启动URI参数: {startupUriArg}", LogHelper.LogType.Event);
// 延迟一点执行,确保窗口初始化完成
Task.Delay(1000).ContinueWith(_ =>

Check warning on line 1125 in Ink Canvas/App.xaml.cs

View workflow job for this annotation

GitHub Actions / Build & Package

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
{
mainWindow.Dispatcher.Invoke(() =>
{
Expand Down Expand Up @@ -1189,12 +1194,19 @@

// 心跳相关
private static DispatcherTimer heartbeatTimer;
private static DateTime lastHeartbeat = DateTime.Now;
private static long lastHeartbeatTicksUtc = DateTime.UtcNow.Ticks;
private static Timer watchdogTimer;
private static bool isStartupComplete = false;
private static DateTime startupCompleteHeartbeat = DateTime.MinValue;
private static DateTime splashScreenStartTime = DateTime.MinValue;
private static DateTime appStartupStartTime = DateTime.MinValue;
private static long startupCompleteHeartbeatTicksUtc = DateTime.MinValue.Ticks;
private static long splashScreenStartTimeTicksUtc = DateTime.MinValue.Ticks;
private static long appStartupStartTimeTicksUtc = DateTime.MinValue.Ticks;
private static readonly TimeSpan StartupCompleteGracePeriod = TimeSpan.FromSeconds(30);

private static DateTime ReadUtcTicks(ref long ticks)
{
long value = Interlocked.Read(ref ticks);
return value == DateTime.MinValue.Ticks ? DateTime.MinValue : new DateTime(value, DateTimeKind.Utc);
}

/// <summary>
/// 启动并管理应用的心跳与守护检查定时器,监测启动阶段与主线程是否无响应,并在符合配置的情况下尝试静默重启应用。
Expand All @@ -1213,20 +1225,24 @@
{
Interval = TimeSpan.FromSeconds(1)
};
heartbeatTimer.Tick += (_, __) => lastHeartbeat = DateTime.Now;
heartbeatTimer.Tick += (_, __) => Interlocked.Exchange(ref lastHeartbeatTicksUtc, DateTime.UtcNow.Ticks);
heartbeatTimer.Start();

watchdogTimer = new Timer(_ =>
{
if (IsOobeShowing)
return;

DateTime now = DateTime.UtcNow;
DateTime appStartupStartTime = ReadUtcTicks(ref appStartupStartTimeTicksUtc);

if (!isStartupComplete && appStartupStartTime != DateTime.MinValue)
{
DateTime splashScreenStartTime = ReadUtcTicks(ref splashScreenStartTimeTicksUtc);
DateTime startTime = _isSplashScreenShown && splashScreenStartTime != DateTime.MinValue
? splashScreenStartTime
: appStartupStartTime;
TimeSpan elapsedSinceStart = DateTime.Now - startTime;
TimeSpan elapsedSinceStart = now - startTime;
if (elapsedSinceStart.TotalMinutes >= 2)
{
string timeType = _isSplashScreenShown ? "启动画面已显示" : "应用启动开始";
Expand All @@ -1252,8 +1268,13 @@
return;
}
}
if (isStartupComplete && (DateTime.Now - lastHeartbeat).TotalSeconds > 10)
DateTime lastHeartbeat = ReadUtcTicks(ref lastHeartbeatTicksUtc);
if (isStartupComplete && (now - lastHeartbeat).TotalSeconds > 10)
{
DateTime startupCompleteHeartbeat = ReadUtcTicks(ref startupCompleteHeartbeatTicksUtc);
if (startupCompleteHeartbeat != DateTime.MinValue && (now - startupCompleteHeartbeat) < StartupCompleteGracePeriod)
return;

LogHelper.NewLog("检测到主线程无响应,自动重启。");
SyncCrashActionFromSettings();
if (CrashAction == CrashActionType.SilentRestart)
Expand Down
Loading