Example #1
0
        /// <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));
 }
Example #3
0
 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"));
 }
Example #5
0
        /// <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);
Example #6
0
 /// <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"));
 }
Example #9
0
 /// <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));
 }
Example #10
0
        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"));
 }
Example #12
0
 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"));
        }
Example #14
0
        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);
            }
        }
Example #15
0
 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();
     }
 }
Example #16
0
 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();
     }
 }
Example #17
0
        /// <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);
                }
            }
        }
Example #21
0
        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);
                }
            }
        }
Example #23
0
        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"));
        }
Example #24
0
        /// <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));
            }
        }