public async Task OnErrorAsync(FdbError code, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); switch (code) { case FdbError.TimedOut: case FdbError.PastVersion: { // wait a bit ++m_retryCount; if (m_retryCount > this.RetryLimit) { // max rety limit reached throw new FdbException(code); } //HACKHACK: implement a real back-off delay logic await Task.Delay(15, cancellationToken).ConfigureAwait(false); this.Reset(); return; } default: { throw new FdbException(code); } } }
/// <summary>Throws an exception if the code represents a failure</summary> internal static void DieOnError(FdbError code) { if (Failed(code)) { throw MapToException(code); } }
/// <summary>Maps an error code into an Exception (to be throwned)</summary> /// <param name="code"></param> /// <returns>Exception object corresponding to the error code, or null if the code is not an error</returns> public static Exception MapToException(FdbError code) { if (code == FdbError.Success) { return(null); } string msg = GetErrorMessage(code); if (msg == null) { throw new FdbException(code, $"Unexpected error code {(int) code}"); } //TODO: create a custom FdbException to be able to store the error code and error message switch (code) { case FdbError.TimedOut: return(new TimeoutException("Operation timed out")); case FdbError.LargeAllocFailed: return(new OutOfMemoryException("Large block allocation failed")); //TODO! default: return(new FdbException(code, msg)); } }
public override Task OnErrorAsync(FdbError code) { return(ExecuteAsync( new FdbTransactionLog.OnErrorCommand(code), (_tr, _cmd) => _tr.OnErrorAsync(_cmd.Code) )); }
protected FoundationDbException(SerializationInfo info, StreamingContext context) { if (info != null) { ErrorCode = (FdbError)info.GetInt32(nameof(ErrorCode)); } }
/// <summary> /// Ensures success by throwing an exception if <paramref name="error"/> does not indicate Success. /// </summary> /// <param name="error">The error code to check.</param> /// <exception cref="FoundationDbException">Thrown when <paramref name="error"/> does not indicate Success.</exception> public static void EnsureSuccess(this FdbError error) { if (error == FdbError.Success) { return; } ThrowFoundationDbException(error); }
public static FutureHandle TransactionOnError(TransactionHandle transaction, FdbError errorCode) { var future = NativeMethods.fdb_transaction_on_error(transaction, errorCode); Contract.Assert(future != null); #if DEBUG_NATIVE_CALLS Debug.WriteLine("fdb_transaction_on_error(0x" + transaction.Handle.ToString("x") + ", " + errorCode + ") => 0x" + future.Handle.ToString("x")); #endif return(future); }
private static string GetOrAddErrorMessage(FdbError error) { return(ErrorMessages.GetOrAdd(error, err => { var msgPtr = fdb_get_error(err); string message = Marshal.PtrToStringAnsi(msgPtr); return message; })); }
/// <summary> /// Converts the given <paramref name="error"/> to an exception. /// </summary> /// <param name="error">The error code to convert to an <see cref="Exception"/>.</param> /// <returns>The exception encapsulating the error code.</returns> /// <exception cref="InvalidOperationException">Thrown when <paramref name="error"/> is Success.</exception> public static FoundationDbException ToException(this FdbError error) { if (error == FdbError.Success) { throw new InvalidOperationException("FdbError.Success is not an error."); } if (!ErrorMessages.TryGetValue(error, out string errorMessage)) { errorMessage = GetOrAddErrorMessage(error); } return(new FoundationDbException(errorMessage, error)); }
public static async Task AssertThrowsFdbErrorAsync(Func <Task> asyncTest, FdbError expectedCode, string message = null, object[] args = null) { try { await asyncTest(); Assert.Fail(message, args); } catch (AssertionException) { throw; } catch (Exception e) { Assert.That(e, Is.InstanceOf <FdbException>().With.Property("Code").EqualTo(expectedCode), message, args); } }
/// <summary>Update the Task with the state of a ready Future</summary> /// <returns>True if we got a result, or false in case of error (or invalid state)</returns> private void HandleCompletion() { if (HasAnyFlags(FdbFuture.Flags.DISPOSED | FdbFuture.Flags.COMPLETED)) { return; } #if DEBUG_FUTURES Debug.WriteLine("FutureArray<" + typeof(T).Name + ">.Callback(...) handling completion on thread #" + Thread.CurrentThread.ManagedThreadId.ToString()); #endif try { UnregisterCancellationRegistration(); List <Exception>?errors = null; bool cancellation = false; var selector = m_resultSelector; var results = selector != null ? new T[m_handles.Length] : null; for (int i = 0; i < m_handles.Length; i++) { var handle = m_handles[i]; if (handle != null && !handle.IsClosed && !handle.IsInvalid) { FdbError err = FdbNative.FutureGetError(handle); if (Fdb.Failed(err)) { // it failed... if (err != FdbError.OperationCancelled) { // get the exception from the error code var ex = Fdb.MapToException(err) !; (errors ??= new List <Exception>()).Add(ex); } else { cancellation = true; break; } } else { // it succeeded... // try to get the result... if (selector != null) { //note: result selector will execute from network thread, but this should be our own code that only calls into some fdb_future_get_XXXX(), which should be safe... results ![i] = selector(handle);
/// <summary> /// Implements the recommended retry and backoff behavior for a transaction. /// /// This function knows which of the error codes generated by other query functions represent temporary error conditions and which represent application errors that should be handled by the application. /// It also implements an exponential backoff strategy to avoid swamping the database cluster with excessive retries when there is a high level of conflict between transactions. /// </summary> /// <param name="code">FdbError code thrown by the previous command</param> /// <returns>Returns a task that completes if the operation can be safely retried, or that rethrows the original exception if the operation is not retryable.</returns> public async Task OnErrorAsync(FdbError code) { EnsureCanRetry(); await m_handler.OnErrorAsync(code, cancellationToken : m_cancellation).ConfigureAwait(false); // If fdb_transaction_on_error succeeds, that means that the transaction has been reset and is usable again var state = this.State; if (state != STATE_DISPOSED) { Interlocked.CompareExchange(ref m_state, STATE_READY, state); } RestoreDefaultSettings(); }
private static void SetResult(IntPtr handle, IntPtr futurePtr) { var fdbFutureGch = GCHandle.FromIntPtr(futurePtr); var fdbFuture = (FdbFuture <T>)fdbFutureGch.Target; Debug.Assert(fdbFuture != null); using (fdbFuture) { TaskCompletionSource <T> task = fdbFuture._tcs; FdbError error = fdbFuture.GetError(); if (error == FdbError.Success) { T result = fdbFuture.GetResult(); task.SetResult(result); } else { task.SetException(error.ToException()); } } }
/// <summary>Returns true if the error code represents a failure</summary> public static bool Failed(FdbError code) { return(code != FdbError.Success); }
public virtual Task OnErrorAsync(FdbError code) { return m_transaction.OnErrorAsync(code); }
public virtual Task OnErrorAsync(FdbError code) { return(m_transaction.OnErrorAsync(code)); }
/// <summary>Maps an error code into an Exception (to be throwned)</summary> /// <param name="code"></param> /// <returns>Exception object corresponding to the error code, or null if the code is not an error</returns> public static Exception MapToException(FdbError code) { if (code == FdbError.Success) return null; string msg = GetErrorMessage(code); if (msg == null) throw new FdbException(code, String.Format("Unexpected error code {0}", (int)code)); //TODO: create a custom FdbException to be able to store the error code and error message switch(code) { case FdbError.TimedOut: return new TimeoutException("Operation timed out"); case FdbError.LargeAllocFailed: return new OutOfMemoryException("Large block allocation failed"); //TODO! default: return new FdbException(code, msg); } }
/// <summary>Throws an exception if the code represents a failure</summary> internal static void DieOnError(FdbError code) { if (Failed(code)) throw MapToException(code); }
/// <summary> /// Implements the recommended retry and backoff behavior for a transaction. /// /// This function knows which of the error codes generated by other query functions represent temporary error conditions and which represent application errors that should be handled by the application. /// It also implements an exponential backoff strategy to avoid swamping the database cluster with excessive retries when there is a high level of conflict between transactions. /// </summary> /// <param name="code">FdbError code thrown by the previous command</param> /// <returns>Returns a task that completes if the operation can be safely retried, or that rethrows the original exception if the operation is not retryable.</returns> public async Task OnErrorAsync(FdbError code) { EnsureCanRetry(); await m_handler.OnErrorAsync(code, cancellationToken: m_cancellation).ConfigureAwait(false); // If fdb_transaction_on_error succeeds, that means that the transaction has been reset and is usable again var state = this.State; if (state != STATE_DISPOSED) Interlocked.CompareExchange(ref m_state, STATE_READY, state); RestoreDefaultSettings(); }
/// <summary>fdb_get_error</summary> public static string GetError(FdbError code) { return ToManagedString(NativeMethods.fdb_get_error(code)); }
public FdbException(FdbError errorCode, string message, Exception innerException) : base(message, innerException) { this.Code = errorCode; }
public FdbException(FdbError errorCode, string message) : this(errorCode, message, null) { }
public FdbException(FdbError errorCode) : this(errorCode, Fdb.GetErrorMessage(errorCode), null) { }
public Task OnErrorAsync(FdbError code, CancellationToken cancellationToken) { var future = FdbNative.TransactionOnError(m_handle, code); return FdbFuture.CreateTaskFromHandle<object>(future, (h) => { ResetInternal(); return null; }, cancellationToken); }
public static FutureHandle TransactionOnError(TransactionHandle transaction, FdbError errorCode) { var future = NativeMethods.fdb_transaction_on_error(transaction, errorCode); Contract.Assert(future != null); #if DEBUG_NATIVE_CALLS Debug.WriteLine("fdb_transaction_on_error(0x" + transaction.Handle.ToString("x") + ", " + errorCode + ") => 0x" + future.Handle.ToString("x")); #endif return future; }
/// <summary>Return the error message matching the specified error code</summary> public static string GetErrorMessage(FdbError code) { return(FdbNative.GetError(code)); }
/// <summary>Start the Network Thread, using the currently selected API version level</summary> /// <remarks>If you need a specific API version level, it must be defined by either calling <see cref="UseApiVersion"/> before calling this method, or by using the <see cref="Start(int)"/> override. Otherwise, the default API version will be selected.</remarks> public static void Start() { if (s_started) { return; } //BUGBUG: Specs say we cannot restart the network thread anymore in the process after stoping it ! :( s_started = true; int apiVersion = s_apiVersion; if (apiVersion <= 0) { apiVersion = DefaultApiVersion; } if (Logging.On) { Logging.Info(typeof(Fdb), "Start", $"Selecting fdb API version {apiVersion}"); } FdbError err = FdbNative.SelectApiVersion(apiVersion); if (err != FdbError.Success) { if (Logging.On) { Logging.Error(typeof(Fdb), "Start", $"Failed to fdb API version {apiVersion}: {err}"); } switch (err) { case FdbError.ApiVersionNotSupported: { // bad version was selected ? // note: we already bound check the values before, so that means that fdb_c.dll is either an older version or an incompatible new version. throw new FdbException(err, $"The API version {apiVersion} is not supported by the FoundationDB client library (fdb_c.dll) installed on this system. The binding only supports versions {GetMinApiVersion()} to {GetMaxApiVersion()}. You either need to upgrade the .NET binding or the FoundationDB client library to a newer version."); } #if DEBUG case FdbError.ApiVersionAlreadySet: { // Temporary hack to allow multiple debugging using the cached host process in VS Console.Error.WriteLine("FATAL: CANNOT REUSE EXISTING PROCESS! FoundationDB client cannot be restarted once stopped. Current process will be terminated."); Environment.FailFast("FATAL: CANNOT REUSE EXISTING PROCESS! FoundationDB client cannot be restarted once stopped. Current process will be terminated."); break; } #endif } DieOnError(err); } s_apiVersion = apiVersion; if (!string.IsNullOrWhiteSpace(Fdb.Options.TracePath)) { if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", $"Will trace client activity in '{Fdb.Options.TracePath}'"); } // create trace directory if missing... if (!SystemIO.Directory.Exists(Fdb.Options.TracePath)) { SystemIO.Directory.CreateDirectory(Fdb.Options.TracePath); } DieOnError(SetNetworkOption(FdbNetworkOption.TraceEnable, Fdb.Options.TracePath)); } if (!string.IsNullOrWhiteSpace(Fdb.Options.TLSPlugin)) { if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", $"Will use custom TLS plugin '{Fdb.Options.TLSPlugin}'"); } DieOnError(SetNetworkOption(FdbNetworkOption.TLSPlugin, Fdb.Options.TLSPlugin)); } if (Fdb.Options.TLSCertificateBytes.IsPresent) { if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS root certificate and private key from memory ({Fdb.Options.TLSCertificateBytes.Count} bytes)"); } DieOnError(SetNetworkOption(FdbNetworkOption.TLSCertBytes, Fdb.Options.TLSCertificateBytes)); } else if (!string.IsNullOrWhiteSpace(Fdb.Options.TLSCertificatePath)) { if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS root certificate and private key from '{Fdb.Options.TLSCertificatePath}'"); } DieOnError(SetNetworkOption(FdbNetworkOption.TLSCertPath, Fdb.Options.TLSCertificatePath)); } if (Fdb.Options.TLSPrivateKeyBytes.IsPresent) { if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS private key from memory ({Fdb.Options.TLSPrivateKeyBytes.Count} bytes)"); } DieOnError(SetNetworkOption(FdbNetworkOption.TLSKeyBytes, Fdb.Options.TLSPrivateKeyBytes)); } else if (!string.IsNullOrWhiteSpace(Fdb.Options.TLSPrivateKeyPath)) { if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS private key from '{Fdb.Options.TLSPrivateKeyPath}'"); } DieOnError(SetNetworkOption(FdbNetworkOption.TLSKeyPath, Fdb.Options.TLSPrivateKeyPath)); } if (Fdb.Options.TLSVerificationPattern.IsPresent) { if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", $"Will verify TLS peers with pattern '{Fdb.Options.TLSVerificationPattern}'"); } DieOnError(SetNetworkOption(FdbNetworkOption.TLSVerifyPeers, Fdb.Options.TLSVerificationPattern)); } try { } finally { // register with the AppDomain to ensure that everyting is cleared when the process exists s_appDomainUnloadHandler = (sender, args) => { if (s_started) { //note: since app domain is unloading, the logger may also have already stopped... if (Logging.On) { Logging.Verbose(typeof(Fdb), "AppDomainUnloadHandler", "AppDomain is unloading, stopping FoundationDB Network Thread..."); } Stop(); } }; AppDomain.CurrentDomain.DomainUnload += s_appDomainUnloadHandler; AppDomain.CurrentDomain.ProcessExit += s_appDomainUnloadHandler; if (Logging.On) { Logging.Verbose(typeof(Fdb), "Start", "Setting up Network Thread..."); } DieOnError(FdbNative.SetupNetwork()); s_started = true; //BUGBUG: already set at the start of the method. Maybe we need state flags ? } if (Logging.On) { Logging.Info(typeof(Fdb), "Start", "Network thread has been set up"); } StartEventLoop(); }
/// <summary>Returns true if the error code represents a failure</summary> public static bool Failed(FdbError code) { return code != FdbError.Success; }
/// <summary>Return the error message matching the specified error code</summary> public static string GetErrorMessage(FdbError code) { return FdbNative.GetError(code); }
public virtual Task OnErrorAsync(FdbError code) { ThrowIfDisposed(); return(m_transaction.OnErrorAsync(code)); }
/// <summary>Update the Task with the state of a ready Future</summary> /// <param name="fromCallback">If true, we are called from the network thread</param> /// <returns>True if we got a result, or false in case of error (or invalid state)</returns> private void HandleCompletion(bool fromCallback) { // note: if fromCallback is true, we are running on the network thread // this means that we have to signal the TCS from the threadpool, if not continuations on the task may run inline. // this is very frequent when we are called with await, or ContinueWith(..., TaskContinuationOptions.ExecuteSynchronously) if (HasAnyFlags(FdbFuture.Flags.DISPOSED | FdbFuture.Flags.COMPLETED)) { return; } #if DEBUG_FUTURES var sw = Stopwatch.StartNew(); #endif try { var handle = m_handle; if (handle != null && !handle.IsClosed && !handle.IsInvalid) { UnregisterCancellationRegistration(); FdbError err = FdbNative.FutureGetError(handle); if (Fdb.Failed(err)) { // it failed... #if DEBUG_FUTURES Debug.WriteLine("Future<" + typeof(T).Name + "> has FAILED: " + err); #endif if (err != FdbError.OperationCancelled) { // get the exception from the error code var ex = Fdb.MapToException(err); SetFaulted(ex, fromCallback); return; } //else: will be handle below } else { // it succeeded... // try to get the result... #if DEBUG_FUTURES Debug.WriteLine("Future<" + typeof(T).Name + "> has completed successfully"); #endif var selector = m_resultSelector; if (selector != null) { //note: result selector will execute from network thread, but this should be our own code that only calls into some fdb_future_get_XXXX(), which should be safe... var result = selector(handle); SetResult(result, fromCallback); return; } //else: it will be handled below } } // most probably the future was cancelled or we are shutting down... SetCanceled(fromCallback); } catch (Exception e) { // something went wrong if (e is ThreadAbortException) { SetCanceled(fromCallback); throw; } SetFaulted(e, fromCallback); } finally { #if DEBUG_FUTURES sw.Stop(); Debug.WriteLine("Future<" + typeof(T).Name + "> callback completed in " + sw.Elapsed.TotalMilliseconds.ToString() + " ms"); #endif TryCleanup(); } }
public OnErrorCommand(FdbError code) { this.Code = code; }
/// <inheritdoc /> public void AddConflictRange(ReadOnlySpan <byte> beginKeyInclusive, ReadOnlySpan <byte> endKeyExclusive, FdbConflictRangeType type) { FdbError err = FdbNative.TransactionAddConflictRange(m_handle, beginKeyInclusive, endKeyExclusive, type); Fdb.DieOnError(err); }
public static async Task AssertThrowsFdbErrorAsync([NotNull] Func<Task> asyncTest, FdbError expectedCode, string message = null, object[] args = null) { try { await asyncTest(); Assert.Fail(message, args); } catch (AssertionException) { throw; } catch (Exception e) { Assert.That(e, Is.InstanceOf<FdbException>().With.Property("Code").EqualTo(expectedCode), message, args); } }
public static extern FutureHandle fdb_transaction_on_error(TransactionHandle transaction, FdbError error);
/// <summary>Returns true if the error code represents a success</summary> public static bool Success(FdbError code) { return(code == FdbError.Success); }
Task IFdbReadOnlyTransaction.OnErrorAsync(FdbError code) { throw new NotSupportedException("You cannot retry on a Snapshot view of a transaction."); }
public virtual Task OnErrorAsync(FdbError code) { ThrowIfDisposed(); return m_transaction.OnErrorAsync(code); }
public static extern IntPtr fdb_get_error(FdbError code);
/// <summary>Returns true if the error code represents a success</summary> public static bool Success(FdbError code) { return code == FdbError.Success; }
public Task OnErrorAsync(FdbError code, CancellationToken ct) { var future = FdbNative.TransactionOnError(m_handle, code); return(FdbFuture.CreateTaskFromHandle <object?>(future, (h) => { ResetInternal(); return null; }, ct)); }
/// <summary>fdb_get_error</summary> public static string GetError(FdbError code) { return(ToManagedString(NativeMethods.fdb_get_error(code))); }