/// <summary>
        /// Destroy the transaction and release all allocated resources, including all non-committed changes.
        /// </summary>
        /// <remarks>This instance will not be usable again and most methods will throw an ObjectDisposedException.</remarks>
        public void Dispose()
        {
            // note: we can be called by user code, or by the FdbDatabase when it is terminating with pending transactions
            if (Interlocked.Exchange(ref m_state, STATE_DISPOSED) != STATE_DISPOSED)
            {
                try
                {
                    this.Database.UnregisterTransaction(this);
                    m_cts.SafeCancelAndDispose();

                    if (Logging.On)
                    {
                        Logging.Verbose(this, "Dispose", String.Format("Transaction #{0} has been disposed", m_id));
                    }
                }
                finally
                {
                    // Dispose of the handle
                    if (m_handler != null)
                    {
                        try { m_handler.Dispose(); }
                        catch (Exception e)
                        {
                            if (Logging.On)
                            {
                                Logging.Error(this, "Dispose", String.Format("Transaction #{0} failed to dispose the transaction handler: {1}", m_id, e.Message));
                            }
                        }
                    }
                    if (!m_context.Shared)
                    {
                        m_context.Dispose();
                    }
                    m_cts.Dispose();
                }
            }
        }
Пример #2
0
        /// <summary>Execute a retry loop on this context</summary>
        internal static async Task ExecuteInternal([NotNull] IFdbDatabase db, [NotNull] FdbOperationContext context, [NotNull] Delegate handler, Delegate onDone)
        {
            Contract.Requires(db != null && context != null && handler != null);
            Contract.Requires(context.Shared);

            if (context.Abort)
            {
                throw new InvalidOperationException("Operation context has already been aborted or disposed");
            }

            try
            {
                // make sure to reset everything (in case a context is reused multiple times)
                context.Committed    = false;
                context.Retries      = 0;
                context.BaseDuration = TimeSpan.Zero;
                context.StartedUtc   = DateTime.UtcNow;
                context.Clock.Start();
                //note: we start the clock immediately, but the transaction's 5 seconde max lifetime is actually measured from the first read operation (Get, GetRange, GetReadVersion, etc...)
                // => algorithms that monitor the elapsed duration to rate limit themselves may think that the trans is older than it really is...
                // => we would need to plug into the transaction handler itself to be notified when exactly a read op starts...

                using (var trans = db.BeginTransaction(context.Mode, CancellationToken.None, context))
                {
                    while (!context.Committed && !context.Cancellation.IsCancellationRequested)
                    {
                        FdbException e = null;
                        try
                        {
                            // call the user provided lambda
                            if (handler is Func <IFdbTransaction, Task> funcWritable)
                            {
                                await funcWritable(trans).ConfigureAwait(false);
                            }
                            else if (handler is Action <IFdbTransaction> action)
                            {
                                action(trans);
                            }
                            else if (handler is Func <IFdbReadOnlyTransaction, Task> funcReadOnly)
                            {
                                await funcReadOnly(trans).ConfigureAwait(false);
                            }
                            else
                            {
                                throw new NotSupportedException($"Cannot execute handlers of type {handler.GetType().Name}");
                            }

                            if (context.Abort)
                            {
                                break;
                            }

                            if (!trans.IsReadOnly)
                            {                             // commit the transaction
                                await trans.CommitAsync().ConfigureAwait(false);
                            }

                            // we are done
                            context.Committed = true;

                            if (onDone != null)
                            {
                                if (onDone is Action <IFdbReadOnlyTransaction> action1)
                                {
                                    action1(trans);
                                }
                                else if (onDone is Action <IFdbTransaction> action2)
                                {
                                    action2(trans);
                                }
                                else if (onDone is Func <IFdbReadOnlyTransaction, Task> func1)
                                {
                                    await func1(trans).ConfigureAwait(false);
                                }
                                else if (onDone is Func <IFdbTransaction, Task> func2)
                                {
                                    await func2(trans).ConfigureAwait(false);
                                }
                                else
                                {
                                    throw new NotSupportedException($"Cannot execute completion handler of type {handler.GetType().Name}");
                                }
                            }
                        }
                        catch (FdbException x)
                        {
                            //TODO: will be able to await in catch block in C# 6 !
                            e = x;
                        }

                        if (e != null)
                        {
                            if (Logging.On && Logging.IsVerbose)
                            {
                                Logging.Verbose(String.Format(CultureInfo.InvariantCulture, "fdb: transaction {0} failed with error code {1}", trans.Id, e.Code));
                            }
                            await trans.OnErrorAsync(e.Code).ConfigureAwait(false);

                            if (Logging.On && Logging.IsVerbose)
                            {
                                Logging.Verbose(String.Format(CultureInfo.InvariantCulture, "fdb: transaction {0} can be safely retried", trans.Id));
                            }
                        }

                        // update the base time for the next attempt
                        context.BaseDuration = context.ElapsedTotal;
                        if (context.BaseDuration.TotalSeconds >= 10)
                        {
                            //REVIEW: this may not be a goot idea to spam the logs with long running transactions??
                            if (Logging.On)
                            {
                                Logging.Info(String.Format(CultureInfo.InvariantCulture, "fdb WARNING: long transaction ({0:N1} sec elapsed in transaction lambda function ({1} retries, {2})", context.BaseDuration.TotalSeconds, context.Retries, context.Committed ? "committed" : "not yet committed"));
                            }
                        }

                        context.Retries++;
                    }
                }
                context.Cancellation.ThrowIfCancellationRequested();

                if (context.Abort)
                {
                    throw new OperationCanceledException(context.Cancellation);
                }
            }
            finally
            {
                context.Clock.Stop();
                context.Dispose();
            }
        }
Пример #3
0
        internal static async Task ExecuteInternal([NotNull] IFdbDatabase db, [NotNull] FdbOperationContext context, [NotNull] Delegate handler, Delegate onDone)
        {
            Contract.Requires(db != null && context != null && handler != null);
            Contract.Requires(context.Shared);

            if (context.Abort)
            {
                throw new InvalidOperationException("Operation context has already been aborted or disposed");
            }

            try
            {
                context.Committed  = false;
                context.Retries    = 0;
                context.StartedUtc = DateTime.UtcNow;
                context.Duration.Start();

                using (var trans = db.BeginTransaction(context.Mode, CancellationToken.None, context))
                {
                    while (!context.Committed && !context.Cancellation.IsCancellationRequested)
                    {
                        FdbException e = null;
                        try
                        {
                            // call the user provided lambda
                            if (handler is Func <IFdbTransaction, Task> )
                            {
                                await((Func <IFdbTransaction, Task>)handler)(trans).ConfigureAwait(false);
                            }
                            else if (handler is Action <IFdbTransaction> )
                            {
                                ((Action <IFdbTransaction>)handler)(trans);
                            }
                            else if (handler is Func <IFdbReadOnlyTransaction, Task> )
                            {
                                await((Func <IFdbReadOnlyTransaction, Task>)handler)(trans).ConfigureAwait(false);
                            }
                            else
                            {
                                throw new NotSupportedException(String.Format("Cannot execute handlers of type {0}", handler.GetType().Name));
                            }

                            if (context.Abort)
                            {
                                break;
                            }

                            if (!trans.IsReadOnly)
                            {                             // commit the transaction
                                await trans.CommitAsync().ConfigureAwait(false);
                            }

                            // we are done
                            context.Committed = true;

                            if (onDone != null)
                            {
                                if (onDone is Action <IFdbReadOnlyTransaction> )
                                {
                                    ((Action <IFdbReadOnlyTransaction>)onDone)(trans);
                                }
                                else if (onDone is Action <IFdbTransaction> )
                                {
                                    ((Action <IFdbTransaction>)onDone)(trans);
                                }
                                else if (onDone is Func <IFdbReadOnlyTransaction, Task> )
                                {
                                    await((Func <IFdbReadOnlyTransaction, Task>)onDone)(trans).ConfigureAwait(false);
                                }
                                else if (onDone is Func <IFdbTransaction, Task> )
                                {
                                    await((Func <IFdbTransaction, Task>)onDone)(trans).ConfigureAwait(false);
                                }
                                else
                                {
                                    throw new NotSupportedException(String.Format("Cannot execute completion handler of type {0}", handler.GetType().Name));
                                }
                            }
                        }
                        catch (FdbException x)
                        {
                            e = x;
                        }

                        if (e != null)
                        {
                            if (Logging.On && Logging.IsVerbose)
                            {
                                Logging.Verbose(String.Format(CultureInfo.InvariantCulture, "fdb: transaction {0} failed with error code {1}", trans.Id, e.Code));
                            }
                            await trans.OnErrorAsync(e.Code).ConfigureAwait(false);

                            if (Logging.On && Logging.IsVerbose)
                            {
                                Logging.Verbose(String.Format(CultureInfo.InvariantCulture, "fdb: transaction {0} can be safely retried", trans.Id));
                            }
                        }

                        if (context.Duration.Elapsed.TotalSeconds >= 1)
                        {
                            if (Logging.On)
                            {
                                Logging.Info(String.Format(CultureInfo.InvariantCulture, "fdb WARNING: long transaction ({0:N1} sec elapsed in transaction lambda function ({1} retries, {2})", context.Duration.Elapsed.TotalSeconds, context.Retries, context.Committed ? "committed" : "not yet committed"));
                            }
                        }

                        context.Retries++;
                    }
                }
                context.Cancellation.ThrowIfCancellationRequested();

                if (context.Abort)
                {
                    throw new OperationCanceledException(context.Cancellation);
                }
            }
            finally
            {
                context.Duration.Stop();
                context.Dispose();
            }
        }