async Task OnTimeoutAsync(BitcoinAddress address, CancellationToken cancellationToken) { await this.semaphore.WaitAsync(); try { if (!this.watchings.Remove(address, out var watching)) { // Already completed. return; } var rule = watching.Rule; await this.rules.SetTimedOutAsync(rule.Id, CancellationToken.None); await this.addressPool.ReleaseAddressAsync(rule.AddressReservation.Id, CancellationToken.None); // Mark all watches that was created by this rule as timed out and calculate total received amount. var watches = await this.watches.TransitionToTimedOutAsync(rule, CancellationToken.None); var confirmed = watches.Where(p => p.Value > 0).ToDictionary(p => p.Key, p => p.Value); var amount = SumChanges(confirmed, rule.TargetConfirmation); // Invoke callback. var result = new CallbackData() { Received = amount, }; await InvokeCallbackAsync(rule.Callback, rule.TimeoutStatus, result, CancellationToken.None); } catch (Exception ex) // lgtm[cs/catch-of-all-exceptions] { this.logger.LogError(ex, "Failed to trigger timeout for address {Address}", address); } finally { this.semaphore.Release(); } }
async Task <bool> IConfirmationWatcherHandler <Rule, Watch, Confirmation> .ConfirmationUpdateAsync( Confirmation confirm, int count, ConfirmationType type, CancellationToken cancellationToken) { // All watches MUST come from the same rule; otherwise that mean there is a bug somewhere. var rule = confirm.Watches.Select(w => w.Key.Context).Distinct().Single(); var address = rule.AddressReservation.Address.Address; Watching watching; await this.semaphore.WaitAsync(cancellationToken); try { if (!this.watchings.ContainsKey(address)) { // Already timed out. return(false); } // Update confirmation count for all watches. switch (type) { case ConfirmationType.Confirmed: await this.watches.SetConfirmationCountAsync(confirm.Watches, cancellationToken); break; case ConfirmationType.Unconfirming: await this.watches.SetConfirmationCountAsync( confirm.Watches.ToDictionary( p => p.Key, p => { var confirmation = p.Value - 1; if (confirmation < 0) { throw new ArgumentException( "Some watches contains an invalid confirmation count.", nameof(confirm)); } return(confirmation); }), cancellationToken); return(false); default: throw new ArgumentOutOfRangeException(nameof(type), type, "The value is unsupported."); } // Check if completed. var amount = SumChanges(confirm.Watches, rule.TargetConfirmation); if (amount.Confirmed == null || amount.Confirmed < rule.TargetAmount) { return(false); } // Trigger completion. this.watchings.Remove(address, out watching); await this.rules.SetSucceededAsync(rule.Id, CancellationToken.None); await this.addressPool.ReleaseAddressAsync(rule.AddressReservation.Id, CancellationToken.None); // Invoke callback. var result = new CallbackData() { Received = amount, }; await InvokeCallbackAsync(rule.Callback, CallbackResult.StatusSuccess, result, CancellationToken.None); } finally { this.semaphore.Release(); } // Stop timer. We need to do this outside semaphore otherwise it will cause dead lock. await watching.Timer.StopAsync(CancellationToken.None); return(true); }