namespace MS.Microservice.Messaging.SelfManaged; /// Renews ownership separately from the business context and cancels work when ownership is lost. /// 续租回调必须使用独立短事务:业务 DbContext 不支持并发访问。取消只是停止工作,数据库更新仍需验证 token。 internal sealed class LeaseGuard : IAsyncDisposable { private readonly CancellationTokenSource _stop = new(); private readonly CancellationTokenSource _work; private readonly Task _renewal; public CancellationToken Token => _work.Token; public bool Lost { get; private set; } public LeaseGuard(Func> renew, TimeSpan lease, TimeSpan timeout, TimeProvider clock, CancellationToken cancellationToken) { _work = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _work.CancelAfter(timeout); _renewal = RenewAsync(renew, TimeSpan.FromTicks(lease.Ticks / 3), clock); } private async Task RenewAsync(Func> renew, TimeSpan interval, TimeProvider clock) { try { while (!_stop.IsCancellationRequested) { await Task.Delay(interval, clock, _stop.Token); if (!await renew(_stop.Token)) { Lost = true; await _work.CancelAsync(); return; } } } catch (OperationCanceledException) when (_stop.IsCancellationRequested) { } catch { Lost = true; await _work.CancelAsync(); } } public async Task StopRenewingAsync() { await _stop.CancelAsync(); await _renewal; } public async ValueTask DisposeAsync() { await StopRenewingAsync(); _stop.Dispose(); _work.Dispose(); } }