From b134c335b248d09fc18e939d9040d960fc61951c Mon Sep 17 00:00:00 2001 From: meoacgx Date: Tue, 10 Mar 2026 00:25:48 +0800 Subject: [PATCH] fix: stabilize task pause and auto sync scheduling --- .../AccountDataAutoSyncBackgroundService.cs | 61 +++++++++++++++++-- .../Services/UserChatActiveTaskHandler.cs | 38 +++++++++++- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs b/src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs index 0785b60..2d37694 100644 --- a/src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs +++ b/src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs @@ -16,6 +16,7 @@ public sealed class AccountDataAutoSyncBackgroundService : BackgroundService private readonly IConfiguration _configuration; private readonly IWebHostEnvironment _environment; private readonly ILogger _logger; + private DateTime? _lastAutoSyncAtUtc; public AccountDataAutoSyncBackgroundService( IServiceScopeFactory scopeFactory, @@ -48,7 +49,7 @@ public sealed class AccountDataAutoSyncBackgroundService : BackgroundService } var nowUtc = DateTime.UtcNow; - var lastUtc = ReadLastAutoSyncAtUtcFromConfig(); + var lastUtc = GetLastAutoSyncAtUtc(); if (lastUtc.HasValue) { var nextUtc = lastUtc.Value.AddHours(hours); @@ -86,7 +87,9 @@ public sealed class AccountDataAutoSyncBackgroundService : BackgroundService summary.AccountFailures.Count); // 记录“上次自动同步时间”,用于容器重启后继续按间隔调度,避免“重启即跑一轮”导致限流。 - await PersistLastAutoSyncAtUtcAsync(DateTime.UtcNow, stoppingToken); + var completedAtUtc = DateTime.UtcNow; + _lastAutoSyncAtUtc = completedAtUtc; + await PersistLastAutoSyncAtUtcAsync(completedAtUtc, stoppingToken); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { @@ -107,13 +110,43 @@ public sealed class AccountDataAutoSyncBackgroundService : BackgroundService } } - private DateTime? ReadLastAutoSyncAtUtcFromConfig() + private DateTime? GetLastAutoSyncAtUtc() { - var raw = (_configuration["Sync:LastAutoSyncAtUtc"] ?? "").Trim(); - if (string.IsNullOrWhiteSpace(raw)) + var configValue = ReadUtcValue(_configuration["Sync:LastAutoSyncAtUtc"]); + var fileValue = ReadLastAutoSyncAtUtcFromLocalConfig(); + return MaxUtc(_lastAutoSyncAtUtc, fileValue, configValue); + } + + private DateTime? ReadLastAutoSyncAtUtcFromLocalConfig() + { + try + { + var path = LocalConfigFile.ResolvePath(_configuration, _environment); + if (!File.Exists(path)) + return null; + + var json = File.ReadAllText(path); + if (string.IsNullOrWhiteSpace(json)) + return null; + + var root = JsonNode.Parse(json)?.AsObject(); + var raw = root?["Sync"]?["LastAutoSyncAtUtc"]?.GetValue(); + return ReadUtcValue(raw); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Failed to read Sync:LastAutoSyncAtUtc from local config (ignored)"); + return null; + } + } + + private static DateTime? ReadUtcValue(string? raw) + { + var value = (raw ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(value)) return null; - if (DateTime.TryParse(raw, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed)) + if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed)) { if (parsed.Kind == DateTimeKind.Unspecified) parsed = DateTime.SpecifyKind(parsed, DateTimeKind.Utc); @@ -123,6 +156,22 @@ public sealed class AccountDataAutoSyncBackgroundService : BackgroundService return null; } + private static DateTime? MaxUtc(params DateTime?[] values) + { + DateTime? max = null; + foreach (var value in values) + { + if (!value.HasValue) + continue; + + var utc = value.Value.Kind == DateTimeKind.Utc ? value.Value : value.Value.ToUniversalTime(); + if (!max.HasValue || utc > max.Value) + max = utc; + } + + return max; + } + private async Task PersistLastAutoSyncAtUtcAsync(DateTime utcNow, CancellationToken cancellationToken) { try diff --git a/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs b/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs index 53ee9fd..12532fe 100644 --- a/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs +++ b/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs @@ -24,6 +24,8 @@ public sealed class UserChatActiveTaskHandler : IModuleTaskHandler var config = DeserializeConfig(host.Config); ValidateAndNormalizeConfig(config); + config.Canceled = false; + config.Error = null; var selectedCategoryIds = NormalizeSelectedCategoryIds(config).ToHashSet(); var allAccounts = (await accountManagement.GetAllAccountsAsync()) @@ -116,6 +118,12 @@ public sealed class UserChatActiveTaskHandler : IModuleTaskHandler var messageIdx = SelectIndex(config.MessageMode, config.Dictionary.Count, ref messageQueueIndex); var text = config.Dictionary[messageIdx]; + if (!await host.IsStillRunningAsync(cancellationToken)) + { + config.Canceled = true; + break; + } + var send = await accountTools.SendMessageToResolvedChatAsync( accountSlot.Account.Id, targetSlot.Resolved, @@ -155,8 +163,11 @@ public sealed class UserChatActiveTaskHandler : IModuleTaskHandler break; var delayMs = NextDelayMilliseconds(config.DelayMinMs, config.DelayMaxMs); - if (delayMs > 0) - await Task.Delay(delayMs, cancellationToken); + if (delayMs > 0 && !await DelayWithPauseCheckAsync(host, delayMs, cancellationToken)) + { + config.Canceled = true; + break; + } } } catch (Exception ex) @@ -298,6 +309,29 @@ public sealed class UserChatActiveTaskHandler : IModuleTaskHandler return Random.Shared.Next(minMs, maxMs + 1); } + private static async Task DelayWithPauseCheckAsync( + IModuleTaskExecutionHost host, + int delayMs, + CancellationToken cancellationToken) + { + if (delayMs <= 0) + return true; + + var remaining = delayMs; + while (remaining > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!await host.IsStillRunningAsync(cancellationToken)) + return false; + + var chunk = Math.Min(remaining, 1000); + await Task.Delay(chunk, cancellationToken); + remaining -= chunk; + } + + return true; + } + private static bool ShouldPersistProgress(int completed, bool hadFailureThisRound, DateTime lastPersistAt) { if (completed <= 1)