internal async Task RetryAsync(SpannerException abortedException, CancellationToken cancellationToken, int timeoutSeconds = MAX_TIMEOUT_SECONDS) { if (!EnableInternalRetries) { throw abortedException; } DateTime?overallDeadline = _options.CalculateDeadline(_clock); TimeSpan retryDelay = _options.InitialDelay; // If there's a recommended retry delay specified on the exception // we should respect it. retryDelay = abortedException.RecommendedRetryDelay ?? _options.Jitter(retryDelay); while (true) { if (RetryCount >= MaxInternalRetryCount) { throw new SpannerException(ErrorCode.Aborted, "Transaction was aborted because it aborted and retried too many times"); } DateTime expectedRetryTime = _clock.GetCurrentDateTimeUtc() + retryDelay; if (expectedRetryTime > overallDeadline) { throw new SpannerException(ErrorCode.Aborted, "Transaction was aborted because it timed out while retrying"); } await _scheduler.Delay(retryDelay, cancellationToken).ConfigureAwait(false); // TODO: Preferably the Spanner client library should have some 'reset' option on an existing // transaction instead of having to begin a new transaction. This will potentially use a transaction // on a different session, while the recommended behavior for a retry is to use the same session as // the original attempt. SpannerTransaction.Dispose(); SpannerTransaction = await Connection.SpannerConnection.BeginTransactionAsync(cancellationToken); RetryCount++; try { foreach (IRetriableStatement statement in _retriableStatements) { await statement.RetryAsync(this, cancellationToken, timeoutSeconds); } break; } catch (SpannerAbortedDueToConcurrentModificationException) { // Retry failed because of a concurrent modification. throw; } catch (SpannerException e) when(e.ErrorCode == ErrorCode.Aborted) { // Ignore and retry. retryDelay = e.RecommendedRetryDelay ?? _options.Jitter(_options.NextDelay(retryDelay)); } } }
protected override void Dispose(bool disposing) { if (Disposed) { return; } if (disposing) { SpannerTransaction.Dispose(); } Disposed = true; base.Dispose(disposing); }