diff --git a/FoundationDB.Client/FdbDatabase.cs b/FoundationDB.Client/FdbDatabase.cs
index bf6f9f3f1..3197c4eac 100644
--- a/FoundationDB.Client/FdbDatabase.cs
+++ b/FoundationDB.Client/FdbDatabase.cs
@@ -39,6 +39,7 @@ namespace FoundationDB.Client
using FoundationDB.Client.Core;
using FoundationDB.Client.Native;
using FoundationDB.DependencyInjection;
+ using FoundationDB.Filters.Logging;
/// FoundationDB database session handle
/// An instance of this class can be used to create any number of concurrent transactions that will read and/or write to this particular database.
@@ -185,6 +186,11 @@ internal FdbTransaction CreateNewTransaction(FdbOperationContext context)
if (m_defaultTimeout != 0) trans.Timeout = m_defaultTimeout;
if (m_defaultRetryLimit != 0) trans.RetryLimit = m_defaultRetryLimit;
if (m_defaultMaxRetryDelay != 0) trans.MaxRetryDelay = m_defaultMaxRetryDelay;
+ if (this.DefaultLogHandler != null)
+ {
+ trans.SetLogHandler(this.DefaultLogHandler, this.DefaultLogOptions);
+ }
+
// flag as ready
trans.State = FdbTransaction.STATE_READY;
return trans;
@@ -233,6 +239,16 @@ internal void UnregisterTransaction(FdbTransaction transaction)
//TODO: compare removed value with the specified transaction to ensure it was the correct one?
}
+ public void SetDefaultLogHandler(Action handler, FdbLoggingOptions options = default)
+ {
+ this.DefaultLogHandler = handler;
+ this.DefaultLogOptions = options;
+ }
+
+ private Action DefaultLogHandler { get; set; }
+
+ private FdbLoggingOptions DefaultLogOptions { get; set; }
+
#endregion
#region Transactionals...
diff --git a/FoundationDB.Client/FdbTransaction.Snapshot.cs b/FoundationDB.Client/FdbTransaction.Snapshot.cs
index 632e1d30d..9b52849b0 100644
--- a/FoundationDB.Client/FdbTransaction.Snapshot.cs
+++ b/FoundationDB.Client/FdbTransaction.Snapshot.cs
@@ -33,6 +33,7 @@ namespace FoundationDB.Client
using System.Threading;
using System.Threading.Tasks;
using Doxense.Diagnostics.Contracts;
+ using FoundationDB.Filters.Logging;
/// Wraps an FDB_TRANSACTION handle
public partial class FdbTransaction
@@ -185,8 +186,6 @@ public Task GetRangeAsync(KeySelector beginInclusive,
return m_parent.m_handler.GetRangeAsync(beginInclusive, endExclusive, limit, reverse, targetBytes, mode, read, iteration, snapshot: true, ct: m_parent.m_cancellation);
}
-
-
public FdbRangeQuery> GetRange(KeySelector beginInclusive, KeySelector endExclusive, FdbRangeOptions? options = null)
{
return m_parent.GetRangeCore(beginInclusive, endExclusive, options, snapshot: true, kv => kv);
@@ -260,6 +259,16 @@ void IDisposable.Dispose()
{
// NO-OP
}
+
+
+ public FdbTransactionLog? Log => m_parent.Log;
+
+ public bool IsLogged() => m_parent.IsLogged();
+
+ public void StopLogging() => m_parent.StopLogging();
+
+ public void Annotate(string comment) => m_parent.Annotate(comment);
+
}
}
diff --git a/FoundationDB.Client/FdbTransaction.cs b/FoundationDB.Client/FdbTransaction.cs
index dae0e3af1..0d372aa2d 100644
--- a/FoundationDB.Client/FdbTransaction.cs
+++ b/FoundationDB.Client/FdbTransaction.cs
@@ -43,6 +43,7 @@ namespace FoundationDB.Client
using Doxense.Threading.Tasks;
using FoundationDB.Client.Core;
using FoundationDB.Client.Native;
+ using FoundationDB.Filters.Logging;
using JetBrains.Annotations;
/// FoundationDB transaction handle.
@@ -97,6 +98,11 @@ public sealed partial class FdbTransaction : IFdbTransaction
/// Random token (but constant per transaction retry) used to generate incomplete VersionStamps
private ulong m_versionStampToken;
+ /// Contains the log used by this transaction (or null if logging is disabled)
+ private FdbTransactionLog? m_log;
+
+ private Action? m_logHandler;
+
#endregion
#region Constructors...
@@ -199,6 +205,7 @@ public void SetOption(FdbTransactionOption option)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "SetOption", $"Setting transaction option {option.ToString()}");
+ m_log?.Annotate($"SetOption({option})");
m_handler.SetOption(option, default);
}
@@ -209,6 +216,7 @@ public void SetOption(FdbTransactionOption option, string value)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "SetOption", $"Setting transaction option {option.ToString()} to '{value ?? ""}'");
+ m_log?.Annotate($"SetOption({option}, \"{value}\")");
var data = FdbNative.ToNativeString(value.AsSpan(), nullTerminated: false);
m_handler.SetOption(option, data.Span);
}
@@ -220,6 +228,7 @@ public void SetOption(FdbTransactionOption option, ReadOnlySpan value)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "SetOption", $"Setting transaction option {option.ToString()} to '{value.ToString() ?? ""}'");
+ m_log?.Annotate($"SetOption({option}, \"{value.ToString()}\")");
var data = FdbNative.ToNativeString(value, nullTerminated: false);
m_handler.SetOption(option, data.Span);
}
@@ -231,6 +240,8 @@ public void SetOption(FdbTransactionOption option, long value)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "SetOption", $"Setting transaction option {option.ToString()} to {value}");
+ m_log?.Annotate($"SetOption({option}, \"{value:N0}\")");
+
// Spec says: "If the option is documented as taking an Int parameter, value must point to a signed 64-bit integer (little-endian), and value_length must be 8."
Span tmp = stackalloc byte[8];
UnsafeHelpers.WriteFixed64(tmp, (ulong) value);
@@ -239,11 +250,53 @@ public void SetOption(FdbTransactionOption option, long value)
#endregion
+ #region Logging...
+
+ /// Log of all operations performed on this transaction (if logging was enabled on the database or transaction)
+ public FdbTransactionLog? Log => m_log;
+
+ /// Return true if logging is enabled on this transaction
+ ///
+ /// If logging is enabled, the transaction will track all the operations performed by this transaction until it completes.
+ /// The log can be accessed via the property.
+ /// Comments can be added via the method.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsLogged() => m_log != null;
+
+ /// Add a comment to the transaction log
+ /// Line of text that will be added to the log
+ /// This method does nothing if logging is disabled. To prevent unnecessary allocations, you may check first
+ /// if (tr.IsLogged()) tr.Annonate($"Reticulated {splines.Count} splines");
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Annotate(string comment)
+ {
+ m_log?.Annotate(comment);
+ }
+
+ /// If logging was previously enabled on this transaction, clear the log and stop logging any new operations
+ /// Any log handler attached to this transaction will not be called
+ public void StopLogging()
+ {
+ m_log = null;
+ }
+
+ internal void SetLogHandler(Action handler, FdbLoggingOptions options)
+ {
+ if (m_log != null) throw new InvalidOperationException("There is already a log handler attached to this transaction.");
+ m_logHandler = handler;
+ m_log = new FdbTransactionLog(options);
+ m_log.Start(this);
+ }
+
+ #endregion
+
#region Versions...
private Task? CachedReadVersion;
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Task GetReadVersionAsync()
{
// can be called after the transaction has been committed
@@ -252,11 +305,29 @@ public Task GetReadVersionAsync()
}
/// Get the read version when it is not in cache
+ [MethodImpl(MethodImplOptions.NoInlining)]
private Task GetReadVersionSlow()
{
lock (this)
{
- return this.CachedReadVersion ??= m_handler.GetReadVersionAsync(m_cancellation);
+ return this.CachedReadVersion ??= FetchReadVersionInternal();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private Task FetchReadVersionInternal()
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetReadVersionCommand(),
+ (tr, cmd) => tr.m_handler.GetReadVersionAsync(tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetReadVersionAsync(m_cancellation);
}
}
@@ -274,6 +345,7 @@ public void SetReadVersion(long version)
{
EnsureCanRead();
+ m_log?.Annotate($"Set read version to {version:N0}");
m_handler.SetReadVersion(version);
}
@@ -365,7 +437,7 @@ public void TouchMetadataVersionKey(Slice key = default)
try
{
// this can fail if the value has been changed earlier in the transaction!
- value = await m_handler.GetAsync(key.Span, snapshot: true, m_cancellation).ConfigureAwait(false);
+ value = await PerformGetOperation(key.Span, snapshot: true).ConfigureAwait(false);
}
catch (FdbException e)
{
@@ -409,9 +481,8 @@ internal void SetMetadataVersionKey(Slice key)
cache[key] = (PoisonedMetadataVersion, false);
// update the key with a new versionstamp
- m_handler.Atomic(key.Span, Fdb.System.MetadataVersionValue.Span, FdbMutationType.VersionStampedValue);
+ PerformAtomicOperation(key.Span, Fdb.System.MetadataVersionValue.Span, FdbMutationType.VersionStampedValue);
}
-
}
///
@@ -503,9 +574,24 @@ public Task GetAsync(ReadOnlySpan key)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "GetAsync", $"Getting value for '{key.ToString()}'");
#endif
- return m_handler.GetAsync(key, snapshot: false, ct: m_cancellation);
+ return PerformGetOperation(key, snapshot: false);
}
+ private Task PerformGetOperation(ReadOnlySpan key, bool snapshot)
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetCommand(m_log.Grab(key)) { Snapshot = snapshot },
+ (tr, cmd) => tr.m_handler.GetAsync(cmd.Key.Span, cmd.Snapshot, tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetAsync(key, snapshot: snapshot, m_cancellation);
+ }
+ }
#endregion
@@ -525,7 +611,23 @@ public Task GetValuesAsync(Slice[] keys)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "GetValuesAsync", $"Getting batch of {keys.Length} values ...");
#endif
- return m_handler.GetValuesAsync(keys, snapshot: false, ct: m_cancellation);
+ return PerformGetValuesOperation(keys, snapshot: false);
+ }
+
+ private Task PerformGetValuesOperation(Slice[] keys, bool snapshot)
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetValuesCommand(m_log.Grab(keys)) { Snapshot = snapshot },
+ (tr, cmd) => tr.m_handler.GetValuesAsync(cmd.Keys.AsSpan(), cmd.Snapshot, tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetValuesAsync(keys, snapshot: snapshot, m_cancellation);
+ }
}
#endregion
@@ -553,7 +655,40 @@ public Task GetRangeAsync(
// The iteration value is only needed when in iterator mode, but then it should start from 1
if (iteration == 0) iteration = 1;
- return m_handler.GetRangeAsync(beginInclusive, endExclusive, limit, reverse, targetBytes, mode, read, iteration, snapshot: false, ct: m_cancellation);
+ return PerformGetRangeOperation(beginInclusive, endExclusive, snapshot: false, limit, reverse, targetBytes, mode, read, iteration);
+ }
+
+ private Task PerformGetRangeOperation(
+ KeySelector beginInclusive,
+ KeySelector endExclusive,
+ bool snapshot,
+ int limit,
+ bool reverse,
+ int targetBytes,
+ FdbStreamingMode mode,
+ FdbReadMode read,
+ int iteration)
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetRangeCommand(
+ m_log.Grab(beginInclusive),
+ m_log.Grab(endExclusive),
+ new FdbRangeOptions(limit, reverse, targetBytes, mode, read),
+ iteration
+ )
+ {
+ Snapshot = snapshot
+ },
+ (tr, cmd) => tr.m_handler.GetRangeAsync(cmd.Begin, cmd.End, cmd.Options.Limit.Value, cmd.Options.Reverse.Value, cmd.Options.TargetBytes.Value, cmd.Options.Mode.Value, cmd.Options.Read.Value, cmd.Iteration, cmd.Snapshot, tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetRangeAsync(beginInclusive, endExclusive, limit, reverse, targetBytes, mode, read, iteration, snapshot, m_cancellation);
+ }
}
#endregion
@@ -606,7 +741,23 @@ public Task GetKeyAsync(KeySelector selector)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "GetKeyAsync", $"Getting key '{selector.ToString()}'");
#endif
- return m_handler.GetKeyAsync(selector, snapshot: false, ct: m_cancellation);
+ return PerformGetKeyOperation(selector, snapshot: false);
+ }
+
+ private Task PerformGetKeyOperation(KeySelector selector, bool snapshot)
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetKeyCommand(m_log.Grab(selector)) { Snapshot = snapshot },
+ (tr, cmd) => tr.m_handler.GetKeyAsync(cmd.Selector, cmd.Snapshot, tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetKeyAsync(selector, snapshot: snapshot, m_cancellation);
+ }
}
#endregion
@@ -627,7 +778,24 @@ public Task GetKeysAsync(KeySelector[] selectors)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "GetKeysAsync", $"Getting batch of {selectors.Length} keys ...");
#endif
- return m_handler.GetKeysAsync(selectors, snapshot: false, ct: m_cancellation);
+ return PerformGetKeysOperation(selectors, snapshot: false);
+ }
+
+
+ private Task PerformGetKeysOperation(KeySelector[] selectors, bool snapshot)
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetKeysCommand(m_log.Grab(selectors)) { Snapshot = snapshot },
+ (tr, cmd) => tr.m_handler.GetKeysAsync(cmd.Selectors, cmd.Snapshot, tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetKeysAsync(selectors, snapshot: snapshot, m_cancellation);
+ }
}
#endregion
@@ -645,8 +813,23 @@ public void Set(ReadOnlySpan key, ReadOnlySpan value)
#if DEBUG
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "Set", $"Setting '{FdbKey.Dump(key)}' = {Slice.Dump(value)}");
#endif
+ PerformSetOperation(key, value);
+ }
- m_handler.Set(key, value);
+ private void PerformSetOperation(ReadOnlySpan key, ReadOnlySpan value)
+ {
+ if (m_log != null)
+ {
+ m_log.Execute(
+ this,
+ new FdbTransactionLog.SetCommand(m_log.Grab(key), m_log.Grab(value)),
+ (tr, cmd) => tr.m_handler.Set(cmd.Key.Span, cmd.Value.Span)
+ );
+ }
+ else
+ {
+ m_handler.Set(key, value);
+ }
}
#endregion
@@ -785,7 +968,24 @@ public void Atomic(ReadOnlySpan key, ReadOnlySpan param, FdbMutation
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "AtomicCore", $"Atomic {mutation.ToString()} on '{FdbKey.Dump(key)}' = {Slice.Dump(param)}");
#endif
- m_handler.Atomic(key, param, mutation);
+ PerformAtomicOperation(key, param, mutation);
+ }
+
+ private void PerformAtomicOperation(ReadOnlySpan key, ReadOnlySpan param, FdbMutationType type)
+ {
+ if (m_log != null)
+ {
+ m_log.Execute(
+ this,
+ new FdbTransactionLog.AtomicCommand(m_log.Grab(key), m_log.Grab(param), type),
+ (tr, cmd) => tr.m_handler.Atomic(cmd.Key.Span, cmd.Param.Span, cmd.Mutation)
+ );
+ }
+ else
+ {
+ m_handler.Atomic(key, param, type);
+
+ }
}
#endregion
@@ -803,7 +1003,23 @@ public void Clear(ReadOnlySpan key)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "Clear", $"Clearing '{FdbKey.Dump(key)}'");
#endif
- m_handler.Clear(key);
+ PerformClearOperation(key);
+ }
+
+ private void PerformClearOperation(ReadOnlySpan key)
+ {
+ if (m_log != null)
+ {
+ m_log.Execute(
+ this,
+ new FdbTransactionLog.ClearCommand(m_log.Grab(key)),
+ (tr, cmd) => tr.m_handler.Clear(cmd.Key.Span)
+ );
+ }
+ else
+ {
+ m_handler.Clear(key);
+ }
}
#endregion
@@ -822,7 +1038,23 @@ public void ClearRange(ReadOnlySpan beginKeyInclusive, ReadOnlySpan
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "ClearRange", $"Clearing Range '{beginKeyInclusive.ToString()}' <= k < '{endKeyExclusive.ToString()}'");
#endif
- m_handler.ClearRange(beginKeyInclusive, endKeyExclusive);
+ PerformClearRangeOperation(beginKeyInclusive, endKeyExclusive);
+ }
+
+ private void PerformClearRangeOperation(ReadOnlySpan beginKeyInclusive, ReadOnlySpan endKeyExclusive)
+ {
+ if (m_log != null)
+ {
+ m_log.Execute(
+ this,
+ new FdbTransactionLog.ClearRangeCommand(m_log.Grab(beginKeyInclusive), m_log.Grab(endKeyExclusive)),
+ (tr, cmd) => tr.m_handler.ClearRange(cmd.Begin.Span, cmd.End.Span)
+ );
+ }
+ else
+ {
+ m_handler.ClearRange(beginKeyInclusive, endKeyExclusive);
+ }
}
#endregion
@@ -841,7 +1073,23 @@ public void AddConflictRange(ReadOnlySpan beginKeyInclusive, ReadOnlySpan<
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "AddConflictRange", String.Format("Adding {2} conflict range '{0}' <= k < '{1}'", beginKeyInclusive.ToString(), endKeyExclusive.ToString(), type.ToString()));
#endif
- m_handler.AddConflictRange(beginKeyInclusive, endKeyExclusive, type);
+ PerformAddConflictRangeOperation(beginKeyInclusive, endKeyExclusive, type);
+ }
+
+ private void PerformAddConflictRangeOperation(ReadOnlySpan beginKeyInclusive, ReadOnlySpan endKeyExclusive, FdbConflictRangeType type)
+ {
+ if (m_log != null)
+ {
+ m_log.Execute(
+ this,
+ new FdbTransactionLog.AddConflictRangeCommand(m_log.Grab(beginKeyInclusive), m_log.Grab(endKeyExclusive), type),
+ (tr, cmd) => tr.m_handler.AddConflictRange(cmd.Begin.Span, cmd.End.Span, cmd.Type)
+ );
+ }
+ else
+ {
+ m_handler.AddConflictRange(beginKeyInclusive, endKeyExclusive, type);
+ }
}
#endregion
@@ -859,7 +1107,23 @@ public Task GetAddressesForKeyAsync(ReadOnlySpan key)
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "GetAddressesForKeyAsync", $"Getting addresses for key '{FdbKey.Dump(key)}'");
#endif
- return m_handler.GetAddressesForKeyAsync(key, ct: m_cancellation);
+ return PerformGetAddressesForKeyOperation(key);
+ }
+
+ private Task PerformGetAddressesForKeyOperation(ReadOnlySpan key)
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetAddressesForKeyCommand(m_log.Grab(key)),
+ (tr, cmd) => tr.m_handler.GetAddressesForKeyAsync(cmd.Key.Span, tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetAddressesForKeyAsync(key, m_cancellation);
+ }
}
#endregion
@@ -871,7 +1135,23 @@ public Task GetApproximateSizeAsync()
{
EnsureCanWrite();
- return m_handler.GetApproximateSizeAsync(m_cancellation);
+ return PerformGetApproximateSizeOperation();
+ }
+
+ private Task PerformGetApproximateSizeOperation()
+ {
+ if (m_log != null)
+ {
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.GetApproximateSizeCommand(),
+ (tr, cmd) => tr.m_handler.GetApproximateSizeAsync(tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.GetApproximateSizeAsync(m_cancellation);
+ }
}
#endregion
@@ -905,6 +1185,38 @@ public async Task CommitAsync()
}
}
+ private Task PerformCommitOperation()
+ {
+ if (m_log != null)
+ {
+ int size = this.Size;
+ m_log.CommitSize = size;
+ m_log.TotalCommitSize += size;
+ m_log.Attempts++;
+
+ Task? tvs = m_log.RequiresVersionStamp ? m_handler.GetVersionStampAsync(m_cancellation) : null;
+
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.CommitCommand(),
+ (tr, cmd) => tr.m_handler.CommitAsync(tr.m_cancellation),
+ (tr, cmd, log) =>
+ {
+ log.CommittedUtc = DateTimeOffset.UtcNow;
+ var cv = tr.GetCommittedVersion();
+ log.CommittedVersion = cv;
+ cmd.CommitVersion = cv;
+ if (tvs != null) log.VersionStamp = tvs.GetAwaiter().GetResult();
+ }
+ );
+
+ }
+ else
+ {
+ return m_handler.CommitAsync(m_cancellation);
+ }
+ }
+
#endregion
#region Watches...
@@ -937,7 +1249,13 @@ public FdbWatch Watch(ReadOnlySpan key, CancellationToken ct)
// Since Task does not expose any cancellation mechanism by itself (and we don't want to force the caller to create a CancellationTokenSource every time),
// we will return the FdbWatch that wraps the FdbFuture directly, since it knows how to cancel itself.
- return m_handler.Watch(mkey, ct);
+ return PerformWatchOperation(mkey, ct);
+ }
+
+ private FdbWatch PerformWatchOperation(Slice key, CancellationToken ct)
+ {
+ m_log?.AddOperation(new FdbTransactionLog.WatchCommand(m_log.Grab(key)));
+ return m_handler.Watch(key, ct);
}
#endregion
@@ -949,7 +1267,7 @@ public async Task OnErrorAsync(FdbError code)
{
EnsureCanRetry();
- await m_handler.OnErrorAsync(code, ct: m_cancellation).ConfigureAwait(false);
+ await PerformOnErrorOperation(code).ConfigureAwait(false);
// If fdb_transaction_on_error succeeds, that means that the transaction has been reset and is usable again
var state = this.State;
@@ -958,6 +1276,24 @@ public async Task OnErrorAsync(FdbError code)
RestoreDefaultSettings();
}
+ private Task PerformOnErrorOperation(FdbError code)
+ {
+
+ if (m_log != null)
+ {
+ m_log.RequiresVersionStamp = false;
+ return m_log.ExecuteAsync(
+ this,
+ new FdbTransactionLog.OnErrorCommand(code),
+ (tr, cmd) => tr.m_handler.OnErrorAsync(cmd.Code, tr.m_cancellation)
+ );
+ }
+ else
+ {
+ return m_handler.OnErrorAsync(code, ct: m_cancellation);
+ }
+ }
+
#endregion
#region Reset/Rollback/Cancel...
@@ -1002,7 +1338,8 @@ public void Reset()
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "Reset", "Resetting transaction");
- m_handler.Reset();
+ PerformResetOperation();
+
m_state = STATE_READY;
RestoreDefaultSettings();
@@ -1010,6 +1347,23 @@ public void Reset()
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "Reset", "Transaction has been reset");
}
+ private void PerformResetOperation()
+ {
+ if (m_log != null)
+ {
+ m_log.RequiresVersionStamp = false;
+ m_log.Execute(
+ this,
+ new FdbTransactionLog.ResetCommand(),
+ (tr, cmd) => tr.m_handler.Reset()
+ );
+ }
+ else
+ {
+ m_handler.Reset();
+ }
+ }
+
///
public void Cancel()
{
@@ -1029,11 +1383,27 @@ public void Cancel()
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "Cancel", "Canceling transaction...");
- m_handler.Cancel();
+ PerformCancelOperation();
if (Logging.On && Logging.IsVerbose) Logging.Verbose(this, "Cancel", "Transaction has been canceled");
}
+ private void PerformCancelOperation()
+ {
+ if (m_log != null)
+ {
+ m_log.Execute(
+ this,
+ new FdbTransactionLog.CancelCommand(),
+ (tr, cmd) => tr.m_handler.Cancel()
+ );
+ }
+ else
+ {
+ m_handler.Cancel();
+ }
+ }
+
#endregion
#region IDisposable...
@@ -1067,6 +1437,7 @@ public void EnsureCanWrite()
}
/// Throws if the transaction is not safely retryable
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureCanRetry()
{
EnsureStillValid(allowFromNetworkThread: false, allowFailedState: true);
@@ -1176,6 +1547,26 @@ public void Dispose()
context.Dispose();
}
m_cts.Dispose();
+
+ if (m_log?.Completed == false)
+ {
+ m_log.Stop(this);
+ if (m_logHandler != null)
+ {
+ try
+ {
+ m_logHandler.Invoke(m_log);
+ }
+#if DEBUG
+ catch(Exception e)
+ {
+ System.Diagnostics.Debug.WriteLine("Logged transaction handler failed: " + e.ToString());
+ }
+#else
+ catch { }
+#endif
+ }
+ }
}
}
}
diff --git a/FoundationDB.Client/Filters/FdbDatabaseFilter.cs b/FoundationDB.Client/Filters/FdbDatabaseFilter.cs
deleted file mode 100644
index 1d068c647..000000000
--- a/FoundationDB.Client/Filters/FdbDatabaseFilter.cs
+++ /dev/null
@@ -1,403 +0,0 @@
-#region BSD License
-/* Copyright (c) 2013-2020, Doxense SAS
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of Doxense nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-#endregion
-
-namespace FoundationDB.Filters
-{
- using System;
- using System.Diagnostics;
- using System.Runtime.CompilerServices;
- using System.Threading;
- using System.Threading.Tasks;
- using Doxense.Diagnostics.Contracts;
- using FoundationDB.Client;
- using JetBrains.Annotations;
-
- /// Base class for simple database filters
- [DebuggerDisplay("ClusterFile={m_database.ClusterFile}")]
- public abstract class FdbDatabaseFilter : IFdbDatabase
- {
- #region Private Members...
-
- /// Inner database
- protected readonly IFdbDatabase m_database;
-
- /// If true, forces the inner database to be read only
- protected readonly bool m_readOnly;
-
- /// If true, dispose the inner database when we get disposed
- protected readonly bool m_owner;
-
- /// If true, we have been disposed
- protected bool m_disposed;
-
- /// Wrapper for the inner db's Root property
- protected FdbDirectorySubspaceLocation? m_root;
-
- #endregion
-
- #region Constructors...
-
- protected FdbDatabaseFilter(IFdbDatabase database, bool forceReadOnly, bool ownsDatabase)
- {
- Contract.NotNull(database, nameof(database));
-
- m_database = database;
- m_readOnly = forceReadOnly || database.IsReadOnly;
- m_owner = ownsDatabase;
- }
-
- #endregion
-
- #region Public Properties...
-
- /// Database instance configured to read and write data from this partition
- protected IFdbDatabase Database => m_database;
-
- internal IFdbDatabase GetInnerDatabase()
- {
- return m_database;
- }
-
- ///
- [Obsolete("This property is not supported anymore and will always return \"DB\".")]
- public string Name => m_database.Name;
-
- ///
- public string? ClusterFile => m_database.ClusterFile;
-
- ///
- public CancellationToken Cancellation => m_database.Cancellation;
-
- ///
- public virtual FdbDirectorySubspaceLocation Root
- {
- get
- {
- if (m_root == null || !object.ReferenceEquals(m_root, m_database.Root))
- {
- m_root = m_database.Root;
- }
- return m_root;
- }
- }
-
- ///
- public virtual FdbDirectoryLayer DirectoryLayer => m_database.DirectoryLayer;
-
- ///
- public virtual bool IsReadOnly => m_readOnly;
-
- #endregion
-
- #region Transactionals...
-
- public virtual ValueTask BeginTransactionAsync(FdbTransactionMode mode, CancellationToken ct = default, FdbOperationContext? context = null)
- {
- ThrowIfDisposed();
-
- // enforce read-only mode!
- if (m_readOnly) mode |= FdbTransactionMode.ReadOnly;
-
- if (context == null)
- {
- context = new FdbOperationContext(this, mode, ct);
- }
-
- return m_database.BeginTransactionAsync(mode, ct, context);
- }
-
- #region IFdbReadOnlyRetryable...
-
- private Task ExecuteReadOnlyAsync(TState state, Delegate handler, Delegate? success, CancellationToken ct)
- {
- Contract.NotNull(handler, nameof(handler));
- if (ct.IsCancellationRequested) return Task.FromCanceled(ct);
- ThrowIfDisposed();
-
- var context = new FdbOperationContext(this, FdbTransactionMode.ReadOnly | FdbTransactionMode.InsideRetryLoop, ct);
- return FdbOperationContext.ExecuteInternal(context, state, handler, success);
- }
-
- ///
- public Task ReadAsync(Func handler, CancellationToken ct)
- {
- return ExecuteReadOnlyAsync