fix: stabilize task pause and auto sync scheduling

This commit is contained in:
meoacgx committed 2026-03-10 00:25:48 +08:00
1 parent 23d88acbd3
commit b134c335b2
2 files changed
+91 -8

No files matched your search

@@ -16,6 +16,7 @@ public sealed class AccountDataAutoSyncBackgroundService : BackgroundService
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _environment;
private readonly ILogger<AccountDataAutoSyncBackgroundService> _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<string>();
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
@@ -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<bool> 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)