feat: daily node restart controlled from the admin, connect timings as a default line task [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful

Agents and Probes exit in order at a configured time (default 04:30 UTC+8, switch and time zone in the admin settings) and the service manager starts them again; a local restart_at or MOSHIRO_RESTART_AT wins. The connect kind (TCP connect and TLS handshake timings) is now a default continuous line template, with a section on the line page.
This commit is contained in:
justme committed 2026-10-07 14:56:50 +08:00
1 parent 388619b8b6
commit 9bcef56497
37 files changed
+928 -101

No files matched your search

+35 -1
View File
@@ -126,7 +126,8 @@ public static class PlatformCheck
/// <summary>处理 Ctrl+C 和 SIGTERM:取消令牌,让各个模块有序退出。</summary>
public static class ServiceShell
{
public static async Task RunAsync(Func<CancellationToken, Task> run, INodeLog log)
/// <param name="restart">每天定时有序退出一次(见 <see cref="RestartController"/>),重新拉起由服务管理器负责。</param>
public static async Task RunAsync(Func<CancellationToken, Task> run, INodeLog log, RestartController? restart = null, TimeProvider? time = null)
{
using var cts = new CancellationTokenSource();
using PosixSignalRegistration term = PosixSignalRegistration.Create(PosixSignal.SIGTERM, context =>
@@ -156,6 +157,7 @@ public static class ServiceShell
cts.Cancel();
});
Task trimmer = TrimHeapAsync(cts.Token);
Task scheduled = restart is null ? Task.CompletedTask : WaitForRestartAsync(restart, time ?? TimeProvider.System, log, cts);
try
{
await run(cts.Token);
@@ -168,6 +170,38 @@ public static class ServiceShell
{
await cts.CancelAsync();
await trimmer;
await scheduled;
}
}
private static async Task WaitForRestartAsync(RestartController restart, TimeProvider time, INodeLog log, CancellationTokenSource cts)
{
while (!cts.IsCancellationRequested)
{
// 先取令牌、再取设置:中间被改动的话令牌会被取消,下一轮重新排
CancellationToken changed = restart.ChangeToken;
RestartSchedule? schedule = restart.Current;
using CancellationTokenSource wait = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, changed);
try
{
if (schedule is null)
{
await Task.Delay(Timeout.Infinite, wait.Token);
continue;
}
DateTimeOffset next = schedule.NextAfter(time.GetUtcNow() + RestartSchedule.MinimumUptime);
log.Info("restart scheduled", ("at", next.ToString("O", System.Globalization.CultureInfo.InvariantCulture)), ("source", restart.IsLocal ? "local" : "panel"));
await Task.Delay(next - time.GetUtcNow(), time, wait.Token);
}
catch (OperationCanceledException)
{
continue; // 进程在退出,或者设置被改了:循环条件和下一轮会处理
}
log.Info("stopping", ("result", "scheduled_restart"));
await cts.CancelAsync();
return;
}
}