Пример #1
0
        /// <summary>
        /// Execute the Executor lambda against QLDB and retrieve the result within a transaction.
        /// </summary>
        ///
        /// <param name="func">The Executor lambda representing the block of code to be executed within the transaction.
        /// This cannot have any side effects as it may be invoked multiple times, and the result cannot be trusted
        /// until the transaction is committed.</param>
        /// <typeparam name="T">The return type.</typeparam>
        ///
        /// <returns>The return value of executing the executor. Note that if you directly return a
        /// <see cref="IResult"/>, this will be automatically buffered in memory before the implicit commit to allow
        /// reading, as the commit will close any open results. Any other <see cref="IResult"/> instances created within
        /// the executor block will be invalidated, including if the return value is an object which nests said
        /// <see cref="IResult"/> instances within it.</returns>
        ///
        /// <exception cref="TransactionAbortedException">
        /// Thrown if the Executor lambda calls <see cref="TransactionExecutor.Abort"/>.
        /// </exception>
        /// <exception cref="AmazonServiceException">
        /// Thrown when there is an error executing against QLDB.
        /// </exception>
        internal T Execute <T>(Func <TransactionExecutor, T> func)
        {
            ValidationUtils.AssertNotNull(func, "func");

            Transaction transaction   = null;
            string      transactionId = QldbTransactionException.DefaultTransactionId;

            try
            {
                transaction   = this.StartTransaction();
                transactionId = transaction.Id;
                T returnedValue = func(new TransactionExecutor(transaction));
                if (returnedValue is IResult result)
                {
                    returnedValue = (T)(object)BufferedResult.BufferResult(result);
                }

                transaction.Commit();
                return(returnedValue);
            }
            catch (TransactionAbortedException)
            {
                throw;
            }
            catch (InvalidSessionException ise)
            {
                if (IsTransactionExpiredException(ise))
                {
                    throw new QldbTransactionException(transactionId, this.TryAbort(transaction), ise);
                }
                else
                {
                    throw new RetriableException(transactionId, false, ise);
                }
            }
            catch (OccConflictException occ)
            {
                throw new RetriableException(transactionId, true, occ);
            }
            catch (AmazonServiceException ase)
            {
                if (ase.StatusCode == HttpStatusCode.InternalServerError ||
                    ase.StatusCode == HttpStatusCode.ServiceUnavailable)
                {
                    throw new RetriableException(transactionId, this.TryAbort(transaction), ase);
                }

                throw new QldbTransactionException(transactionId, this.TryAbort(transaction), ase);
            }
            catch (Exception e)
            {
                throw new QldbTransactionException(transactionId, this.TryAbort(transaction), e);
            }
        }
        /// <summary>
        /// Execute the Executor lambda against QLDB and retrieve the result within a transaction.
        /// </summary>
        ///
        /// <param name="func">The Executor lambda representing the block of code to be executed within the transaction. This cannot have any
        /// side effects as it may be invoked multiple times, and the result cannot be trusted until the
        /// transaction is committed. The operation can be cancelled.</param>
        /// <typeparam name="T">The return type.</typeparam>
        /// <param name="cancellationToken">
        ///     A cancellation token that can be used by other objects or threads to receive notice of cancellation.
        /// </param>
        ///
        /// <returns>The return value of executing the executor. Note that if you directly return a <see cref="IResult"/>, this will
        /// be automatically buffered in memory before the implicit commit to allow reading, as the commit will close
        /// any open results. Any other <see cref="IResult"/> instances created within the executor block will be
        /// invalidated, including if the return value is an object which nests said <see cref="IResult"/> instances within it.
        /// </returns>
        ///
        /// <exception cref="TransactionAbortedException">Thrown if the Executor lambda calls <see cref="TransactionExecutor.Abort"/>.</exception>
        /// <exception cref="QldbDriverException">Thrown when called on a disposed instance.</exception>
        /// <exception cref="AmazonServiceException">Thrown when there is an error executing against QLDB.</exception>
        public async Task <T> Execute <T>(Func <TransactionExecutor, CancellationToken, Task <T> > func, CancellationToken cancellationToken = default)
        {
            ValidationUtils.AssertNotNull(func, "func");

            ITransaction transaction = null;

            try
            {
                transaction = await this.StartTransaction(cancellationToken);

                T returnedValue = await func(new TransactionExecutor(transaction), cancellationToken);

                if (returnedValue is IResult)
                {
                    returnedValue = (T)(object)await BufferedResult.BufferResult((IResult)returnedValue);
                }

                await transaction.Commit(cancellationToken);

                return(returnedValue);
            }
            catch (InvalidSessionException ise)
            {
                this.isAlive = false;
                throw new RetriableException(transaction.Id, false, ise);
            }
            catch (OccConflictException occ)
            {
                throw new RetriableException(transaction.Id, occ);
            }
            catch (AmazonServiceException ase)
            {
                if (ase.StatusCode == HttpStatusCode.InternalServerError ||
                    ase.StatusCode == HttpStatusCode.ServiceUnavailable)
                {
                    throw new RetriableException(transaction.Id, await this.TryAbort(transaction, cancellationToken), ase);
                }

                throw new QldbTransactionException(transaction.Id, await this.TryAbort(transaction, cancellationToken), ase);
            }
            catch (QldbTransactionException te)
            {
                throw te;
            }
            catch (Exception e)
            {
                throw new QldbTransactionException(transaction == null ? null : transaction.Id, await this.TryAbort(transaction, cancellationToken), e);
            }
        }
        /// <summary>
        /// Execute the Executor lambda against QLDB and retrieve the result within a transaction.
        /// </summary>
        ///
        /// <param name="func">The Executor lambda representing the block of code to be executed within the transaction. This cannot have any
        /// side effects as it may be invoked multiple times, and the result cannot be trusted until the
        /// transaction is committed.</param>
        /// <param name="retryAction">A lambda that is invoked when the Executor lambda is about to be retried due to
        /// a retriable error. Can be null if not applicable.</param>
        /// <typeparam name="T">The return type.</typeparam>
        ///
        /// <returns>The return value of executing the executor. Note that if you directly return a <see cref="IResult"/>, this will
        /// be automatically buffered in memory before the implicit commit to allow reading, as the commit will close
        /// any open results. Any other <see cref="IResult"/> instances created within the executor block will be
        /// invalidated, including if the return value is an object which nests said <see cref="IResult"/> instances within it.
        /// </returns>
        ///
        /// <exception cref="AbortException">Thrown if the Executor lambda calls <see cref="TransactionExecutor.Abort"/>.</exception>
        /// <exception cref="ObjectDisposedException">Thrown when called on a disposed instance.</exception>
        /// <exception cref="AmazonClientException">Thrown when there is an error executing against QLDB.</exception>
        public virtual T Execute <T>(Func <TransactionExecutor, T> func, Action <int> retryAction)
        {
            ValidationUtils.AssertNotNull(func, "func");
            this.ThrowIfClosed();

            var executionAttempt = 0;

            while (true)
            {
                ITransaction transaction = null;
                try
                {
                    transaction = this.StartTransaction();
                    T returnedValue = func(new TransactionExecutor(transaction));
                    if (returnedValue is IResult)
                    {
                        returnedValue = (T)(object)BufferedResult.BufferResult((IResult)returnedValue);
                    }

                    transaction.Commit();
                    return(returnedValue);
                }
                catch (AbortException ae)
                {
                    this.NoThrowAbort(transaction);
                    throw ae;
                }
                catch (InvalidSessionException ise)
                {
                    if (executionAttempt >= this.retryLimit)
                    {
                        throw ise;
                    }

                    this.logger.LogInformation(
                        "Creating a new session to QLDB; previous session is no longer valid: {}", ise);
                    this.session =
                        Session.StartSession(this.session.LedgerName, this.session.SessionClient, this.logger);
                }
                catch (OccConflictException oce)
                {
                    this.logger.LogInformation("OCC conflict occured: {}", oce);
                    if (executionAttempt >= this.retryLimit)
                    {
                        throw oce;
                    }
                }
                catch (AmazonQLDBSessionException aqse)
                {
                    this.NoThrowAbort(transaction);
                    if (executionAttempt >= this.retryLimit ||
                        ((aqse.StatusCode != HttpStatusCode.InternalServerError) &&
                         (aqse.StatusCode != HttpStatusCode.ServiceUnavailable)))
                    {
                        throw aqse;
                    }
                }
                catch (AmazonClientException ace)
                {
                    this.NoThrowAbort(transaction);
                    throw ace;
                }

                executionAttempt++;
                retryAction?.Invoke(executionAttempt);
                SleepOnRetry(executionAttempt);
            }
        }