save progress
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
// <copyright file="HlcSchedulerDequeueService.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.HybridLogicalClock;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.Queue.Hlc;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of HLC-ordered scheduler job dequeuing.
|
||||
/// </summary>
|
||||
public sealed class HlcSchedulerDequeueService : IHlcSchedulerDequeueService
|
||||
{
|
||||
private readonly ISchedulerLogRepository _logRepository;
|
||||
private readonly ILogger<HlcSchedulerDequeueService> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new HLC scheduler dequeue service.
|
||||
/// </summary>
|
||||
public HlcSchedulerDequeueService(
|
||||
ISchedulerLogRepository logRepository,
|
||||
ILogger<HlcSchedulerDequeueService> logger)
|
||||
{
|
||||
_logRepository = logRepository ?? throw new ArgumentNullException(nameof(logRepository));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SchedulerHlcDequeueResult> DequeueAsync(
|
||||
string tenantId,
|
||||
int limit,
|
||||
string? partitionKey = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(limit);
|
||||
|
||||
var entries = await _logRepository.GetByHlcOrderAsync(
|
||||
tenantId,
|
||||
partitionKey,
|
||||
limit,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Get total count for pagination info
|
||||
var totalCount = await _logRepository.CountByHlcRangeAsync(
|
||||
tenantId,
|
||||
startTHlc: null,
|
||||
endTHlc: null,
|
||||
partitionKey,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Dequeued {Count} of {Total} entries in HLC order. TenantId={TenantId}, PartitionKey={PartitionKey}",
|
||||
entries.Count,
|
||||
totalCount,
|
||||
tenantId,
|
||||
partitionKey ?? "(all)");
|
||||
|
||||
return new SchedulerHlcDequeueResult(
|
||||
entries,
|
||||
totalCount,
|
||||
RangeStartHlc: null,
|
||||
RangeEndHlc: null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SchedulerHlcDequeueResult> DequeueByRangeAsync(
|
||||
string tenantId,
|
||||
HlcTimestamp? startHlc,
|
||||
HlcTimestamp? endHlc,
|
||||
int limit,
|
||||
string? partitionKey = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(limit);
|
||||
|
||||
var startTHlc = startHlc?.ToSortableString();
|
||||
var endTHlc = endHlc?.ToSortableString();
|
||||
|
||||
var entries = await _logRepository.GetByHlcRangeAsync(
|
||||
tenantId,
|
||||
startTHlc,
|
||||
endTHlc,
|
||||
limit,
|
||||
partitionKey,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var totalCount = await _logRepository.CountByHlcRangeAsync(
|
||||
tenantId,
|
||||
startTHlc,
|
||||
endTHlc,
|
||||
partitionKey,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Dequeued {Count} of {Total} entries in HLC range [{Start}, {End}]. TenantId={TenantId}",
|
||||
entries.Count,
|
||||
totalCount,
|
||||
startTHlc ?? "(unbounded)",
|
||||
endTHlc ?? "(unbounded)",
|
||||
tenantId);
|
||||
|
||||
return new SchedulerHlcDequeueResult(
|
||||
entries,
|
||||
totalCount,
|
||||
startHlc,
|
||||
endHlc);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SchedulerHlcDequeueResult> DequeueAfterAsync(
|
||||
string tenantId,
|
||||
HlcTimestamp afterHlc,
|
||||
int limit,
|
||||
string? partitionKey = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(limit);
|
||||
|
||||
var afterTHlc = afterHlc.ToSortableString();
|
||||
|
||||
var entries = await _logRepository.GetAfterHlcAsync(
|
||||
tenantId,
|
||||
afterTHlc,
|
||||
limit,
|
||||
partitionKey,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Count remaining entries after cursor
|
||||
var totalCount = await _logRepository.CountByHlcRangeAsync(
|
||||
tenantId,
|
||||
afterTHlc,
|
||||
endTHlc: null,
|
||||
partitionKey,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Dequeued {Count} entries after HLC {AfterHlc}. TenantId={TenantId}, PartitionKey={PartitionKey}",
|
||||
entries.Count,
|
||||
afterTHlc,
|
||||
tenantId,
|
||||
partitionKey ?? "(all)");
|
||||
|
||||
return new SchedulerHlcDequeueResult(
|
||||
entries,
|
||||
totalCount,
|
||||
afterHlc,
|
||||
RangeEndHlc: null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SchedulerLogEntry?> GetByJobIdAsync(
|
||||
string tenantId,
|
||||
Guid jobId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
|
||||
var entry = await _logRepository.GetByJobIdAsync(jobId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Verify tenant isolation
|
||||
if (entry is not null && !string.Equals(entry.TenantId, tenantId, StringComparison.Ordinal))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Job {JobId} found but belongs to different tenant. RequestedTenant={RequestedTenant}, ActualTenant={ActualTenant}",
|
||||
jobId,
|
||||
tenantId,
|
||||
entry.TenantId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user