/// <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(); } } }
/// <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(); } }
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(); } }