Split MAX bot loops and make chats cache-first

This commit is contained in:
sevenhill
2026-07-05 23:20:20 +03:00
parent 0106130744
commit 3bc6456ed7
7 changed files with 157 additions and 16 deletions
@@ -59,7 +59,12 @@ public sealed class ChatsController(
}
[HttpGet("{chatId:guid}/messages")]
public async Task<ActionResult<IReadOnlyList<MessageDto>>> GetMessages(Guid chatId, int take = 80, DateTimeOffset? before = null, CancellationToken cancellationToken = default)
public async Task<ActionResult<IReadOnlyList<MessageDto>>> GetMessages(
Guid chatId,
int take = 80,
DateTimeOffset? before = null,
bool sync = false,
CancellationToken cancellationToken = default)
{
var chat = await db.Chats.FirstOrDefaultAsync(x => x.Id == chatId && x.DeletedAt == null, cancellationToken);
if (chat is null)
@@ -67,7 +72,7 @@ public sealed class ChatsController(
return NotFound();
}
if (before is null && !string.IsNullOrWhiteSpace(chat.ExternalId))
if (sync && before is null && !string.IsNullOrWhiteSpace(chat.ExternalId))
{
await syncService.SyncChatHistoryAsync(chat.ExternalId, chat.WebUrl, cancellationToken);
db.ChangeTracker.Clear();
+8 -3
View File
@@ -21,8 +21,12 @@ public sealed class MaxOutboxService(
public async Task<int> ProcessOnceAsync(CancellationToken cancellationToken)
{
var processed = 0;
var processed = await ProcessPendingMessagesOnceAsync(cancellationToken);
return processed + await ProcessPendingChatActionsOnceAsync(cancellationToken);
}
public async Task<int> ProcessPendingMessagesOnceAsync(CancellationToken cancellationToken)
{
var candidates = await db.Messages
.Include(message => message.Chat)
.Include(message => message.Attachments)
@@ -41,6 +45,7 @@ public sealed class MaxOutboxService(
.Take(10)
.ToArray();
var processed = 0;
foreach (var message in pending)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -50,10 +55,10 @@ public sealed class MaxOutboxService(
}
}
return processed + await ProcessPendingChatActionsAsync(cancellationToken);
return processed;
}
private async Task<int> ProcessPendingChatActionsAsync(CancellationToken cancellationToken)
public async Task<int> ProcessPendingChatActionsOnceAsync(CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
var pending = await db.Chats
+22 -2
View File
@@ -13,6 +13,26 @@ public sealed class MaxOutboxWorker(
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var delay = TimeSpan.FromSeconds(Math.Max(1, _options.MaxOutboxPollIntervalSeconds));
await Task.WhenAll(
RunOutboxLoopAsync(
"message outbox",
delay,
(outbox, cancellationToken) => outbox.ProcessPendingMessagesOnceAsync(cancellationToken),
stoppingToken),
RunOutboxLoopAsync(
"chat command outbox",
delay,
(outbox, cancellationToken) => outbox.ProcessPendingChatActionsOnceAsync(cancellationToken),
stoppingToken));
}
private async Task RunOutboxLoopAsync(
string workerName,
TimeSpan delay,
Func<MaxOutboxService, CancellationToken, Task<int>> process,
CancellationToken stoppingToken)
{
using var timer = new PeriodicTimer(delay);
while (!stoppingToken.IsCancellationRequested)
@@ -30,11 +50,11 @@ public sealed class MaxOutboxWorker(
{
await using var scope = scopeFactory.CreateAsyncScope();
var outbox = scope.ServiceProvider.GetRequiredService<MaxOutboxService>();
await outbox.ProcessOnceAsync(stoppingToken);
await process(outbox, stoppingToken);
}
catch (Exception ex)
{
logger.LogWarning(ex, "QMAX outbox tick failed.");
logger.LogWarning(ex, "QMAX {WorkerName} tick failed.", workerName);
}
}
}