2024-10-31 08:09:05 +00:00
|
|
|
using Elsa.Common;
|
|
|
|
|
using Elsa.Common.Entities;
|
|
|
|
|
using Elsa.Common.Models;
|
|
|
|
|
using Elsa.Workflows.Runtime.Filters;
|
2024-11-26 19:37:41 +00:00
|
|
|
using Elsa.Workflows.Runtime.Options;
|
2024-10-31 08:09:05 +00:00
|
|
|
using Elsa.Workflows.Runtime.OrderDefinitions;
|
|
|
|
|
using JetBrains.Annotations;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2024-11-26 19:37:41 +00:00
|
|
|
using Microsoft.Extensions.Options;
|
2024-10-31 08:09:05 +00:00
|
|
|
|
|
|
|
|
namespace Elsa.Workflows.Runtime;
|
|
|
|
|
|
|
|
|
|
[UsedImplicitly]
|
2026-05-21 21:43:36 +00:00
|
|
|
public class DefaultBookmarkQueuePurger(
|
|
|
|
|
IBookmarkQueueStore store,
|
|
|
|
|
IBookmarkQueueDeadLetterStore deadLetterStore,
|
|
|
|
|
IBookmarkQueueDeadLetterManager deadLetterManager,
|
|
|
|
|
ISystemClock systemClock,
|
|
|
|
|
IOptions<BookmarkQueuePurgeOptions> options,
|
|
|
|
|
ILogger<DefaultBookmarkQueuePurger> logger) : IBookmarkQueuePurger
|
2024-10-31 08:09:05 +00:00
|
|
|
{
|
|
|
|
|
public async Task PurgeAsync(CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
var now = systemClock.UtcNow;
|
2024-11-26 19:37:41 +00:00
|
|
|
var thresholdDate = now - options.Value.Ttl;
|
2024-10-31 08:09:05 +00:00
|
|
|
|
2025-02-26 20:10:41 +00:00
|
|
|
logger.LogDebug("Purging bookmark queue items older than {ThresholdDate}.", thresholdDate);
|
2024-10-31 08:09:05 +00:00
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
2026-05-21 21:43:36 +00:00
|
|
|
var pageArgs = PageArgs.FromPage(0, options.Value.BatchSize);
|
2024-10-31 08:09:05 +00:00
|
|
|
var filter = new BookmarkQueueFilter
|
|
|
|
|
{
|
|
|
|
|
CreatedAtLessThan = thresholdDate
|
|
|
|
|
};
|
|
|
|
|
var order = new BookmarkQueueItemOrder<DateTimeOffset>(x => x.CreatedAt, OrderDirection.Ascending);
|
|
|
|
|
var page = await store.PageAsync(pageArgs, filter, order, cancellationToken);
|
2026-05-21 21:43:36 +00:00
|
|
|
var items = page.Items.ToList();
|
2024-10-31 08:09:05 +00:00
|
|
|
|
|
|
|
|
if (items.Count == 0)
|
|
|
|
|
break;
|
|
|
|
|
|
2026-05-21 21:43:36 +00:00
|
|
|
var itemIds = items.Select(x => x.Id).ToList();
|
|
|
|
|
var activeItemIds = (await store.FindManyAsync(new BookmarkQueueFilter
|
2024-10-31 08:09:05 +00:00
|
|
|
{
|
2026-05-21 21:43:36 +00:00
|
|
|
Ids = itemIds
|
|
|
|
|
}, cancellationToken)).Select(x => x.Id).ToList();
|
|
|
|
|
var deadLetters = (await deadLetterManager.DeadLetterManyAsync(items, "Expired", cancellationToken: cancellationToken)).ToList();
|
|
|
|
|
var deletedCount = activeItemIds.Count == 0
|
|
|
|
|
? 0
|
|
|
|
|
: await store.DeleteAsync(new BookmarkQueueFilter
|
|
|
|
|
{
|
|
|
|
|
Ids = activeItemIds
|
|
|
|
|
}, cancellationToken);
|
|
|
|
|
var alreadyRemovedItemIds = itemIds.Except(activeItemIds).ToHashSet();
|
2024-10-31 08:09:05 +00:00
|
|
|
|
2026-05-21 21:43:36 +00:00
|
|
|
if (alreadyRemovedItemIds.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var deadLetter in deadLetters.Where(x => x.CanReplay && x.Reason == "Expired" && alreadyRemovedItemIds.Contains(x.OriginalQueueItemId)))
|
|
|
|
|
{
|
|
|
|
|
deadLetter.CanReplay = false;
|
|
|
|
|
await deadLetterStore.SaveAsync(deadLetter, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-31 08:09:05 +00:00
|
|
|
|
2026-05-21 21:43:36 +00:00
|
|
|
logger.LogInformation(
|
|
|
|
|
"Recorded {DeadLetterCount} expired bookmark queue dead-letter items and deleted {DeletedCount} active queue items.",
|
|
|
|
|
deadLetters.Count,
|
|
|
|
|
deletedCount);
|
2024-10-31 08:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-21 21:43:36 +00:00
|
|
|
await PurgeDeadLettersAsync(now, cancellationToken);
|
|
|
|
|
|
2025-02-26 20:10:41 +00:00
|
|
|
logger.LogDebug("Finished purging bookmark queue items.");
|
2024-10-31 08:09:05 +00:00
|
|
|
}
|
2026-05-21 21:43:36 +00:00
|
|
|
|
|
|
|
|
private async Task PurgeDeadLettersAsync(DateTimeOffset now, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var thresholdDate = now - options.Value.DeadLetterTtl;
|
|
|
|
|
|
|
|
|
|
logger.LogDebug("Purging bookmark queue dead-letter items older than {ThresholdDate}.", thresholdDate);
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var pageArgs = PageArgs.FromPage(0, options.Value.BatchSize);
|
|
|
|
|
var filter = new BookmarkQueueDeadLetterFilter
|
|
|
|
|
{
|
|
|
|
|
DeadLetteredAtLessThan = thresholdDate
|
|
|
|
|
};
|
|
|
|
|
var order = new BookmarkQueueDeadLetterItemOrder<DateTimeOffset>(x => x.DeadLetteredAt, OrderDirection.Ascending);
|
|
|
|
|
var page = await deadLetterStore.PageAsync(pageArgs, filter, order, cancellationToken);
|
|
|
|
|
var items = page.Items;
|
|
|
|
|
|
|
|
|
|
if (items.Count == 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
await deadLetterStore.DeleteAsync(new BookmarkQueueDeadLetterFilter
|
|
|
|
|
{
|
|
|
|
|
Ids = items.Select(x => x.Id).ToList()
|
|
|
|
|
}, cancellationToken);
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("Purged {Count} bookmark queue dead-letter items.", items.Count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|