/// <summary> /// Reads the next row of values from Cloud Spanner. /// Important: Cloud Spanner supports limited cancellation of this task. /// </summary> /// <param name="cancellationToken">A cancellation token to cancel the read. Cloud Spanner currently /// supports limited cancellation while advancing the read to the next row.</param> /// <returns>True if another row was read.</returns> public override Task <bool> ReadAsync(CancellationToken cancellationToken) { return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { if (_metadata == null) { await PopulateMetadataAsync(cancellationToken).ConfigureAwait(false); } _innerList.Clear(); //read # values == # fields. var first = await _resultSet.NextAsync(cancellationToken).ConfigureAwait(false); if (first == null) { return false; } _innerList.Add(first); //we expect to get full rows... for (var i = 1; i < _metadata.RowType.Fields.Count; i++) { _innerList.Add(await _resultSet.NextAsync(cancellationToken).ConfigureAwait(false)); } return true; }, "SpannerDataReader.Read", Logger)); }
internal Task <ResultSetMetadata> PopulateMetadataAsync(CancellationToken cancellationToken) { return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => _metadata ?? (_metadata = await _resultSet.GetMetadataAsync(cancellationToken) .ConfigureAwait(false)), "SpannerDataReader.GetMetadata", Logger)); }
private void CommitToSpanner() { // If it's a read-only transaction, then just tell the outer transaction that everything is good. // This can happen with a read-only transaction or a write transaction where we never // executed any mutations. if (_transaction != null && _transaction.Mode != TransactionMode.ReadOnly) { ExecuteHelper.WithErrorTranslationAndProfiling(() => _transaction.Commit(), "VolatileResourceManager.Commit", Logger); } }
/// <summary> /// Rolls back a transaction asynchronously. /// </summary> public Task RollbackAsync() { return(ExecuteHelper.WithErrorTranslationAndProfiling( () => { GaxPreconditions.CheckState( Mode != TransactionMode.ReadOnly, "You cannot roll back a readonly transaction."); return WireTransaction.RollbackAsync(Session); }, "SpannerTransaction.Rollback")); }
/// <summary> /// Reads the next row of values from Cloud Spanner. /// Important: Cloud Spanner supports limited cancellation of this task. /// </summary> /// <param name="cancellationToken">A cancellation token to cancel the read. Cloud Spanner currently /// supports limited cancellation while advancing the read to the next row.</param> /// <returns>True if another row was read.</returns> public override Task <bool> ReadAsync(CancellationToken cancellationToken) => ExecuteHelper.WithErrorTranslationAndProfiling(async() => { using (var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(_readTimeoutSeconds))) { var timeoutToken = timeoutCts.Token; using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken)) { try { var effectiveToken = linkedCts.Token; if (_metadata == null) { await PopulateMetadataAsync(effectiveToken).ConfigureAwait(false); } _rowValid = false; _innerList.Clear(); var first = await _resultSet.NextAsync(effectiveToken).ConfigureAwait(false); if (first == null) { // If this is the first thing we've tried to read, then we know there are no rows. if (_hasRows == null) { _hasRows = false; } return(false); } _innerList.Add(first); // We expect to get full rows... for (var i = 1; i < _metadata.RowType.Fields.Count; i++) { var value = await _resultSet.NextAsync(effectiveToken).ConfigureAwait(false); GaxPreconditions.CheckState(value != null, "Incomplete row returned by Spanner server"); _innerList.Add(value); } _rowValid = true; _hasRows = true; return(true); } // Translate timeouts from our own cancellation token into a Spanner exception. // This mimics the behavior of SqlDataReader, which throws a SqlException on timeout. // It's *possible* that the operation was cancelled due to the user-provided cancellation token, // and that it just happens that the timeout has been fired as well... but there's a race // condition anyway in that case, so it's probably reasonable to take the simple path. catch (OperationCanceledException) when(timeoutToken.IsCancellationRequested) { throw new SpannerException(ErrorCode.DeadlineExceeded, "Read operation timed out"); } } } }, "SpannerDataReader.Read", Logger);
/// <summary> /// Commits the database transaction asynchronously. /// </summary> /// <returns>Returns the UTC timestamp when the data was written to the database.</returns> public Task <DateTime?> CommitAsync() { return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { GaxPreconditions.CheckState( Mode != TransactionMode.ReadOnly, "You cannot commit a readonly transaction."); var response = await WireTransaction.CommitAsync(Session, Mutations).ConfigureAwait(false); return response.CommitTimestamp?.ToDateTime(); }, "SpannerTransaction.Commit")); }
public Task <long> ExecuteDmlAsync(ExecuteSqlRequest request, CancellationToken cancellationToken, int timeoutSeconds) => ExecuteHelper.WithErrorTranslationAndProfiling( async() => { using (var transaction = await _connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false)) { transaction.CommitTimeout = timeoutSeconds; long count = await((ISpannerTransaction)transaction).ExecuteDmlAsync(request, cancellationToken, timeoutSeconds).ConfigureAwait(false); await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); return(count); } }, "EphemeralTransaction.ExecuteDmlAsync", Logger);
/// <summary> /// Commits the database transaction asynchronously. /// </summary> /// <returns>Returns the UTC timestamp when the data was written to the database.</returns> public Task <DateTime?> CommitAsync() { return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { GaxPreconditions.CheckState( Mode != TransactionMode.ReadOnly, "You cannot commit a readonly transaction."); // We allow access to _mutations outside of a lock here because multithreaded // access at this point should be done and only one caller can call commit. var response = await WireTransaction.CommitAsync(Session, _mutations).ConfigureAwait(false); return response.CommitTimestamp?.ToDateTime(); }, "SpannerTransaction.Commit")); }
/// <summary> /// Gets the read timestamp if <see cref="TimestampBound.ReturnReadTimestamp" /> was true /// else returns <c>null</c>. /// </summary> /// <returns>The value converted to a <see cref="Timestamp"/>, or <c>null</c> if /// <see cref="TimestampBound.ReturnReadTimestamp" /> was false.</returns> public Task <Timestamp> GetReadTimestampAsync(CancellationToken cancellationToken) { if (_readTimestamp != null) { return(Task.FromResult(_readTimestamp)); } return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { var metadata = await PopulateMetadataAsync(cancellationToken).ConfigureAwait(false); _readTimestamp = metadata.Transaction?.ReadTimestamp; return _readTimestamp; }, "SpannerDataReader.GetReadTimestamp", Logger)); }
public Task <ReliableStreamReader> ExecuteQueryAsync(ExecuteSqlRequest request, CancellationToken cancellationToken, int timeoutSeconds) { return(ExecuteHelper.WithErrorTranslationAndProfiling(Impl, "EphemeralTransaction.ExecuteQuery", _connection.Logger)); async Task <ReliableStreamReader> Impl() { PooledSession session = await _connection.AcquireSessionAsync(_transactionOptions, cancellationToken).ConfigureAwait(false); var reader = session.ExecuteSqlStreamReader(request, timeoutSeconds); reader.StreamClosed += delegate { session.ReleaseToPool(forceDelete: false); }; return(reader); } }
public Task <ReliableStreamReader> ExecuteQueryAsync( ExecuteSqlRequest request, CancellationToken cancellationToken) { GaxPreconditions.CheckNotNull(request, nameof(request)); return(ExecuteHelper.WithErrorTranslationAndProfiling( () => { request.Transaction = new TransactionSelector { SingleUse = _options }; return Task.FromResult(_spannerConnection.SpannerClient.GetSqlStreamReader(request, _session)); }, "SingleUseTransaction.ExecuteQuery")); }
Task <int> ISpannerTransaction.ExecuteMutationsAsync( List <Mutation> mutations, CancellationToken cancellationToken) { GaxPreconditions.CheckNotNull(mutations, nameof(mutations)); CheckCompatibleMode(TransactionMode.ReadWrite); return(ExecuteHelper.WithErrorTranslationAndProfiling( () => { var taskCompletionSource = new TaskCompletionSource <int>(); cancellationToken.ThrowIfCancellationRequested(); _mutations.AddRange(mutations); taskCompletionSource.SetResult(mutations.Count); return taskCompletionSource.Task; }, "SpannerTransaction.ExecuteMutations")); }
Task <ReliableStreamReader> ISpannerTransaction.ExecuteQueryAsync( ExecuteSqlRequest request, CancellationToken cancellationToken) { GaxPreconditions.CheckNotNull(request, nameof(request)); return(ExecuteHelper.WithErrorTranslationAndProfiling( () => { var taskCompletionSource = new TaskCompletionSource <ReliableStreamReader>(); request.Transaction = GetTransactionSelector(TransactionMode.ReadOnly); taskCompletionSource.SetResult(_connection.SpannerClient.GetSqlStreamReader(request, Session)); return taskCompletionSource.Task; }, "SpannerTransaction.ExecuteQuery")); }
public Task <ReliableStreamReader> ExecuteReadOrQueryAsync(ReadOrQueryRequest request, CancellationToken cancellationToken, int timeoutSeconds /* ignored */) { return(ExecuteHelper.WithErrorTranslationAndProfiling(Impl, "EphemeralTransaction.ExecuteReadOrQuery", _connection.Logger)); async Task <ReliableStreamReader> Impl() { PooledSession session = await _connection.AcquireSessionAsync(_transactionOptions, cancellationToken).ConfigureAwait(false); var callSettings = _connection.CreateCallSettings( request.GetCallSettings, cancellationToken); var reader = request.ExecuteReadOrQueryStreamReader(session, callSettings); reader.StreamClosed += delegate { session.ReleaseToPool(forceDelete: false); }; return(reader); } }
public void Rollback(Enlistment enlistment) { try { ExecuteHelper.WithErrorTranslationAndProfiling(() => _transaction?.Rollback(), "VolatileResourceManager.Rollback", Logger); enlistment.Done(); } catch (Exception e) { Logger.Error( () => "Error attempting to rollback a transaction.", e); } finally { Dispose(); } }
public void Rollback(Enlistment enlistment) { try { // We don't need to roll back if we're in a read-only transaction, and indeed doing so will cause an error. if (_transaction != null && _transaction.Mode != TransactionMode.ReadOnly) { ExecuteHelper.WithErrorTranslationAndProfiling(() => _transaction.Rollback(), "VolatileResourceManager.Rollback", Logger); } enlistment.Done(); } catch (Exception e) { Logger.Error("Error attempting to rollback a transaction.", e); } finally { Dispose(); } }
/// <summary> /// Acquires a read/write transaction from Spannerconnection and releases the transaction back into the pool /// after the operation is complete. /// SpannerCommand never uses implicit Transactions for write operations because they are non idempotent and /// may result in unexpected errors if retry is used. /// </summary> /// <param name="mutations"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public Task <int> ExecuteMutationsAsync(List <Mutation> mutations, CancellationToken cancellationToken) { GaxPreconditions.CheckNotNull(mutations, nameof(mutations)); Logger.Debug(() => "Executing a mutation change through an ephemeral transaction."); int count; return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { using (var transaction = await _connection.BeginTransactionAsync(cancellationToken) .ConfigureAwait(false)) { count = await((ISpannerTransaction)transaction) .ExecuteMutationsAsync(mutations, cancellationToken) .ConfigureAwait(false); await transaction.CommitAsync().ConfigureAwait(false); } return count; }, "EphemeralTransaction.ExecuteMutations")); }
public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) { try { ExecuteHelper.WithErrorTranslationAndProfiling(() => _transaction?.Commit(), "VolatileResourceManager.Commit"); singlePhaseEnlistment.Committed(); } catch (SpannerException e) { singlePhaseEnlistment.Aborted(e); } catch (Exception e) { singlePhaseEnlistment.InDoubt(e); } finally { Dispose(); } }
/// <summary> /// Acquires a read/write transaction from Spannerconnection and releases the transaction back into the pool /// after the operation is complete. /// SpannerCommand never uses implicit Transactions for write operations because they are non idempotent and /// may result in unexpected errors if retry is used. /// </summary> /// <param name="mutations">The list of changes to apply.</param> /// <param name="cancellationToken">A cancellation token used for this task.</param> /// <param name="timeoutSeconds">The timeout which will apply to the commit part of this method.</param> /// <returns>The number of rows modified.</returns> public Task <int> ExecuteMutationsAsync(List <Mutation> mutations, CancellationToken cancellationToken, int timeoutSeconds) { return(ExecuteHelper.WithErrorTranslationAndProfiling(Impl, "EphemeralTransaction.ExecuteMutationsAsync", _connection.Logger)); async Task <int> Impl() { using (var transaction = await _connection.BeginTransactionImplAsync(_transactionOptions, TransactionMode.ReadWrite, cancellationToken).ConfigureAwait(false)) { // Importantly, we need to set timeout on the transaction, because // ExecuteMutations on SpannerTransaction doesnt actually hit the network // until you commit or rollback. transaction.CommitTimeout = timeoutSeconds; int count = await((ISpannerTransaction)transaction) .ExecuteMutationsAsync(mutations, cancellationToken, timeoutSeconds) .ConfigureAwait(false); await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); return(count); } } }
public Task <IEnumerable <long> > ExecuteBatchDmlAsync(ExecuteBatchDmlRequest request, CancellationToken cancellationToken, int timeoutSeconds) { return(ExecuteHelper.WithErrorTranslationAndProfiling(Impl, "EphemeralTransaction.ExecuteBatchDmlAsync", _connection.Logger)); async Task <IEnumerable <long> > Impl() { using (var transaction = await _connection.BeginTransactionImplAsync(_transactionOptions, TransactionMode.ReadWrite, cancellationToken).ConfigureAwait(false)) { transaction.CommitTimeout = timeoutSeconds; IEnumerable <long> result; result = await((ISpannerTransaction)transaction) .ExecuteBatchDmlAsync(request, cancellationToken, timeoutSeconds) .ConfigureAwait(false); await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); return(result); } } }
public Task <long> ExecuteDmlAsync(ExecuteSqlRequest request, CancellationToken cancellationToken, int timeoutSeconds) { return(ExecuteHelper.WithErrorTranslationAndProfiling(Impl, "EphemeralTransaction.ExecuteDmlAsync", _connection.Logger)); async Task <long> Impl() { using (var transaction = await _connection.BeginTransactionImplAsync(_transactionOptions, TransactionMode.ReadWrite, cancellationToken).ConfigureAwait(false)) { transaction.CommitTimeout = timeoutSeconds; transaction.CommitPriority = _commitPriority; while (true) { try { long count = await((ISpannerTransaction)transaction) .ExecuteDmlAsync(request, cancellationToken, timeoutSeconds) .ConfigureAwait(false); // This is somewhat ugly. PDML commits as it goes, so we don't need to, whereas non-partitioned // DML needs the commit afterwards to finish up. if (_transactionOptions.ModeCase != TransactionOptions.ModeOneofCase.PartitionedDml) { await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); } return(count); } catch (SpannerException e) when( _transactionOptions.ModeCase == TransactionOptions.ModeOneofCase.PartitionedDml && e.ErrorCode == ErrorCode.Internal && e.Message.Contains("Received unexpected EOS on DATA frame from server")) { // Retry with the same transaction. Since this error happens in long-lived // transactions (>= 30 mins), it's unnecessary to do exponential backoff. continue; } } } } }
public Task <long> ExecuteDmlAsync(ExecuteSqlRequest request, CancellationToken cancellationToken, int timeoutSeconds) { return(ExecuteHelper.WithErrorTranslationAndProfiling(Impl, "EphemeralTransaction.ExecuteDmlAsync", _connection.Logger)); async Task <long> Impl() { using (var transaction = await _connection.BeginTransactionImplAsync(_transactionOptions, TransactionMode.ReadWrite, cancellationToken).ConfigureAwait(false)) { transaction.CommitTimeout = timeoutSeconds; long count = await((ISpannerTransaction)transaction) .ExecuteDmlAsync(request, cancellationToken, timeoutSeconds) .ConfigureAwait(false); // This is somewhat ugly. PDML commits as it goes, so we don't need to, whereas non-partitioned // DML needs the commit afterwards to finish up. if (_transactionOptions.ModeCase != TransactionOptions.ModeOneofCase.PartitionedDml) { await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); } return(count); } } }
public Task <ReliableStreamReader> ExecuteQueryAsync( ExecuteSqlRequest request, CancellationToken cancellationToken) { return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { GaxPreconditions.CheckNotNull(request, nameof(request)); Logger.Debug(() => "Executing a query through an ephemeral transaction."); using (var holder = await SpannerConnection.SessionHolder .Allocate(_connection, cancellationToken) .ConfigureAwait(false)) { var streamReader = _connection.SpannerClient.GetSqlStreamReader(request, holder.Session); holder.TakeOwnership(); streamReader.StreamClosed += (o, e) => { _connection.ReleaseSession(streamReader.Session); }; return streamReader; } }, "EphemeralTransaction.ExecuteQuery")); }
/// <summary> /// Acquires a read/write transaction from Spannerconnection and releases the transaction back into the pool /// after the operation is complete. /// SpannerCommand never uses implicit Transactions for write operations because they are non idempotent and /// may result in unexpected errors if retry is used. /// </summary> /// <param name="mutations">The list of changes to apply.</param> /// <param name="cancellationToken">A cancellation token used for this task.</param> /// <param name="timeoutSeconds">The timeout which will apply to the commit part of this method.</param> /// <returns></returns> public Task <int> ExecuteMutationsAsync(List <Mutation> mutations, CancellationToken cancellationToken, int timeoutSeconds) { GaxPreconditions.CheckNotNull(mutations, nameof(mutations)); Logger.Debug(() => "Executing a mutation change through an ephemeral transaction."); int count; return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { using (var transaction = await _connection.BeginTransactionAsync(cancellationToken) .ConfigureAwait(false)) { // Importantly, we need to set timeout on the transaction, because // ExecuteMutations on SpannerTransaction doesnt actually hit the network // until you commit or rollback. transaction.CommitTimeout = timeoutSeconds; count = await((ISpannerTransaction)transaction) .ExecuteMutationsAsync(mutations, cancellationToken, timeoutSeconds) .ConfigureAwait(false); await transaction.CommitAsync().ConfigureAwait(false); } return count; }, "EphemeralTransaction.ExecuteMutations")); }
internal async Task <TResult> RunAsync <TResult>(Func <SpannerTransaction, Task <TResult> > asyncWork, CancellationToken cancellationToken = default) { GaxPreconditions.CheckNotNull(asyncWork, nameof(asyncWork)); // Session will be initialized and subsequently modified by CommitAttempt. PooledSession session = null; try { return(await ExecuteWithRetryAsync(CommitAttempt, cancellationToken).ConfigureAwait(false)); } finally { session?.Dispose(); } async Task <TResult> CommitAttempt() { return(await ExecuteHelper.WithErrorTranslationAndProfiling( async() => { SpannerTransaction transaction = null; try { session = await(session?.WithFreshTransactionOrNewAsync(SpannerConnection.s_readWriteTransactionOptions, cancellationToken) ?? _connection.AcquireReadWriteSessionAsync(cancellationToken)).ConfigureAwait(false); transaction = new SpannerTransaction(_connection, TransactionMode.ReadWrite, session, null); TResult result = await asyncWork(transaction).ConfigureAwait(false); await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); return result; } // We only catch to attempt a rollback, and that is possible if we have a transaction already. // If the exception was thrown when refreshing the session the we don't have a transaction yet. catch when(transaction != null) { try { await transaction.RollbackAsync(cancellationToken).ConfigureAwait(false); } catch (Exception e) { // If the commit failed, calling Rollback will fail as well. // We don't want that or any other rollback exception to propagate, // it will not trigger the retry _connection.Logger.Warn("A rollback attempt failed on RetriableTransaction.RunAsync.CommitAttempt", e); } // Throw, the retry helper will know when to retry. throw; } finally { if (transaction != null) { // Let's make sure that the associated session is not released to the pool // because we'll be attempting to get a fresh transaction for this same session first. // If that fails will attempt a new session acquisition. // This session will be disposed of by the pool if it can't be refreshed or by the RunAsync method // if we are not retrying anymore. transaction.DisposeBehavior = DisposeBehavior.Detach; transaction.Dispose(); } } }, "RetriableTransaction.RunAsync.CommitAttempt", _connection.Logger).ConfigureAwait(false)); } }