From 8e81edfef977ecdf4ff1110c7087f95f4c742b41 Mon Sep 17 00:00:00 2001 From: Christophe Chevalier Date: Thu, 14 May 2020 14:22:50 +0200 Subject: [PATCH] Remove FdbDatabaseFilter and re-implement logging directly inside the FdbTransaction implementation - The previous approach (wrapping transactions inside filters) does work well if the inner class needs to call other methods (it will not go through the wrapper!) - Since only the logging filter was used in practice, it has been moved inside the transaction implementation itself, and the current approach to filter has been removed - Filters are still a good idea, but should probably be implemented differently (maybe at the handler level?) --- FoundationDB.Client/FdbDatabase.cs | 16 + .../FdbTransaction.Snapshot.cs | 13 +- FoundationDB.Client/FdbTransaction.cs | 431 +++++++++- .../Filters/FdbDatabaseFilter.cs | 403 ---------- .../Filters/FdbFilterExtensions.cs | 57 -- .../Filters/FdbTransactionFilter.cs | 580 -------------- .../Filters/Logging/FdbLoggedDatabase.cs | 70 -- .../Filters/Logging/FdbLoggedTransaction.cs | 640 --------------- .../Filters/Logging/FdbLoggingExtensions.cs | 160 ---- .../Filters/PrefixRewriterTransaction.cs | 177 ----- .../Filters/ReadOnlyTransactionFilter.cs | 42 - FoundationDB.Client/IFdbDatabase.cs | 6 + .../IFdbReadOnlyTransaction.cs | 22 + FoundationDB.Client/IFdbTransaction.cs | 1 + .../Tracing/FdbLoggingExtensions.cs | 93 +++ .../FdbTransactionLog.Commands.cs | 24 + .../Logging => Tracing}/FdbTransactionLog.cs | 194 +++++ .../Messaging/WorkerPoolTest.cs | 6 +- FoundationDB.Samples/Program.cs | 8 +- FoundationDB.Tests/ExoticTestCases.cs | 740 +++++++++--------- .../Filters/LoggingFilterFacts.cs | 13 +- FoundationDB.Tests/Layers/DirectoryFacts.cs | 265 +++---- FoundationDB.Tests/TestHelpers.cs | 13 +- FoundationDB.Tests/TransactionFacts.cs | 127 ++- 24 files changed, 1362 insertions(+), 2739 deletions(-) delete mode 100644 FoundationDB.Client/Filters/FdbDatabaseFilter.cs delete mode 100644 FoundationDB.Client/Filters/FdbFilterExtensions.cs delete mode 100644 FoundationDB.Client/Filters/FdbTransactionFilter.cs delete mode 100644 FoundationDB.Client/Filters/Logging/FdbLoggedDatabase.cs delete mode 100644 FoundationDB.Client/Filters/Logging/FdbLoggedTransaction.cs delete mode 100644 FoundationDB.Client/Filters/Logging/FdbLoggingExtensions.cs delete mode 100644 FoundationDB.Client/Filters/PrefixRewriterTransaction.cs delete mode 100644 FoundationDB.Client/Filters/ReadOnlyTransactionFilter.cs create mode 100644 FoundationDB.Client/Tracing/FdbLoggingExtensions.cs rename FoundationDB.Client/{Filters/Logging => Tracing}/FdbTransactionLog.Commands.cs (97%) rename FoundationDB.Client/{Filters/Logging => Tracing}/FdbTransactionLog.cs (81%) 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(null, handler, null, ct); - } - - /// - public Task ReadAsync(TState state, Func handler, CancellationToken ct) - { - return ExecuteReadOnlyAsync(state, handler, null, ct); - } - - /// - public Task ReadAsync(Func> handler, CancellationToken ct) - { - return ExecuteReadOnlyAsync(null, handler, null, ct); - } - - /// - public Task ReadAsync(TState state, Func> handler, CancellationToken ct) - { - return ExecuteReadOnlyAsync(state, handler, null, ct); - } - - /// - public Task ReadAsync(Func> handler, Action success, CancellationToken ct) - { - return ExecuteReadOnlyAsync(null, handler, success, ct); - } - - /// - public Task ReadAsync(Func> handler, Func success, CancellationToken ct) - { - return ExecuteReadOnlyAsync(null, handler, success, ct); - } - - /// - public Task ReadAsync(Func> handler, Func> success, CancellationToken ct) - { - return ExecuteReadOnlyAsync(null, handler, success, ct); - } - - /// - public Task ReadAsync(TState state, Func> handler, Func> success, CancellationToken ct) - { - return ExecuteReadOnlyAsync(state, handler, success, ct); - } - - #endregion - - #region IFdbRetryable... - - private Task ExecuteReadWriteAsync(TState state, Delegate handler, Delegate? success, CancellationToken ct) - { - Contract.NotNull(handler, nameof(handler)); - if (ct.IsCancellationRequested) return Task.FromCanceled(ct); - ThrowIfDisposed(); - if (m_readOnly) throw new InvalidOperationException("Cannot mutate a read-only database."); - - var context = new FdbOperationContext(this, FdbTransactionMode.Default | FdbTransactionMode.InsideRetryLoop, ct); - return FdbOperationContext.ExecuteInternal(context, state, handler, success); - } - - /// - public Task WriteAsync(Action handler, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, null, ct); - } - - /// - public Task WriteAsync(TState state, Action handler, CancellationToken ct) - { - return ExecuteReadWriteAsync(state, handler, null, ct); - } - - /// - public Task WriteAsync(Func handler, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, null, ct); - } - - /// - public Task WriteAsync(TState state, Func handler, CancellationToken ct) - { - return ExecuteReadWriteAsync(state, handler, null, ct); - } - - /// - public Task WriteAsync(Action handler, Action success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task WriteAsync(Action handler, Func success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task ReadWriteAsync(Action handler, Func success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task WriteAsync(Func handler, Action success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task WriteAsync(Func handler, Func success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task ReadWriteAsync(Func> handler, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, null, ct); - } - - /// - public Task ReadWriteAsync(TState state, Func> handler, CancellationToken ct) - { - return ExecuteReadWriteAsync(state, handler, null, ct); - } - - /// - public Task ReadWriteAsync(Func> handler, Action success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task ReadWriteAsync(Func handler, Func> success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task ReadWriteAsync(Func handler, Func success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task ReadWriteAsync(Func> handler, Func success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task ReadWriteAsync(Func> handler, Func> success, CancellationToken ct) - { - return ExecuteReadWriteAsync(null, handler, success, ct); - } - - /// - public Task ReadWriteAsync(TState state, Func> handler, Func> success, CancellationToken ct) - { - return ExecuteReadWriteAsync(state, handler, success, ct); - } - - #endregion - - #endregion - - #region Options... - - public virtual void SetOption(FdbDatabaseOption option) - { - ThrowIfDisposed(); - m_database.SetOption(option); - } - - public virtual void SetOption(FdbDatabaseOption option, string? value) - { - ThrowIfDisposed(); - m_database.SetOption(option, value); - } - - public virtual void SetOption(FdbDatabaseOption option, long value) - { - ThrowIfDisposed(); - m_database.SetOption(option, value); - } - - public int DefaultTimeout - { - get => m_database.DefaultTimeout; - set - { - ThrowIfDisposed(); - m_database.DefaultTimeout = value; - } - } - - public int DefaultRetryLimit - { - get => m_database.DefaultRetryLimit; - set - { - ThrowIfDisposed(); - m_database.DefaultRetryLimit = value; - } - } - - public int DefaultMaxRetryDelay - { - get => m_database.DefaultMaxRetryDelay; - set - { - ThrowIfDisposed(); - m_database.DefaultMaxRetryDelay = value; - } - } - - #endregion - - #region IDisposable Members... - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ThrowIfDisposed() - { - if (m_disposed) throw ThrowFilterAlreadyDisposed(this); - } - - [Pure, MethodImpl(MethodImplOptions.NoInlining)] - private Exception ThrowFilterAlreadyDisposed(FdbDatabaseFilter filter) - { - return new ObjectDisposedException(filter.GetType().Name); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (!m_disposed) - { - m_disposed = true; - if (disposing && m_owner) - { - m_database.Dispose(); - } - } - } - - #endregion - - } - -} diff --git a/FoundationDB.Client/Filters/FdbFilterExtensions.cs b/FoundationDB.Client/Filters/FdbFilterExtensions.cs deleted file mode 100644 index 461707a76..000000000 --- a/FoundationDB.Client/Filters/FdbFilterExtensions.cs +++ /dev/null @@ -1,57 +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 Doxense.Diagnostics.Contracts; - using FoundationDB.Client; - using JetBrains.Annotations; - - [PublicAPI] - public static class FdbFilterExtensions - { - /// Return a read-only view of this transaction, that will only allow read operations. - /// Transaction to secure - /// The same transaction instance if it is already read-only, or a thin read-only wrapper around the transaction if it is writable. - public static IFdbReadOnlyTransaction AsReadOnly(this IFdbTransaction trans) - { - Contract.NotNull(trans, nameof(trans)); - - if (trans.IsReadOnly) - { // this is already read-only - return trans; - } - else - { // we must protect this transaction by wrapping it with a read-only filter - return new ReadOnlyTransactionFilter(trans, ownsTransaction: false); - } - } - } - -} diff --git a/FoundationDB.Client/Filters/FdbTransactionFilter.cs b/FoundationDB.Client/Filters/FdbTransactionFilter.cs deleted file mode 100644 index 108123ecc..000000000 --- a/FoundationDB.Client/Filters/FdbTransactionFilter.cs +++ /dev/null @@ -1,580 +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 FoundationDB.Client; - using System; - using System.Collections.Generic; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Doxense.Diagnostics.Contracts; - using JetBrains.Annotations; - - /// Base class for simple transaction filters - public abstract class FdbTransactionFilter : IFdbTransaction - { - /// Inner database - protected readonly IFdbTransaction m_transaction; - /// If true, forces the underlying transaction 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; - - /// Base constructor for transaction filters - /// Underlying transaction that will be exposed as read-only - /// If true, force the transaction to be read-only. If false, use the read-only mode of the underlying transaction - /// If true, the underlying transaction will also be disposed when this instance is disposed - protected FdbTransactionFilter(IFdbTransaction trans, bool forceReadOnly, bool ownsTransaction) - { - Contract.NotNull(trans, nameof(trans)); - - m_transaction = trans; - m_readOnly = forceReadOnly || trans.IsReadOnly; - m_owner = ownsTransaction; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ThrowIfDisposed() - { - // this should be inlined by the caller - if (m_disposed) throw FilterAlreadyDisposed(this); - } - - [Pure, MethodImpl(MethodImplOptions.NoInlining)] - private static Exception FilterAlreadyDisposed(FdbTransactionFilter filter) - { - return new ObjectDisposedException(filter.GetType().Name); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (!m_disposed) - { - m_disposed = true; - if (disposing && m_owner) - { - m_transaction.Dispose(); - } - } - } - - /// - public int Id => m_transaction.Id; - - /// - public FdbOperationContext Context => m_transaction.Context; - - /// - public bool IsSnapshot => m_transaction.IsSnapshot; - - /// - public bool IsReadOnly => m_readOnly; - - /// - public virtual IFdbReadOnlyTransaction Snapshot => m_transaction.Snapshot; - //BUGBUG: we need a snapshot wrapper ? - - /// - public CancellationToken Cancellation => m_transaction.Cancellation; - - /// - public virtual void EnsureCanRead() - { - ThrowIfDisposed(); - m_transaction.EnsureCanRead(); - } - - /// - public virtual Task GetAsync(ReadOnlySpan key) - { - ThrowIfDisposed(); - return m_transaction.GetAsync(key); - } - - /// - public virtual Task GetValuesAsync(Slice[] keys) - { - ThrowIfDisposed(); - return m_transaction.GetValuesAsync(keys); - } - - /// - public virtual Task GetKeyAsync(KeySelector selector) - { - ThrowIfDisposed(); - return m_transaction.GetKeyAsync(selector); - } - - /// - public virtual Task GetKeysAsync(KeySelector[] selectors) - { - ThrowIfDisposed(); - return m_transaction.GetKeysAsync(selectors); - } - - /// - public virtual Task GetRangeAsync( - KeySelector beginInclusive, - KeySelector endExclusive, - int limit = 0, - bool reverse = false, - int targetBytes = 0, - FdbStreamingMode mode = FdbStreamingMode.Exact, - FdbReadMode read = FdbReadMode.Both, - int iteration = 0) - { - ThrowIfDisposed(); - return m_transaction.GetRangeAsync(beginInclusive, endExclusive, limit, reverse, targetBytes, mode, read, iteration); - } - - /// - public FdbRangeQuery> GetRange(KeySelector beginInclusive, KeySelector endExclusive, FdbRangeOptions? options = null) - { - return GetRange(beginInclusive, endExclusive, kv => kv, options); - } - - /// - public virtual FdbRangeQuery GetRange(KeySelector beginInclusive, KeySelector endExclusive, Func, TResult> selector, FdbRangeOptions? options = null) - { - ThrowIfDisposed(); - return m_transaction.GetRange(beginInclusive, endExclusive, selector, options); - } - - /// - public virtual Task GetAddressesForKeyAsync(ReadOnlySpan key) - { - ThrowIfDisposed(); - return m_transaction.GetAddressesForKeyAsync(key); - } - - /// - public virtual Task GetReadVersionAsync() - { - ThrowIfDisposed(); - return m_transaction.GetReadVersionAsync(); - } - - /// - public virtual Task GetMetadataVersionKeyAsync(Slice key = default) - { - ThrowIfDisposed(); - return m_transaction.GetMetadataVersionKeyAsync(key); - } - - /// - public virtual void TouchMetadataVersionKey(Slice key = default) - { - ThrowIfDisposed(); - m_transaction.TouchMetadataVersionKey(key); - } - - /// - public virtual void EnsureCanWrite() - { - ThrowIfDisposed(); - m_transaction.EnsureCanWrite(); - } - - /// - public virtual int Size => m_transaction.Size; - - /// - public virtual void Set(ReadOnlySpan key, ReadOnlySpan value) - { - ThrowIfDisposed(); - m_transaction.Set(key, value); - } - - /// - public virtual void Atomic(ReadOnlySpan key, ReadOnlySpan param, FdbMutationType mutation) - { - ThrowIfDisposed(); - m_transaction.Atomic(key, param, mutation); - } - - /// - public virtual void Clear(ReadOnlySpan key) - { - ThrowIfDisposed(); - m_transaction.Clear(key); - } - - /// - public virtual void ClearRange(ReadOnlySpan beginKeyInclusive, ReadOnlySpan endKeyExclusive) - { - ThrowIfDisposed(); - m_transaction.ClearRange(beginKeyInclusive, endKeyExclusive); - } - - /// - public virtual void AddConflictRange(ReadOnlySpan beginKeyInclusive, ReadOnlySpan endKeyExclusive, FdbConflictRangeType type) - { - ThrowIfDisposed(); - m_transaction.AddConflictRange(beginKeyInclusive, endKeyExclusive, type); - } - - /// - public virtual void Cancel() - { - ThrowIfDisposed(); - m_transaction.Cancel(); - } - - /// - public virtual void Reset() - { - ThrowIfDisposed(); - m_transaction.Reset(); - } - - /// - public virtual Task CommitAsync() - { - ThrowIfDisposed(); - return m_transaction.CommitAsync(); - } - - /// - public virtual long GetCommittedVersion() - { - ThrowIfDisposed(); - return m_transaction.GetCommittedVersion(); - } - - /// - public Task GetApproximateSizeAsync() - { - ThrowIfDisposed(); - return m_transaction.GetApproximateSizeAsync(); - } - - /// - public virtual Task GetVersionStampAsync() - { - ThrowIfDisposed(); - return m_transaction.GetVersionStampAsync(); - } - - /// - public virtual VersionStamp CreateVersionStamp() - { - ThrowIfDisposed(); - return m_transaction.CreateVersionStamp(); - } - - /// - public virtual VersionStamp CreateVersionStamp(int userVersion) - { - ThrowIfDisposed(); - return m_transaction.CreateVersionStamp(userVersion); - } - - /// - public virtual VersionStamp CreateUniqueVersionStamp() - { - ThrowIfDisposed(); - return m_transaction.CreateUniqueVersionStamp(); - } - - - /// - public virtual void SetReadVersion(long version) - { - ThrowIfDisposed(); - m_transaction.SetReadVersion(version); - } - - /// - public virtual Task OnErrorAsync(FdbError code) - { - ThrowIfDisposed(); - return m_transaction.OnErrorAsync(code); - } - - /// - public virtual FdbWatch Watch(ReadOnlySpan key, CancellationToken ct) - { - ThrowIfDisposed(); - return m_transaction.Watch(key, ct); - } - - /// - public virtual void SetOption(FdbTransactionOption option) - { - ThrowIfDisposed(); - m_transaction.SetOption(option); - } - - /// - public virtual void SetOption(FdbTransactionOption option, string value) - { - ThrowIfDisposed(); - m_transaction.SetOption(option, value); - } - - /// - public virtual void SetOption(FdbTransactionOption option, ReadOnlySpan value) - { - ThrowIfDisposed(); - m_transaction.SetOption(option, value); - } - - /// - public virtual void SetOption(FdbTransactionOption option, long value) - { - ThrowIfDisposed(); - m_transaction.SetOption(option, value); - } - - /// - public int Timeout - { - get => m_transaction.Timeout; - set - { - ThrowIfDisposed(); - m_transaction.Timeout = value; - } - } - - /// - public int RetryLimit - { - get => m_transaction.RetryLimit; - set - { - ThrowIfDisposed(); - m_transaction.RetryLimit = value; - } - } - - /// - public int MaxRetryDelay - { - get => m_transaction.MaxRetryDelay; - set - { - ThrowIfDisposed(); - m_transaction.MaxRetryDelay = value; - } - } - - } - - public class FdbReadOnlyTransactionFilter : IFdbReadOnlyTransaction - { - /// Inner database - protected readonly IFdbReadOnlyTransaction m_transaction; - - protected FdbReadOnlyTransactionFilter(IFdbReadOnlyTransaction transaction) - { - Contract.NotNull(transaction, nameof(transaction)); - m_transaction = transaction; - } - - /// - public int Id => m_transaction.Id; - - /// - public FdbOperationContext Context => m_transaction.Context; - - /// - public bool IsSnapshot => m_transaction.IsSnapshot; - - /// - public IFdbReadOnlyTransaction Snapshot => this; - - /// - public CancellationToken Cancellation => m_transaction.Cancellation; - - /// - public void EnsureCanRead() - { - m_transaction.EnsureCanRead(); - } - - /// - public virtual Task GetAsync(ReadOnlySpan key) - { - return m_transaction.GetAsync(key); - } - - /// - public virtual Task GetValuesAsync(Slice[] keys) - { - return m_transaction.GetValuesAsync(keys); - } - - /// - public virtual Task GetKeyAsync(KeySelector selector) - { - return m_transaction.GetKeyAsync(selector); - } - - /// - public virtual Task GetKeysAsync(KeySelector[] selectors) - { - return m_transaction.GetKeysAsync(selectors); - } - - public virtual Task GetRangeAsync( - KeySelector beginInclusive, - KeySelector endExclusive, - int limit = 0, - bool reverse = false, - int targetBytes = 0, - FdbStreamingMode mode = FdbStreamingMode.Iterator, - FdbReadMode read = FdbReadMode.Both, - int iteration = 0) - { - return m_transaction.GetRangeAsync(beginInclusive, endExclusive, limit, reverse, targetBytes, mode, read, iteration); - } - - /// - public FdbRangeQuery> GetRange(KeySelector beginInclusive, KeySelector endInclusive, FdbRangeOptions? options = null) - { - return GetRange(beginInclusive, endInclusive, kv => kv, options); - } - - /// - public virtual FdbRangeQuery GetRange(KeySelector beginInclusive, KeySelector endInclusive, Func, TResult> selector, FdbRangeOptions? options = null) - { - return m_transaction.GetRange(beginInclusive, endInclusive, selector, options); - } - - /// - public virtual Task GetAddressesForKeyAsync(ReadOnlySpan key) - { - return m_transaction.GetAddressesForKeyAsync(key); - } - - /// - public virtual Task GetReadVersionAsync() - { - return m_transaction.GetReadVersionAsync(); - } - - /// - public virtual Task GetMetadataVersionKeyAsync(Slice key = default) - { - return m_transaction.GetMetadataVersionKeyAsync(key); - } - - /// - public virtual void SetReadVersion(long version) - { - m_transaction.SetReadVersion(version); - } - - /// - public virtual void Cancel() - { - m_transaction.Cancel(); - } - - /// - public virtual void Reset() - { - m_transaction.Reset(); - } - - /// - public virtual Task OnErrorAsync(FdbError code) - { - return m_transaction.OnErrorAsync(code); - } - - /// - public virtual void SetOption(FdbTransactionOption option) - { - m_transaction.SetOption(option); - } - - /// - public virtual void SetOption(FdbTransactionOption option, string value) - { - m_transaction.SetOption(option, value); - } - - /// - public virtual void SetOption(FdbTransactionOption option, ReadOnlySpan value) - { - m_transaction.SetOption(option, value); - } - - /// - public virtual void SetOption(FdbTransactionOption option, long value) - { - m_transaction.SetOption(option, value); - } - - /// - public virtual int Timeout - { - get => m_transaction.Timeout; - set => m_transaction.Timeout = value; - } - - /// - public virtual int RetryLimit - { - get => m_transaction.RetryLimit; - set => m_transaction.RetryLimit = value; - } - - /// - public virtual int MaxRetryDelay - { - get => m_transaction.MaxRetryDelay; - set => m_transaction.MaxRetryDelay = value; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - //NOP? - } - - } - -} diff --git a/FoundationDB.Client/Filters/Logging/FdbLoggedDatabase.cs b/FoundationDB.Client/Filters/Logging/FdbLoggedDatabase.cs deleted file mode 100644 index c0ff4434e..000000000 --- a/FoundationDB.Client/Filters/Logging/FdbLoggedDatabase.cs +++ /dev/null @@ -1,70 +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.Logging -{ - using FoundationDB.Client; - using System; - using System.Threading; - using System.Threading.Tasks; - - /// Database filter that logs all the transactions - public sealed class FdbLoggedDatabase : FdbDatabaseFilter - { - - /// Handler called every time a transaction is successfully committed - public Action OnCommitted { get; private set; } - - public FdbLoggingOptions LoggingOptions { get; private set; } - - /// Wrap a database with a filter that will log the activity of all transactions - /// Wrapped database - /// If true, deny all write operations. - /// If true, also dispose the wrapped database if this instance is disposed. - /// Handler that will be called when a transaction is either committed successfully, or disposed. The log can be accessed via the property. - /// - public FdbLoggedDatabase(IFdbDatabase database, bool forceReadOnly, bool ownsDatabase, Action onCommitted, FdbLoggingOptions defaultOptions = FdbLoggingOptions.Default) - : base(database, forceReadOnly, ownsDatabase) - { - this.OnCommitted = onCommitted; - this.LoggingOptions = defaultOptions; - } - - /// Create a new logged transaction - public override async ValueTask BeginTransactionAsync(FdbTransactionMode mode, CancellationToken ct = default, FdbOperationContext? context = null) - { - return new FdbLoggedTransaction( - await base.BeginTransactionAsync(mode, ct, context), - true, - this.OnCommitted, - this.LoggingOptions - ); - } - } - -} diff --git a/FoundationDB.Client/Filters/Logging/FdbLoggedTransaction.cs b/FoundationDB.Client/Filters/Logging/FdbLoggedTransaction.cs deleted file mode 100644 index 26741a8fa..000000000 --- a/FoundationDB.Client/Filters/Logging/FdbLoggedTransaction.cs +++ /dev/null @@ -1,640 +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.Logging -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using Doxense; - using Doxense.Diagnostics.Contracts; - using FoundationDB.Client; - using JetBrains.Annotations; - - [Flags] - [PublicAPI] - public enum FdbLoggingOptions - { - /// Default logging options - Default = 0, - - /// Capture the stacktrace of the caller method that created the transaction - RecordCreationStackTrace = 0x100, - - /// Capture the stacktrace of the caller method for each operation - RecordOperationStackTrace = 0x200, - - /// Capture all the stack traces. - /// This is a shortcut for | - WithStackTraces = RecordCreationStackTrace | RecordOperationStackTrace, - } - - /// Transaction filter that logs and measure all operations performed on the underlying transaction - [PublicAPI] - public sealed class FdbLoggedTransaction : FdbTransactionFilter - { - - /// Log of all operations performed on this transaction - public FdbTransactionLog Log {get; private set; } - - /// Handler that will be called when this transaction commits successfully - public Action Committed { get; private set; } - - /// If non-null, at least one VersionStamped operation in the last attempt - private Task? VersionStamp { get; set; } - - private Snapshotted? m_snapshotted; - - /// Wrap an existing transaction and log all operations performed - public FdbLoggedTransaction(IFdbTransaction trans, bool ownsTransaction, Action onCommitted, FdbLoggingOptions options) - : base(trans, false, ownsTransaction) - { - this.Log = new FdbTransactionLog(options); - this.Committed = onCommitted; - this.Log.Start(this); - } - - protected override void Dispose(bool disposing) - { - try - { - base.Dispose(disposing); - } - finally - { - if (!this.Log.Completed) - { - this.Log.Stop(this); - OnCommitted(); - } - } - } - - #region Data interning... - - private byte[] m_buffer = new byte[1024]; - private int m_offset; - private readonly object m_lock = new object(); - - private Slice Grab(Slice slice) - { - if (slice.IsNullOrEmpty) return slice.IsNull ? Slice.Nil : Slice.Empty; - - lock (m_lock) - { - if (slice.Count > m_buffer.Length - m_offset) - { // not enough ? - if (slice.Count >= 2048) - { - return slice.Memoize(); - } - m_buffer = new byte[4096]; - m_offset = 0; - } - - int start = m_offset; - slice.CopyTo(m_buffer, m_offset); - m_offset += slice.Count; - return m_buffer.AsSlice(start, slice.Count); - } - } - - private Slice Grab(ReadOnlySpan slice) - { - if (slice.Length == 0) return Slice.Empty; - - lock (m_lock) - { - if (slice.Length > m_buffer.Length - m_offset) - { // not enough ? - if (slice.Length >= 2048) - { - return slice.ToArray().AsSlice(); - } - m_buffer = new byte[4096]; - m_offset = 0; - } - - int start = m_offset; - slice.CopyTo(m_buffer.AsSpan(m_offset)); - m_offset += slice.Length; - return m_buffer.AsSlice(start, slice.Length); - } - } - - private Slice[] Grab(Slice[]? slices) - { - if (slices == null || slices.Length == 0) return Array.Empty(); - - lock (m_lock) - { - int total = 0; - for (int i = 0; i < slices.Length; i++) - { - total += slices[i].Count; - } - - if (total > m_buffer.Length - m_offset) - { - return FdbKey.Merge(Slice.Empty, slices); - } - - var res = new Slice[slices.Length]; - for (int i = 0; i < slices.Length; i++) - { - res[i] = Grab(slices[i]); - } - return res; - } - } - - private KeySelector Grab(in KeySelector selector) - { - return new KeySelector( - Grab(selector.Key), - selector.OrEqual, - selector.Offset - ); - } - - private KeySelector[] Grab(KeySelector[]? selectors) - { - if (selectors == null || selectors.Length == 0) return Array.Empty(); - - var res = new KeySelector[selectors.Length]; - for (int i = 0; i < selectors.Length; i++) - { - res[i] = Grab(in selectors[i]); - } - return res; - } - - #endregion - - #region Instrumentation... - - private void OnCommitted() - { - if (this.Committed != null) - { - try - { - this.Committed(this); - } -#if DEBUG - catch(Exception e) - { - System.Diagnostics.Debug.WriteLine("Logged transaction handler failed: " + e.ToString()); - } -#else - catch { } -#endif - } - } - - private void Execute(TCommand cmd, Action action) - where TCommand : FdbTransactionLog.Command - { - ThrowIfDisposed(); - Exception? error = null; - this.Log.BeginOperation(cmd); - try - { - action(m_transaction, cmd); - } - catch (Exception e) - { - error = e; - throw; - } - finally - { - this.Log.EndOperation(cmd, error); - } - } - - private async Task ExecuteAsync(TCommand cmd, Func lambda, Action? onSuccess = null) - where TCommand : FdbTransactionLog.Command - { - ThrowIfDisposed(); - Exception? error = null; - var tr = m_transaction; - this.Log.BeginOperation(cmd); - try - { - await lambda(tr, cmd).ConfigureAwait(false); - } - catch (Exception e) - { - error = e; - throw; - } - finally - { - this.Log.EndOperation(cmd, error); - if (error == null) onSuccess?.Invoke(this, tr); - } - } - - private async Task ExecuteAsync(TCommand cmd, Func> lambda) - where TCommand : FdbTransactionLog.Command - { - ThrowIfDisposed(); - Exception? error = null; - this.Log.BeginOperation(cmd); - try - { - TResult result = await lambda(m_transaction, cmd).ConfigureAwait(false); - cmd.Result = Maybe.Return(result); - return result; - } - catch (Exception e) - { - error = e; - cmd.Result = Maybe.Error(System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e)); - throw; - } - finally - { - this.Log.EndOperation(cmd, error); - } - } - - #endregion - - #region Meta... - - public override void Cancel() - { - Execute( - new FdbTransactionLog.CancelCommand(), - (tr, cmd) => tr.Cancel() - ); - } - - public override void Reset() - { - this.VersionStamp = null; - Execute( - new FdbTransactionLog.ResetCommand(), - (tr, cmd) => tr.Reset() - ); - } - - public override FdbWatch Watch(ReadOnlySpan key, CancellationToken ct) - { - var cmd = new FdbTransactionLog.WatchCommand(Grab(key)); - this.Log.AddOperation(cmd); - return m_transaction.Watch(cmd.Key, ct); - } - - #endregion - - #region Write... - - public override Task CommitAsync() - { - int size = m_transaction.Size; - this.Log.CommitSize = size; - this.Log.TotalCommitSize += size; - this.Log.Attempts++; - - var cmd = new FdbTransactionLog.CommitCommand(); - return ExecuteAsync( - cmd, - (tr, _) => tr.CommitAsync(), - (self, tr) => - { - self.Log.CommittedUtc = DateTimeOffset.UtcNow; - var cv = tr.GetCommittedVersion(); - self.Log.CommittedVersion = cv; - cmd.CommitVersion = cv; - - if (this.VersionStamp != null) self.Log.VersionStamp = this.VersionStamp.GetAwaiter().GetResult(); - } - ); - } - - public override Task OnErrorAsync(FdbError code) - { - this.VersionStamp = null; - return ExecuteAsync( - new FdbTransactionLog.OnErrorCommand(code), - (_tr, _cmd) => _tr.OnErrorAsync(_cmd.Code) - ); - } - - public override Task GetVersionStampAsync() - { - return ExecuteAsync( - new FdbTransactionLog.GetVersionStampCommand(), - (tr, cmd) => tr.GetVersionStampAsync() - ); - } - - public override void Set(ReadOnlySpan key, ReadOnlySpan value) - { - Execute( - new FdbTransactionLog.SetCommand(Grab(key), Grab(value)), - (_tr, _cmd) => _tr.Set(_cmd.Key, _cmd.Value) - ); - } - - public override void Clear(ReadOnlySpan key) - { - ThrowIfDisposed(); - Execute( - new FdbTransactionLog.ClearCommand(Grab(key)), - (_tr, _cmd) => _tr.Clear(_cmd.Key) - ); - } - - public override void ClearRange(ReadOnlySpan beginKeyInclusive, ReadOnlySpan endKeyExclusive) - { - Execute( - new FdbTransactionLog.ClearRangeCommand(Grab(beginKeyInclusive), Grab(endKeyExclusive)), - (_tr, _cmd) => _tr.ClearRange(_cmd.Begin, _cmd.End) - ); - } - - public override void Atomic(ReadOnlySpan key, ReadOnlySpan param, FdbMutationType mutation) - { - if (mutation == FdbMutationType.VersionStampedKey || mutation == FdbMutationType.VersionStampedValue) - { - this.VersionStamp ??= m_transaction.GetVersionStampAsync(); - } - Execute( - new FdbTransactionLog.AtomicCommand(Grab(key), Grab(param), mutation), - (_tr, _cmd) => _tr.Atomic(_cmd.Key, _cmd.Param, _cmd.Mutation) - ); - } - - public override void AddConflictRange(ReadOnlySpan beginKeyInclusive, ReadOnlySpan endKeyExclusive, FdbConflictRangeType type) - { - Execute( - new FdbTransactionLog.AddConflictRangeCommand(Grab(beginKeyInclusive), Grab(endKeyExclusive), type), - (_tr, _cmd) => _tr.AddConflictRange(_cmd.Begin, _cmd.End, _cmd.Type) - ); - } - - public override void TouchMetadataVersionKey(Slice key = default) - { - Execute( - new FdbTransactionLog.TouchMetadataVersionKeyCommand(key.IsNull ? Fdb.System.MetadataVersionKey : Grab(key)), - (tr, cmd) => tr.TouchMetadataVersionKey(cmd.Key) - ); - } - - #endregion - - #region Read... - - public override Task GetReadVersionAsync() - { - return ExecuteAsync( - new FdbTransactionLog.GetReadVersionCommand(), - (tr, cmd) => tr.GetReadVersionAsync() - ); - } - - public override Task GetMetadataVersionKeyAsync(Slice key = default) - { - return ExecuteAsync( - new FdbTransactionLog.GetMetadataVersionCommand(key.IsNull ? Fdb.System.MetadataVersionKey : Grab(key)), - (tr, cmd) => tr.GetMetadataVersionKeyAsync(cmd.Key) - ); - } - - public override Task GetAsync(ReadOnlySpan key) - { - return ExecuteAsync( - new FdbTransactionLog.GetCommand(Grab(key)), - (tr, cmd) => tr.GetAsync(cmd.Key) - ); - } - - public override Task GetKeyAsync(KeySelector selector) - { - return ExecuteAsync( - new FdbTransactionLog.GetKeyCommand(Grab(in selector)), - (tr, cmd) => tr.GetKeyAsync(selector) - ); - } - - public override Task GetValuesAsync(Slice[] keys) - { - Contract.Requires(keys != null); - return ExecuteAsync( - new FdbTransactionLog.GetValuesCommand(Grab(keys)), - (tr, cmd) => tr.GetValuesAsync(keys) - ); - } - - public override Task GetKeysAsync(KeySelector[] selectors) - { - Contract.Requires(selectors != null); - return ExecuteAsync( - new FdbTransactionLog.GetKeysCommand(Grab(selectors)), - (tr, cmd) => tr.GetKeysAsync(selectors) - ); - } - - public override Task GetRangeAsync( - KeySelector beginInclusive, - KeySelector endExclusive, - int limit = 0, - bool reverse = false, - int targetBytes = 0, - FdbStreamingMode mode = FdbStreamingMode.Exact, - FdbReadMode read = FdbReadMode.Both, - int iteration = 0 - ) - { - return ExecuteAsync( - new FdbTransactionLog.GetRangeCommand(Grab(in beginInclusive), Grab(in endExclusive), new FdbRangeOptions(limit, reverse, targetBytes, mode, read), iteration), - (tr, cmd) => tr.GetRangeAsync(cmd.Begin, cmd.End, cmd.Options, cmd.Iteration) - ); - } - - public override FdbRangeQuery GetRange(KeySelector beginInclusive, KeySelector endExclusive, Func, TResult> selector, FdbRangeOptions? options = null) - { - ThrowIfDisposed(); - - var query = m_transaction.GetRange(beginInclusive, endExclusive, selector, options); - // this method does not execute immediately, so we don't need to record any operation here, only when GetRangeAsync() is called (by ToListAsync() or any other LINQ operator) - // we must override the transaction used by the query, so that we are notified when this happens - return query.UseTransaction(this); - } - - #endregion - - #region Snapshot... - - public override IFdbReadOnlyTransaction Snapshot => m_snapshotted ??= new Snapshotted(this, m_transaction.Snapshot); - - private sealed class Snapshotted : FdbReadOnlyTransactionFilter - { - private readonly FdbLoggedTransaction m_parent; - - public Snapshotted(FdbLoggedTransaction parent, IFdbReadOnlyTransaction snapshot) - : base(snapshot) - { - m_parent = parent; - } - - private async Task ExecuteAsync(TCommand cmd, Func> lambda) - where TCommand : FdbTransactionLog.Command - { - m_parent.ThrowIfDisposed(); - Exception? error = null; - cmd.Snapshot = true; - m_parent.Log.BeginOperation(cmd); - try - { - TResult result = await lambda(m_transaction, cmd).ConfigureAwait(false); - cmd.Result = Maybe.Return(result); - return result; - } - catch (Exception e) - { - error = e; - cmd.Result = Maybe.Error(System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e)); - throw; - } - finally - { - m_parent.Log.EndOperation(cmd, error); - } - } - - public override Task GetReadVersionAsync() - { - return ExecuteAsync( - new FdbTransactionLog.GetReadVersionCommand(), - (tr, cmd) => tr.GetReadVersionAsync() - ); - } - - public override Task GetMetadataVersionKeyAsync(Slice key = default) - { - return ExecuteAsync( - new FdbTransactionLog.GetMetadataVersionCommand(key.IsNull ? Fdb.System.MetadataVersionKey : m_parent.Grab(key)), - (tr, cmd) => tr.GetMetadataVersionKeyAsync(cmd.Key) - ); - } - - public override Task GetAsync(ReadOnlySpan key) - { - return ExecuteAsync( - new FdbTransactionLog.GetCommand(m_parent.Grab(key)), - (tr, cmd) => tr.GetAsync(cmd.Key) - ); - } - - public override Task GetKeyAsync(KeySelector selector) - { - return ExecuteAsync( - new FdbTransactionLog.GetKeyCommand(m_parent.Grab(in selector)), - (tr, cmd) => tr.GetKeyAsync(cmd.Selector) - ); - } - - public override Task GetValuesAsync(Slice[] keys) - { - Contract.Requires(keys != null); - return ExecuteAsync( - new FdbTransactionLog.GetValuesCommand(m_parent.Grab(keys)), - (tr, cmd) => tr.GetValuesAsync(cmd.Keys) - ); - } - - public override Task GetKeysAsync(KeySelector[] selectors) - { - Contract.Requires(selectors != null); - return ExecuteAsync( - new FdbTransactionLog.GetKeysCommand(m_parent.Grab(selectors)), - (tr, cmd) => tr.GetKeysAsync(cmd.Selectors) - ); - } - - public override Task GetRangeAsync( - KeySelector beginInclusive, - KeySelector endExclusive, - int limit = 0, - bool reverse = false, - int targetBytes = 0, - FdbStreamingMode mode = FdbStreamingMode.Iterator, - FdbReadMode read = FdbReadMode.Both, - int iteration = 0 - ) - { - return ExecuteAsync( - new FdbTransactionLog.GetRangeCommand(m_parent.Grab(in beginInclusive), m_parent.Grab(in endExclusive), new FdbRangeOptions(limit, reverse, targetBytes, mode, read), iteration), - (tr, cmd) => tr.GetRangeAsync(cmd.Begin, cmd.End, cmd.Options, cmd.Iteration) - ); - } - - public override FdbRangeQuery GetRange(KeySelector beginInclusive, KeySelector endExclusive, Func, TResult> selector, FdbRangeOptions? options = null) - { - m_parent.ThrowIfDisposed(); - var query = m_transaction.GetRange(beginInclusive, endExclusive, selector, options); - return query.UseTransaction(this); - } - - } - #endregion - - #region Options... - - public override void SetOption(FdbTransactionOption option) - { - ThrowIfDisposed(); - this.Log.AddOperation(new FdbTransactionLog.SetOptionCommand(option), countAsOperation: false); - m_transaction.SetOption(option); - } - - public override void SetOption(FdbTransactionOption option, long value) - { - ThrowIfDisposed(); - this.Log.AddOperation(new FdbTransactionLog.SetOptionCommand(option, value), countAsOperation: false); - m_transaction.SetOption(option, value); - } - - public override void SetOption(FdbTransactionOption option, string value) - { - ThrowIfDisposed(); - this.Log.AddOperation(new FdbTransactionLog.SetOptionCommand(option, value), countAsOperation: false); - m_transaction.SetOption(option, value); - } - - #endregion - - } - -} diff --git a/FoundationDB.Client/Filters/Logging/FdbLoggingExtensions.cs b/FoundationDB.Client/Filters/Logging/FdbLoggingExtensions.cs deleted file mode 100644 index 9f83ecdab..000000000 --- a/FoundationDB.Client/Filters/Logging/FdbLoggingExtensions.cs +++ /dev/null @@ -1,160 +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.Logging -{ - using System; - using System.Globalization; - using System.Runtime.CompilerServices; - using System.Threading.Tasks; - using Doxense.Diagnostics.Contracts; - using FoundationDB.Client; - using JetBrains.Annotations; - - /// Set of extension methods that add logging support on transactions - public static class FdbLoggingExtensions - { - /// Apply the Logging Filter to this database instance - /// Original database instance - /// Handler that will be called every-time a transaction commits successfully, or gets disposed. The log of all operations performed by the transaction can be accessed via the property. - /// Optional logging options - /// Database filter, that will monitor all transactions initiated from it. Disposing this wrapper will NOT dispose the inner database. - public static FdbLoggedDatabase Logged(this IFdbDatabase database, Action handler, FdbLoggingOptions options = FdbLoggingOptions.Default) - { - Contract.NotNull(database, nameof(database)); - Contract.NotNull(handler, nameof(handler)); - - // prevent multiple logging - database = WithoutLogging(database); - - return new FdbLoggedDatabase(database, false, false, handler, options); - } - - /// Apply the Logging Filter to the database exposed by this provider - /// Original database provider instance - /// Handler that will be called every-time a transaction commits successfully, or gets disposed. The log of all operations performed by the transaction can be accessed via the property. - /// Optional logging options - /// Provider that will that will monitor all transactions initiated from it. - public static IFdbDatabaseScopeProvider Logged(this IFdbDatabaseScopeProvider provider, Action handler, FdbLoggingOptions options = FdbLoggingOptions.Default) - { - Contract.NotNull(provider, nameof(provider)); - Contract.NotNull(handler, nameof(handler)); - - return provider.CreateScope((db, ct) => !ct.IsCancellationRequested ? Task.FromResult<(IFdbDatabase, object?)>((Logged(db, handler), null)) : Task.FromCanceled<(IFdbDatabase, object?)>(ct)); - } - - /// Strip the logging behaviour of this database. Use this for boilerplate or test code that would pollute the logs otherwise. - /// Database instance (that may or may not be logged) - /// Either itself if it is not logged, or the inner database if it was. - public static IFdbDatabase WithoutLogging(this IFdbDatabase database) - { - Contract.NotNull(database, nameof(database)); - - return database is FdbLoggedDatabase logged ? logged.GetInnerDatabase() : database; - } - - internal static FdbLoggedTransaction? GetLogger(IFdbReadOnlyTransaction trans) - { - //TODO: the logged transaction could also be wrapped in other filters. - // => we need a recursive "FindFilter" method that would unwrap the filter onion looking for a specific one... - - return trans as FdbLoggedTransaction; - } - - /// Test if logging is enabled on this transaction - /// If false, then there is no point in calling because logging is disabled. - [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLogged(this IFdbReadOnlyTransaction trans) - { - return trans is FdbLoggedTransaction; - } - - /// Annotate a logged transaction - /// - /// This method only applies to transactions created from a logged database instance. - /// Calling this method on regular transaction is a no-op. - /// You can call first, if you don't want to pay the cost of formatting when logging not enabled. - /// - public static void Annotate(this IFdbReadOnlyTransaction trans, string message) - { - var logged = GetLogger(trans); - logged?.Log.AddOperation(new FdbTransactionLog.LogCommand(message), countAsOperation: false); - } - - /// Annotate a logged transaction - /// - /// This method only applies to transactions created from a logged database instance. - /// Calling this method on regular transaction is a no-op. - /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. - /// - [StringFormatMethod("format")] - public static void Annotate(this IFdbReadOnlyTransaction trans, string format, object? arg0) - { - var logged = GetLogger(trans); - logged?.Log.AddOperation(new FdbTransactionLog.LogCommand(string.Format(CultureInfo.InvariantCulture, format, arg0)), countAsOperation: false); - } - - /// Annotate a logged transaction - /// - /// This method only applies to transactions created from a logged databaseinstance. - /// Calling this method on regular transaction is a no-op. - /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. - /// - [StringFormatMethod("format")] - public static void Annotate(this IFdbReadOnlyTransaction trans, string format, object? arg0, object? arg1) - { - GetLogger(trans)?.Log.AddOperation(new FdbTransactionLog.LogCommand(string.Format(CultureInfo.InvariantCulture, format, arg0, arg1)), countAsOperation: false); - } - - /// Annotate a logged transaction - /// - /// This method only applies to transactions created from a logged database instance. - /// Calling this method on regular transaction is a no-op. - /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. - /// - [StringFormatMethod("format")] - public static void Annotate(this IFdbReadOnlyTransaction trans, string format, object? arg0, object? arg1, object? arg2) - { - GetLogger(trans)?.Log.AddOperation(new FdbTransactionLog.LogCommand(string.Format(CultureInfo.InvariantCulture, format, arg0, arg1, arg2)), countAsOperation: false); - } - - /// Annotate a logged transaction - /// - /// This method only applies to transactions created from a logged database instance. - /// Calling this method on regular transaction is a no-op. - /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. - /// - [StringFormatMethod("format")] - public static void Annotate(this IFdbReadOnlyTransaction trans, string format, params object?[] args) - { - GetLogger(trans)?.Log.AddOperation(new FdbTransactionLog.LogCommand(string.Format(CultureInfo.InvariantCulture, format, args)), countAsOperation: false); - } - - } - -} diff --git a/FoundationDB.Client/Filters/PrefixRewriterTransaction.cs b/FoundationDB.Client/Filters/PrefixRewriterTransaction.cs deleted file mode 100644 index ea82cd5e1..000000000 --- a/FoundationDB.Client/Filters/PrefixRewriterTransaction.cs +++ /dev/null @@ -1,177 +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 - -#if TODO - -namespace FoundationDB.Filters -{ - using FoundationDB.Client; - using System; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - - /// [PROOF OF CONCEPT, DO NOT USE YET!] Transaction filter that automatically appends/remove a fixed prefix to all keys - public sealed class PrefixRewriterTransaction : FdbTransactionFilter - { - // We will add a prefix to all keys sent to the db, and remove it on the way back - - public PrefixRewriterTransaction(IKeySubspace prefix, IFdbTransaction trans, bool ownsTransaction) - : base(trans, false, ownsTransaction) - { - this.Prefix = prefix ?? throw new ArgumentNullException(nameof(prefix)); - } - - public IKeySubspace Prefix { get; } - - private Slice Encode(Slice key) - { - return this.Prefix[key]; - } - - private Slice[] Encode(Slice[] keys) - { - return keys.Select(k => this.Prefix[k]).ToArray(); - } - - private KeySelector Encode(KeySelector selector) - { - return new KeySelector( - this.Prefix[selector.Key], - selector.OrEqual, - selector.Offset - ); - } - - private KeySelector[] Encode(KeySelector[] selectors) - { - var keys = new Slice[selectors.Length]; - for (int i = 0; i < selectors.Length;i++) - { - keys[i] = this.Prefix[selectors[i].Key]; - } - - var res = new KeySelector[selectors.Length]; - for (int i = 0; i < selectors.Length; i++) - { - res[i] = new KeySelector( - keys[i], - selectors[i].OrEqual, - selectors[i].Offset - ); - } - return res; - } - - private Slice Decode(Slice key) - { - return this.Prefix.ExtractKey(key); - } - - private Slice[] Decode(Slice[] keys) - { - var res = new Slice[keys.Length]; - for (int i = 0; i < keys.Length;i++) - { - res[i] = this.Prefix.ExtractKey(keys[i]); - } - return res; - } - - public override Task GetAsync(Slice key) - { - return base.GetAsync(Encode(key)); - } - - public override Task GetValuesAsync(Slice[] keys) - { - return base.GetValuesAsync(Encode(keys)); - } - - public override async Task GetKeyAsync(KeySelector selector) - { - return Decode(await base.GetKeyAsync(Encode(selector)).ConfigureAwait(false)); - } - - public override async Task GetKeysAsync(KeySelector[] selectors) - { - return Decode(await base.GetKeysAsync(Encode(selectors)).ConfigureAwait(false)); - } - - public override Task GetAddressesForKeyAsync(Slice key) - { - return base.GetAddressesForKeyAsync(Encode(key)); - } - - public override FdbRangeQuery GetRange(KeySelector beginInclusive, KeySelector endExclusive, Func, TResult> selector, FdbRangeOptions? options = null) - { - throw new NotImplementedException(); - } - - public override Task GetRangeAsync(KeySelector beginInclusive, KeySelector endExclusive, FdbRangeOptions? options = null, int iteration = 0) - { - throw new NotImplementedException(); - } - - - public override void Set(ReadOnlySpan key, ReadOnlySpan value) - { - base.Set(Encode(key), value); - } - - public override void Clear(Slice key) - { - base.Clear(Encode(key)); - } - - public override void ClearRange(Slice beginKeyInclusive, Slice endKeyExclusive) - { - base.ClearRange(Encode(beginKeyInclusive), Encode(endKeyExclusive)); - } - - public override void Atomic(Slice key, Slice param, FdbMutationType mutation) - { - base.Atomic(Encode(key), param, mutation); - } - - public override void AddConflictRange(Slice beginKeyInclusive, Slice endKeyExclusive, FdbConflictRangeType type) - { - base.AddConflictRange(Encode(beginKeyInclusive), Encode(endKeyExclusive), type); - } - - public override FdbWatch Watch(Slice key, CancellationToken ct) - { - //BUGBUG: the watch returns the key, that would need to be decoded! - return base.Watch(Encode(key), ct); - } - - } - -} - -#endif diff --git a/FoundationDB.Client/Filters/ReadOnlyTransactionFilter.cs b/FoundationDB.Client/Filters/ReadOnlyTransactionFilter.cs deleted file mode 100644 index 676b678ff..000000000 --- a/FoundationDB.Client/Filters/ReadOnlyTransactionFilter.cs +++ /dev/null @@ -1,42 +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 FoundationDB.Client; - using System; - - /// Filter that forces a read/write transaction to be read-only - public sealed class ReadOnlyTransactionFilter : FdbTransactionFilter - { - public ReadOnlyTransactionFilter(IFdbTransaction trans, bool ownsTransaction) - : base(trans, true, ownsTransaction) - { } - } - -} diff --git a/FoundationDB.Client/IFdbDatabase.cs b/FoundationDB.Client/IFdbDatabase.cs index 37577ae1b..d0a7e076f 100644 --- a/FoundationDB.Client/IFdbDatabase.cs +++ b/FoundationDB.Client/IFdbDatabase.cs @@ -32,6 +32,7 @@ namespace FoundationDB.Client using System; using System.Threading; using System.Threading.Tasks; + using FoundationDB.Filters.Logging; /// Database connection context. [PublicAPI] @@ -72,6 +73,11 @@ public interface IFdbDatabase : IFdbRetryable, IDisposable /// Value of the parameter void SetOption(FdbDatabaseOption option, long value); + /// Sets the default log handler for this database + /// Default handler that is attached to any new transction, and will be invoked when they complete. + /// This handler may not be called if logging is disabled, if a transaction overrides its handler, or if it calls + void SetDefaultLogHandler(Action handler, FdbLoggingOptions options = default); + /// Default Timeout value (in milliseconds) for all transactions created from this database instance. /// Only effective for future transactions int DefaultTimeout { get; set; } diff --git a/FoundationDB.Client/IFdbReadOnlyTransaction.cs b/FoundationDB.Client/IFdbReadOnlyTransaction.cs index c9bb40bee..1e96334fd 100644 --- a/FoundationDB.Client/IFdbReadOnlyTransaction.cs +++ b/FoundationDB.Client/IFdbReadOnlyTransaction.cs @@ -33,6 +33,7 @@ namespace FoundationDB.Client using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + using FoundationDB.Filters.Logging; /// Transaction that allows read operations [PublicAPI] @@ -219,6 +220,27 @@ Task GetRangeAsync( /// int MaxRetryDelay { get; set; } + /// Log of all operations performed on this transaction (if logging was enabled on the database or transaction) + FdbTransactionLog? Log { get; } + + /// 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. + /// + bool IsLogged(); + + /// 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"); + void Annotate(string 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 + void StopLogging(); + } } diff --git a/FoundationDB.Client/IFdbTransaction.cs b/FoundationDB.Client/IFdbTransaction.cs index b9bb67740..9e94aaa69 100644 --- a/FoundationDB.Client/IFdbTransaction.cs +++ b/FoundationDB.Client/IFdbTransaction.cs @@ -32,6 +32,7 @@ namespace FoundationDB.Client using System; using System.Threading; using System.Threading.Tasks; + using FoundationDB.Filters.Logging; /// Transaction that allows read and write operations [PublicAPI] diff --git a/FoundationDB.Client/Tracing/FdbLoggingExtensions.cs b/FoundationDB.Client/Tracing/FdbLoggingExtensions.cs new file mode 100644 index 000000000..39dfcfdcb --- /dev/null +++ b/FoundationDB.Client/Tracing/FdbLoggingExtensions.cs @@ -0,0 +1,93 @@ +#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.Logging +{ + using System; + using System.Globalization; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + using Doxense.Diagnostics.Contracts; + using FoundationDB.Client; + using JetBrains.Annotations; + + /// Set of extension methods that add logging support on transactions + public static class FdbLoggingExtensions + { + + /// Annotate a logged transaction + /// + /// This method only applies to transactions created from a logged database instance. + /// Calling this method on regular transaction is a no-op. + /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. + /// + [StringFormatMethod("format")] + public static void Annotate(this IFdbReadOnlyTransaction trans, string format, object? arg0) + { + if (trans.IsLogged()) trans.Annotate(string.Format(CultureInfo.InvariantCulture, format, arg0)); + } + + /// Annotate a logged transaction + /// + /// This method only applies to transactions created from a logged databaseinstance. + /// Calling this method on regular transaction is a no-op. + /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. + /// + [StringFormatMethod("format")] + public static void Annotate(this IFdbReadOnlyTransaction trans, string format, object? arg0, object? arg1) + { + if (trans.IsLogged()) trans.Annotate(string.Format(CultureInfo.InvariantCulture, format, arg0, arg1)); + } + + /// Annotate a logged transaction + /// + /// This method only applies to transactions created from a logged database instance. + /// Calling this method on regular transaction is a no-op. + /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. + /// + [StringFormatMethod("format")] + public static void Annotate(this IFdbReadOnlyTransaction trans, string format, object? arg0, object? arg1, object? arg2) + { + if (trans.IsLogged()) trans.Annotate(string.Format(CultureInfo.InvariantCulture, format, arg0, arg1, arg2)); + } + + /// Annotate a logged transaction + /// + /// This method only applies to transactions created from a logged database instance. + /// Calling this method on regular transaction is a no-op. + /// You can call first, if you don't want to pay the cost of formatting the message when logging not enabled. + /// + [StringFormatMethod("format")] + public static void Annotate(this IFdbReadOnlyTransaction trans, string format, params object?[] args) + { + if (trans.IsLogged()) trans.Annotate(string.Format(CultureInfo.InvariantCulture, format, args)); + } + + } + +} diff --git a/FoundationDB.Client/Filters/Logging/FdbTransactionLog.Commands.cs b/FoundationDB.Client/Tracing/FdbTransactionLog.Commands.cs similarity index 97% rename from FoundationDB.Client/Filters/Logging/FdbTransactionLog.Commands.cs rename to FoundationDB.Client/Tracing/FdbTransactionLog.Commands.cs index 5267e4a14..d83a07d5d 100644 --- a/FoundationDB.Client/Filters/Logging/FdbTransactionLog.Commands.cs +++ b/FoundationDB.Client/Tracing/FdbTransactionLog.Commands.cs @@ -33,6 +33,7 @@ namespace FoundationDB.Filters.Logging using System.Diagnostics; using System.Globalization; using System.Text; + using System.Threading; using System.Threading.Tasks; using Doxense; using Doxense.Diagnostics.Contracts; @@ -883,6 +884,29 @@ protected override string Dump(VersionStamp? value) } } + public sealed class GetApproximateSizeCommand : Command + { + public override Operation Op => Operation.GetApproximateSize; + } + + public sealed class GetAddressesForKeyCommand : Command + { + /// Selector to a key in the database + public Slice Key { get; } + + public override Operation Op => Operation.GetAddressesForKey; + + public GetAddressesForKeyCommand(Slice key) + { + this.Key = key; + } + + public override int? ArgumentBytes => this.Key.Count; + + public override string GetArguments(KeyResolver resolver) => resolver.Resolve(this.Key); + + } + public sealed class TouchMetadataVersionKeyCommand : AtomicCommand { public TouchMetadataVersionKeyCommand(Slice key) diff --git a/FoundationDB.Client/Filters/Logging/FdbTransactionLog.cs b/FoundationDB.Client/Tracing/FdbTransactionLog.cs similarity index 81% rename from FoundationDB.Client/Filters/Logging/FdbTransactionLog.cs rename to FoundationDB.Client/Tracing/FdbTransactionLog.cs index 7353bf624..f059f5f97 100644 --- a/FoundationDB.Client/Filters/Logging/FdbTransactionLog.cs +++ b/FoundationDB.Client/Tracing/FdbTransactionLog.cs @@ -35,10 +35,30 @@ namespace FoundationDB.Filters.Logging using System.Runtime.CompilerServices; using System.Text; using System.Threading; + using System.Threading.Tasks; + using Doxense; using Doxense.Diagnostics.Contracts; using FoundationDB.Client; using JetBrains.Annotations; + [Flags] + [PublicAPI] + public enum FdbLoggingOptions + { + /// Default logging options + Default = 0, + + /// Capture the stacktrace of the caller method that created the transaction + RecordCreationStackTrace = 0x100, + + /// Capture the stacktrace of the caller method for each operation + RecordOperationStackTrace = 0x200, + + /// Capture all the stack traces. + /// This is a shortcut for | + WithStackTraces = RecordCreationStackTrace | RecordOperationStackTrace, + } + /// Container that logs all operations performed by a transaction public sealed partial class FdbTransactionLog { @@ -73,6 +93,171 @@ public FdbTransactionLog(FdbLoggingOptions options) /// Only if the option is set public StackTrace? CallSite { get; private set; } + #region Interning... + + private byte[] m_buffer = new byte[1024]; + private int m_offset; + private readonly object m_lock = new object(); + + internal Slice Grab(Slice slice) + { + if (slice.IsNullOrEmpty) return slice.IsNull ? Slice.Nil : Slice.Empty; + + lock (m_lock) + { + if (slice.Count > m_buffer.Length - m_offset) + { // not enough ? + if (slice.Count >= 2048) + { + return slice.Memoize(); + } + m_buffer = new byte[4096]; + m_offset = 0; + } + + int start = m_offset; + slice.CopyTo(m_buffer, m_offset); + m_offset += slice.Count; + return m_buffer.AsSlice(start, slice.Count); + } + } + + internal Slice Grab(ReadOnlySpan slice) + { + if (slice.Length == 0) return Slice.Empty; + + lock (m_lock) + { + if (slice.Length > m_buffer.Length - m_offset) + { // not enough ? + if (slice.Length >= 2048) + { + return slice.ToArray().AsSlice(); + } + m_buffer = new byte[4096]; + m_offset = 0; + } + + int start = m_offset; + slice.CopyTo(m_buffer.AsSpan(m_offset)); + m_offset += slice.Length; + return m_buffer.AsSlice(start, slice.Length); + } + } + + internal Slice[] Grab(Slice[]? slices) + { + if (slices == null || slices.Length == 0) return Array.Empty(); + + lock (m_lock) + { + int total = 0; + for (int i = 0; i < slices.Length; i++) + { + total += slices[i].Count; + } + + if (total > m_buffer.Length - m_offset) + { + return FdbKey.Merge(Slice.Empty, slices); + } + + var res = new Slice[slices.Length]; + for (int i = 0; i < slices.Length; i++) + { + res[i] = Grab(slices[i]); + } + return res; + } + } + + internal KeySelector Grab(in KeySelector selector) + { + return new KeySelector( + Grab(selector.Key), + selector.OrEqual, + selector.Offset + ); + } + + internal KeySelector[] Grab(KeySelector[]? selectors) + { + if (selectors == null || selectors.Length == 0) return Array.Empty(); + + var res = new KeySelector[selectors.Length]; + for (int i = 0; i < selectors.Length; i++) + { + res[i] = Grab(in selectors[i]); + } + return res; + } + + #endregion + + internal void Execute(FdbTransaction tr, TCommand cmd, Action action) + where TCommand : FdbTransactionLog.Command + { + Exception? error = null; + BeginOperation(cmd); + try + { + action(tr, cmd); + } + catch (Exception e) + { + error = e; + throw; + } + finally + { + EndOperation(cmd, error); + } + } + + internal async Task ExecuteAsync(FdbTransaction tr, TCommand cmd, Func lambda, Action? onSuccess = null) + where TCommand : FdbTransactionLog.Command + { + Exception? error = null; + BeginOperation(cmd); + try + { + await lambda(tr, cmd).ConfigureAwait(false); + } + catch (Exception e) + { + error = e; + throw; + } + finally + { + EndOperation(cmd, error); + if (error == null) onSuccess?.Invoke(tr, cmd, this); + } + } + + internal async Task ExecuteAsync(FdbTransaction tr, TCommand cmd, Func> lambda) + where TCommand : FdbTransactionLog.Command + { + Exception? error = null; + BeginOperation(cmd); + try + { + TResult result = await lambda(tr, cmd).ConfigureAwait(false); + cmd.Result = Maybe.Return(result); + return result; + } + catch (Exception e) + { + error = e; + cmd.Result = Maybe.Error(System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e)); + throw; + } + finally + { + EndOperation(cmd, error); + } + } + internal StackTrace CaptureStackTrace(int numStackFramesToSkip) { #if DEBUG @@ -151,6 +336,8 @@ internal bool ShouldCaptureOperationStackTrace /// This value will be non-null only if the last attempt used VersionStamped operations and committed successfully. public VersionStamp? VersionStamp { get; internal set; } + internal bool RequiresVersionStamp { get; set; } + internal static long GetTimestamp() { return Stopwatch.GetTimestamp(); @@ -206,6 +393,11 @@ public void Stop(IFdbTransaction trans) } } + public void Annotate(string text) + { + AddOperation(new LogCommand(text), countAsOperation: false); + } + /// Adds a new already completed command to the log public void AddOperation(Command cmd, bool countAsOperation = true) { @@ -522,6 +714,8 @@ public enum Operation OnError, SetOption, GetVersionStamp, + GetAddressesForKey, + GetApproximateSize, Log, } diff --git a/FoundationDB.Layers.Experimental/Messaging/WorkerPoolTest.cs b/FoundationDB.Layers.Experimental/Messaging/WorkerPoolTest.cs index d704a0aaa..b9dd355b6 100644 --- a/FoundationDB.Layers.Experimental/Messaging/WorkerPoolTest.cs +++ b/FoundationDB.Layers.Experimental/Messaging/WorkerPoolTest.cs @@ -67,10 +67,10 @@ private async Task RunAsync(IFdbDatabase db, IDynamicKeySubspace location, Cance StringBuilder sb = new StringBuilder(); - db = new FdbLoggedDatabase(db, false, false, (log) => + db.SetDefaultLogHandler((log) => { - sb.AppendLine(log.Log.GetTimingsReport(true)); - //Console.WriteLine(log.Log.GetTimingsReport(true)); + sb.AppendLine(log.GetTimingsReport(true)); + //Console.WriteLine(log.GetTimingsReport(true)); }); try { diff --git a/FoundationDB.Samples/Program.cs b/FoundationDB.Samples/Program.cs index 8343a95f8..0d4d4a37e 100644 --- a/FoundationDB.Samples/Program.cs +++ b/FoundationDB.Samples/Program.cs @@ -45,9 +45,11 @@ static StreamWriter GetLogFile(string name) static IFdbDatabase GetLoggedDatabase(IFdbDatabase db, StreamWriter stream, bool autoFlush = false) { - if (stream == null) return db; - - return new FdbLoggedDatabase(db, false, false, (tr) => { stream.WriteLine(tr.Log.GetTimingsReport(true)); if (autoFlush) stream.Flush(); }); + if (stream != null) + { + db.SetDefaultLogHandler(log => { stream.WriteLine(log.GetTimingsReport(true)); if (autoFlush) stream.Flush(); }); + } + return db; } public static void RunAsyncCommand(Func command) diff --git a/FoundationDB.Tests/ExoticTestCases.cs b/FoundationDB.Tests/ExoticTestCases.cs index a629cc990..53650fb21 100644 --- a/FoundationDB.Tests/ExoticTestCases.cs +++ b/FoundationDB.Tests/ExoticTestCases.cs @@ -224,80 +224,79 @@ public async Task Test_Case_6b() [Test] public async Task Test_Case_7() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); - { - var location = db.Root; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); + + var location = db.Root; - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); + + var vX = Slice.FromFixedU32BE(0x55555555); // X + var vY = Slice.FromFixedU32BE(0x66666666); // Y + var vL1 = Slice.FromFixedU32BE(0x11111111); // Low + var vL2 = Slice.FromFixedU32BE(0x22222222); // Low + var vH2 = Slice.FromFixedU32BE(0xFFFFFFFF); // Hi + var vH1 = Slice.FromFixedU32BE(0xEEEEEEEE); // Hi + var vA = Slice.FromFixedU32BE(0xAAAAAAAA); // 10101010 + var vC = Slice.FromFixedU32BE(0xCCCCCCCC); // 11001100 + + var cmds = new[] { - var subspace = await location.Resolve(tr); - - var vX = Slice.FromFixedU32BE(0x55555555); // X - var vY = Slice.FromFixedU32BE(0x66666666); // Y - var vL1 = Slice.FromFixedU32BE(0x11111111); // Low - var vL2 = Slice.FromFixedU32BE(0x22222222); // Low - var vH2 = Slice.FromFixedU32BE(0xFFFFFFFF); // Hi - var vH1 = Slice.FromFixedU32BE(0xEEEEEEEE); // Hi - var vA = Slice.FromFixedU32BE(0xAAAAAAAA); // 10101010 - var vC = Slice.FromFixedU32BE(0xCCCCCCCC); // 11001100 - - var cmds = new[] - { - new { Op = "SET", Left = vX, Right = vY }, - new { Op = "ADD", Left = vX, Right = vY }, - new { Op = "AND", Left = vA, Right = vC }, - new { Op = "OR", Left = vA, Right = vC }, - new { Op = "XOR", Left = vA, Right = vC }, - new { Op = "MIN", Left = vL1, Right = vL2 }, - new { Op = "MAX", Left = vH1, Right = vH2 }, - }; - - Action apply = (t, op, k, v) => - { - switch (op) - { - case "SET": - t.Set(k, v); - break; - case "ADD": - t.AtomicAdd(k, v); - break; - case "AND": - t.AtomicAnd(k, v); - break; - case "OR": - t.AtomicOr(k, v); - break; - case "XOR": - t.AtomicXor(k, v); - break; - case "MIN": - t.AtomicMin(k, v); - break; - case "MAX": - t.AtomicMax(k, v); - break; - default: - Assert.Fail(); - break; - } - }; - - for (int i = 0; i < cmds.Length; i++) + new { Op = "SET", Left = vX, Right = vY }, + new { Op = "ADD", Left = vX, Right = vY }, + new { Op = "AND", Left = vA, Right = vC }, + new { Op = "OR", Left = vA, Right = vC }, + new { Op = "XOR", Left = vA, Right = vC }, + new { Op = "MIN", Left = vL1, Right = vL2 }, + new { Op = "MAX", Left = vH1, Right = vH2 }, + }; + + Action apply = (t, op, k, v) => + { + switch (op) { - for (int j = 0; j < cmds.Length; j++) - { - var key = subspace.Encode(cmds[i].Op + "_" + cmds[j].Op); - Log($"{i};{j} = {key}"); - apply(tr, cmds[i].Op, key, cmds[i].Left); - apply(tr, cmds[j].Op, key, cmds[j].Right); - } + case "SET": + t.Set(k, v); + break; + case "ADD": + t.AtomicAdd(k, v); + break; + case "AND": + t.AtomicAnd(k, v); + break; + case "OR": + t.AtomicOr(k, v); + break; + case "XOR": + t.AtomicXor(k, v); + break; + case "MIN": + t.AtomicMin(k, v); + break; + case "MAX": + t.AtomicMax(k, v); + break; + default: + Assert.Fail(); + break; } + }; - await tr.CommitAsync(); + for (int i = 0; i < cmds.Length; i++) + { + for (int j = 0; j < cmds.Length; j++) + { + var key = subspace.Encode(cmds[i].Op + "_" + cmds[j].Op); + Log($"{i};{j} = {key}"); + apply(tr, cmds[i].Op, key, cmds[i].Left); + apply(tr, cmds[j].Op, key, cmds[j].Right); + } } + + await tr.CommitAsync(); } } } @@ -305,29 +304,26 @@ public async Task Test_Case_7() [Test] public async Task Test_Case_8() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); + await db.WriteAsync(async tr => { - - await db.WriteAsync(async tr => + var subspace = await db.Root.Resolve(tr); + tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999\x00")); + for (int i = 0; i < 1000; i++) { - var subspace = await db.Root.Resolve(tr); - tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999\x00")); - for (int i = 0; i < 1000; i++) - { - tr.Set(subspace.Encode("K" + i.ToString("D4")), Slice.FromFixedU32BE((uint)i)); - } - }, this.Cancellation); + tr.Set(subspace.Encode("K" + i.ToString("D4")), Slice.FromFixedU32BE((uint)i)); + } + }, this.Cancellation); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 100; i++) + { + using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) { - using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) - { - var subspace = await db.Root.Resolve(tr); - var res = await tr.GetAsync(subspace.Encode("K" + i.ToString("D4"))); - Dump(res); - } + var subspace = await db.Root.Resolve(tr); + var res = await tr.GetAsync(subspace.Encode("K" + i.ToString("D4"))); + Dump(res); } } } @@ -336,40 +332,39 @@ await db.WriteAsync(async tr => [Test] public async Task Test_Case_9() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); + + // clear everything + await db.WriteAsync(async (tr) => { - // clear everything - await db.WriteAsync(async (tr) => - { - var subspace = await db.Root.Resolve(tr); - tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999Z")); - }, this.Cancellation); + var subspace = await db.Root.Resolve(tr); + tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999Z")); + }, this.Cancellation); - await db.WriteAsync(async tr => - { - var subspace = await db.Root.Resolve(tr); - tr.Set(subspace.Encode("K0123"), Value("V0123")); - }, this.Cancellation); - await db.WriteAsync(async tr => - { - var subspace = await db.Root.Resolve(tr); - tr.Set(subspace.Encode("K0789"), Value("V0789")); - }, this.Cancellation); + await db.WriteAsync(async tr => + { + var subspace = await db.Root.Resolve(tr); + tr.Set(subspace.Encode("K0123"), Value("V0123")); + }, this.Cancellation); + await db.WriteAsync(async tr => + { + var subspace = await db.Root.Resolve(tr); + tr.Set(subspace.Encode("K0789"), Value("V0789")); + }, this.Cancellation); - using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) - { - var subspace = await db.Root.Resolve(tr); - await tr.GetValuesAsync(subspace.EncodeMany(new[] { "K0123", "K0234", "K0456", "K0567", "K0789" })); - } + using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) + { + var subspace = await db.Root.Resolve(tr); + await tr.GetValuesAsync(subspace.EncodeMany(new[] { "K0123", "K0234", "K0456", "K0567", "K0789" })); + } - // once more with feelings - using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) - { - var subspace = await db.Root.Resolve(tr); - await tr.GetValuesAsync(subspace.EncodeMany(new[] { "K0123", "K0234", "K0456", "K0567", "K0789" })); - } + // once more with feelings + using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) + { + var subspace = await db.Root.Resolve(tr); + await tr.GetValuesAsync(subspace.EncodeMany(new[] { "K0123", "K0234", "K0456", "K0567", "K0789" })); } } } @@ -377,36 +372,34 @@ await db.WriteAsync(async tr => [Test] public async Task Test_Case_10() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); - { - // clear everything and write some values - await db.WriteAsync(async tr => - { - var subspace = await db.Root.Resolve(tr); - tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999Z")); - for (int i = 0; i < 100; i++) - { - tr.Set(subspace.Encode("K" + i.ToString("D4")), Value("V" + i.ToString("D4"))); - } - }, this.Cancellation); + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + // clear everything and write some values + await db.WriteAsync(async tr => + { + var subspace = await db.Root.Resolve(tr); + tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999Z")); + for (int i = 0; i < 100; i++) { - var subspace = await db.Root.Resolve(tr); - tr.ClearRange(subspace.Encode("K0010"), subspace.Encode("K0020")); - tr.ClearRange(subspace.Encode("K0050"), subspace.Encode("K0060")); + tr.Set(subspace.Encode("K" + i.ToString("D4")), Value("V" + i.ToString("D4"))); + } + }, this.Cancellation); - _ = await tr.GetRangeAsync( - KeySelector.FirstGreaterOrEqual(subspace.Encode("K0000")), - KeySelector.LastLessOrEqual(subspace.Encode("K9999")), - new FdbRangeOptions { Mode = FdbStreamingMode.WantAll, Reverse = true } - ); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await db.Root.Resolve(tr); + tr.ClearRange(subspace.Encode("K0010"), subspace.Encode("K0020")); + tr.ClearRange(subspace.Encode("K0050"), subspace.Encode("K0060")); - //no commit - } + _ = await tr.GetRangeAsync( + KeySelector.FirstGreaterOrEqual(subspace.Encode("K0000")), + KeySelector.LastLessOrEqual(subspace.Encode("K9999")), + new FdbRangeOptions { Mode = FdbStreamingMode.WantAll, Reverse = true } + ); + //no commit } } } @@ -414,58 +407,56 @@ await db.WriteAsync(async tr => [Test] public async Task Test_Case_11() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); - { - var location = db.Root; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); - // clear everything and write some values - await db.WriteAsync(async tr => - { - var subspace = await location.Resolve(tr); - tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999Z")); - for (int i = 0; i < 100; i++) - { - tr.Set(subspace.Encode("K" + i.ToString("D4")), Value("V" + i.ToString("D4"))); - } - }, this.Cancellation); + var location = db.Root; - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + // clear everything and write some values + await db.WriteAsync(async tr => + { + var subspace = await location.Resolve(tr); + tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K9999Z")); + for (int i = 0; i < 100; i++) { - var subspace = await location.Resolve(tr); - - tr.ClearRange(subspace.Encode("K0010"), subspace.Encode("K0020")); - tr.ClearRange(subspace.Encode("K0050"), subspace.Encode("K0060")); - tr.Set(subspace.Encode("K0021"), Slice.Empty); - tr.Set(subspace.Encode("K0042"), Slice.Empty); - - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0005"))); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0010"))); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0015"))); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0022"))); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0049"))); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0050"))); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0055"))); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0061"))); - - //no commit + tr.Set(subspace.Encode("K" + i.ToString("D4")), Value("V" + i.ToString("D4"))); } + }, this.Cancellation); - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) - { - var subspace = await location.Resolve(tr); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); + + tr.ClearRange(subspace.Encode("K0010"), subspace.Encode("K0020")); + tr.ClearRange(subspace.Encode("K0050"), subspace.Encode("K0060")); + tr.Set(subspace.Encode("K0021"), Slice.Empty); + tr.Set(subspace.Encode("K0042"), Slice.Empty); + + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0005"))); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0010"))); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0015"))); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0022"))); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0049"))); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0050"))); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0055"))); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0061"))); + + //no commit + } - //tr.SetOption(FdbTransactionOption.ReadYourWritesDisable); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0000"))); // equal=false, offset=1 - await tr.GetKeyAsync(KeySelector.FirstGreaterThan(subspace.Encode("K0011"))); // equal=true, offset=1 - await tr.GetKeyAsync(KeySelector.LastLessOrEqual(subspace.Encode("K0022"))); // equal=true, offset=0 - await tr.GetKeyAsync(KeySelector.LastLessThan(subspace.Encode("K0033"))); // equal=false, offset=0 + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0040")) + 1000); // equal=false, offset=7 ? - await tr.GetKeyAsync(KeySelector.LastLessThan(subspace.Encode("K0050")) + 1000); // equal=false, offset=6 ? - } + //tr.SetOption(FdbTransactionOption.ReadYourWritesDisable); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0000"))); // equal=false, offset=1 + await tr.GetKeyAsync(KeySelector.FirstGreaterThan(subspace.Encode("K0011"))); // equal=true, offset=1 + await tr.GetKeyAsync(KeySelector.LastLessOrEqual(subspace.Encode("K0022"))); // equal=true, offset=0 + await tr.GetKeyAsync(KeySelector.LastLessThan(subspace.Encode("K0033"))); // equal=false, offset=0 + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("K0040")) + 1000); // equal=false, offset=7 ? + await tr.GetKeyAsync(KeySelector.LastLessThan(subspace.Encode("K0050")) + 1000); // equal=false, offset=6 ? } } } @@ -473,37 +464,36 @@ await db.WriteAsync(async tr => [Test] public async Task Test_Case_12() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); - { - var location = db.Root; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) - { - var subspace = await location.Resolve(tr); - await tr.GetAsync(subspace.Encode("KGET")); - tr.AddReadConflictRange(subspace.Encode("KRC0"), subspace.Encode("KRC0")); - tr.AddWriteConflictRange(subspace.Encode("KWRITECONFLICT0"), subspace.Encode("KWRITECONFLICT1")); - tr.Set(subspace.Encode("KWRITE"), Slice.Empty); - await tr.CommitAsync(); - } + var location = db.Root; - // once more with feelings - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) - { - var subspace = await location.Resolve(tr); - tr.SetOption(FdbTransactionOption.ReadYourWritesDisable); - await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("KGETKEY"))); - } + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); + await tr.GetAsync(subspace.Encode("KGET")); + tr.AddReadConflictRange(subspace.Encode("KRC0"), subspace.Encode("KRC0")); + tr.AddWriteConflictRange(subspace.Encode("KWRITECONFLICT0"), subspace.Encode("KWRITECONFLICT1")); + tr.Set(subspace.Encode("KWRITE"), Slice.Empty); + await tr.CommitAsync(); + } - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) - { - var subspace = await location.Resolve(tr); - tr.AddReadConflictRange(subspace.Encode("KRC0"), subspace.Encode("KRC1")); - tr.Set(subspace.Encode("KWRITE"), Slice.Empty); - await tr.CommitAsync(); - } + // once more with feelings + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); + tr.SetOption(FdbTransactionOption.ReadYourWritesDisable); + await tr.GetKeyAsync(KeySelector.FirstGreaterOrEqual(subspace.Encode("KGETKEY"))); + } + + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); + tr.AddReadConflictRange(subspace.Encode("KRC0"), subspace.Encode("KRC1")); + tr.Set(subspace.Encode("KWRITE"), Slice.Empty); + await tr.CommitAsync(); } } } @@ -511,45 +501,44 @@ public async Task Test_Case_12() [Test] public async Task Test_Case_13() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); - { - var location = db.Root; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); - // clear everything and write some values - await db.WriteAsync(async (tr) => - { - var subspace = await location.Resolve(tr); - tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K~~~~")); - tr.Set(subspace.Encode("K000"), Value("BEGIN")); - for (int i = 0; i < 5; i++) - { - tr.Set(subspace.Encode("K" + i + "A"), Value("V111")); - tr.Set(subspace.Encode("K" + i + "B"), Value("V222")); - tr.Set(subspace.Encode("K" + i + "C"), Value("V333")); - tr.Set(subspace.Encode("K" + i + "D"), Value("V444")); - tr.Set(subspace.Encode("K" + i + "E"), Value("V555")); - tr.Set(subspace.Encode("K" + i + "F"), Value("V666")); - tr.Set(subspace.Encode("K" + i + "G"), Value("V777")); - tr.Set(subspace.Encode("K" + i + "H"), Value("V888")); - } - tr.Set(subspace.Encode("K~~~"), Value("END")); - }, this.Cancellation); + var location = db.Root; - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + // clear everything and write some values + await db.WriteAsync(async (tr) => + { + var subspace = await location.Resolve(tr); + tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K~~~~")); + tr.Set(subspace.Encode("K000"), Value("BEGIN")); + for (int i = 0; i < 5; i++) { - var subspace = await location.Resolve(tr); + tr.Set(subspace.Encode("K" + i + "A"), Value("V111")); + tr.Set(subspace.Encode("K" + i + "B"), Value("V222")); + tr.Set(subspace.Encode("K" + i + "C"), Value("V333")); + tr.Set(subspace.Encode("K" + i + "D"), Value("V444")); + tr.Set(subspace.Encode("K" + i + "E"), Value("V555")); + tr.Set(subspace.Encode("K" + i + "F"), Value("V666")); + tr.Set(subspace.Encode("K" + i + "G"), Value("V777")); + tr.Set(subspace.Encode("K" + i + "H"), Value("V888")); + } + tr.Set(subspace.Encode("K~~~"), Value("END")); + }, this.Cancellation); - tr.Set(subspace.Encode("KZZZ"), Value("V999")); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); - var r = await tr.GetRangeAsync( - KeySelector.FirstGreaterOrEqual(subspace.Encode("K0B")), - KeySelector.FirstGreaterOrEqual(subspace.Encode("K0G")) - ); + tr.Set(subspace.Encode("KZZZ"), Value("V999")); - await tr.CommitAsync(); - } + var r = await tr.GetRangeAsync( + KeySelector.FirstGreaterOrEqual(subspace.Encode("K0B")), + KeySelector.FirstGreaterOrEqual(subspace.Encode("K0G")) + ); + + await tr.CommitAsync(); } } } @@ -557,41 +546,39 @@ await db.WriteAsync(async (tr) => [Test] public async Task Test_Case_14() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); - { - var location = db.Root; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); - // clear everything and write some values - await db.WriteAsync(async tr => - { - var subspace = await location.Resolve(tr); - tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K~~~~")); - tr.SetValues(Enumerable.Range(0, 100).Select(i => new KeyValuePair(subspace.Encode("K" + i.ToString("D4")), Value("V" + i.ToString("D4"))))); - tr.Set(subspace.Encode("K~~~"), Value("END")); - }, this.Cancellation); + var location = db.Root; + + // clear everything and write some values + await db.WriteAsync(async tr => + { + var subspace = await location.Resolve(tr); + tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K~~~~")); + tr.SetValues(Enumerable.Range(0, 100).Select(i => new KeyValuePair(subspace.Encode("K" + i.ToString("D4")), Value("V" + i.ToString("D4"))))); + tr.Set(subspace.Encode("K~~~"), Value("END")); + }, this.Cancellation); - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); + + tr.ClearRange(subspace.Encode("K0042"), Value("K0069")); + + var r = await tr.GetRangeAsync( + KeySelector.FirstGreaterOrEqual(subspace.Encode("K0040")), + KeySelector.FirstGreaterOrEqual(subspace.Encode("K0080")), + new FdbRangeOptions { Mode = FdbStreamingMode.WantAll } + ); + // T 1 + // => GETRANGE( (< 'KAAA<00>' +1) .. (< LAST +1) + Log($"Count={r.Count}, HasMore={r.HasMore}"); + foreach (var kvp in r) { - var subspace = await location.Resolve(tr); - - tr.ClearRange(subspace.Encode("K0042"), Value("K0069")); - - var r = await tr.GetRangeAsync( - KeySelector.FirstGreaterOrEqual(subspace.Encode("K0040")), - KeySelector.FirstGreaterOrEqual(subspace.Encode("K0080")), - new FdbRangeOptions { Mode = FdbStreamingMode.WantAll } - ); - // T 1 - // => GETRANGE( (< 'KAAA<00>' +1) .. (< LAST +1) - Log($"Count={r.Count}, HasMore={r.HasMore}"); - foreach (var kvp in r) - { - Log($"{kvp.Key} = {kvp.Value}"); - } + Log($"{kvp.Key} = {kvp.Value}"); } - } } } @@ -599,38 +586,36 @@ await db.WriteAsync(async tr => [Test] public async Task Test_Case_15() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = FoundationDB.Filters.Logging.FdbLoggingExtensions.Logged(zedb, (tr) => Log(tr.Log.GetTimingsReport(true))); - { - var location = db.Root; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); - // clear everything and write some values - await db.WriteAsync(async tr => - { - var subspace = await location.Resolve(tr); - tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K~~~~")); - tr.Set(subspace.Encode("KAAA"), Value("V111")); - tr.Set(subspace.Encode("KBBB"), Value("V222")); - tr.Set(subspace.Encode("KCCC"), Value("V333")); - tr.Set(subspace.Encode("K~~~"), Value("END")); - }, this.Cancellation); - - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) - { - var subspace = await location.Resolve(tr); + var location = db.Root; - // set a key, then read it, and check if it could conflict on it (it should not!) - tr.Set(subspace.Encode("KBBB"), Value("V222b")); - await tr.GetAsync(subspace.Encode("KBBB")); + // clear everything and write some values + await db.WriteAsync(async tr => + { + var subspace = await location.Resolve(tr); + tr.ClearRange(subspace.Encode("K0000"), subspace.Encode("K~~~~")); + tr.Set(subspace.Encode("KAAA"), Value("V111")); + tr.Set(subspace.Encode("KBBB"), Value("V222")); + tr.Set(subspace.Encode("KCCC"), Value("V333")); + tr.Set(subspace.Encode("K~~~"), Value("END")); + }, this.Cancellation); - // read a key, then set it, and check if it could conflict on it (it should!) - await tr.GetAsync(subspace.Encode("KCCC")); - tr.Set(subspace.Encode("KCCC"), Value("V333b")); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await location.Resolve(tr); - await tr.CommitAsync(); - } + // set a key, then read it, and check if it could conflict on it (it should not!) + tr.Set(subspace.Encode("KBBB"), Value("V222b")); + await tr.GetAsync(subspace.Encode("KBBB")); + + // read a key, then set it, and check if it could conflict on it (it should!) + await tr.GetAsync(subspace.Encode("KCCC")); + tr.Set(subspace.Encode("KCCC"), Value("V333b")); + await tr.CommitAsync(); } } } @@ -639,102 +624,101 @@ await db.WriteAsync(async tr => public async Task Test_Case_16() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { - var db = zedb.Logged((tr) => Log(tr.Log.GetTimingsReport(true))); + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); + + //using (var tr = db.BeginTransaction(this.Cancellation)) + //{ + // tr.ClearRange(subspace.Encode("K"), subspace.Encode("KZZZZZZZZZ")); + // await tr.CommitAsync(); + //} + //return; + + // set the key + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace = await db.Root.Resolve(tr); + tr.Set(subspace.Encode("KAAA"), Value("VALUE_AAA")); + await tr.CommitAsync(); + } + // set the key + using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) { - //using (var tr = db.BeginTransaction(this.Cancellation)) - //{ - // tr.ClearRange(subspace.Encode("K"), subspace.Encode("KZZZZZZZZZ")); - // await tr.CommitAsync(); - //} - //return; + var subspace = await db.Root.Resolve(tr); + await tr.GetAsync(subspace.Encode("KAAA")); + } - // set the key - using (var tr = await db.BeginTransactionAsync(this.Cancellation)) - { - var subspace = await db.Root.Resolve(tr); - tr.Set(subspace.Encode("KAAA"), Value("VALUE_AAA")); - await tr.CommitAsync(); - } - // set the key - using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) - { - var subspace = await db.Root.Resolve(tr); - await tr.GetAsync(subspace.Encode("KAAA")); - } + await Task.Delay(500); + + // first: concurrent trans, set only, no conflict + using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) + using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) + { + await Task.WhenAll(tr1.GetReadVersionAsync(), tr2.GetReadVersionAsync()); - await Task.Delay(500); + var subspace1 = await db.Root.Resolve(tr1); + var subspace2 = await db.Root.Resolve(tr2); - // first: concurrent trans, set only, no conflict - using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) - using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) - { - await Task.WhenAll(tr1.GetReadVersionAsync(), tr2.GetReadVersionAsync()); + tr1.Set(subspace1.Encode("KBBB"), Value("VALUE_BBB_111")); + tr2.Set(subspace2.Encode("KCCC"), Value("VALUE_CCC_111")); + var task1 = tr1.CommitAsync(); + var task2 = tr2.CommitAsync(); - var subspace1 = await db.Root.Resolve(tr1); - var subspace2 = await db.Root.Resolve(tr2); + await Task.WhenAll(task1, task2); + } - tr1.Set(subspace1.Encode("KBBB"), Value("VALUE_BBB_111")); - tr2.Set(subspace2.Encode("KCCC"), Value("VALUE_CCC_111")); - var task1 = tr1.CommitAsync(); - var task2 = tr2.CommitAsync(); + await Task.Delay(500); - await Task.WhenAll(task1, task2); - } + // first: concurrent trans, read + set, no conflict + using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) + using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace1 = await db.Root.Resolve(tr1); + var subspace2 = await db.Root.Resolve(tr2); - await Task.Delay(500); + await Task.WhenAll( + tr1.GetAsync(subspace1.Encode("KAAA")), + tr2.GetAsync(subspace2.Encode("KAAA")) + ); - // first: concurrent trans, read + set, no conflict - using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) - using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) - { - var subspace1 = await db.Root.Resolve(tr1); - var subspace2 = await db.Root.Resolve(tr2); + tr1.Set(subspace1.Encode("KBBB"), Value("VALUE_BBB_222")); + tr2.Set(subspace2.Encode("KCCC"), Value("VALUE_CCC_222")); + var task1 = tr1.CommitAsync(); + var task2 = tr2.CommitAsync(); - await Task.WhenAll( - tr1.GetAsync(subspace1.Encode("KAAA")), - tr2.GetAsync(subspace2.Encode("KAAA")) - ); + await Task.WhenAll(task1, task2); + } - tr1.Set(subspace1.Encode("KBBB"), Value("VALUE_BBB_222")); - tr2.Set(subspace2.Encode("KCCC"), Value("VALUE_CCC_222")); - var task1 = tr1.CommitAsync(); - var task2 = tr2.CommitAsync(); + await Task.Delay(500); + // first: concurrent trans, read + set, conflict + using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) + using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) + { + var subspace1 = await db.Root.Resolve(tr1); + var subspace2 = await db.Root.Resolve(tr2); + + await Task.WhenAll( + tr1.GetAsync(subspace1.Encode("KCCC")), + tr2.GetAsync(subspace2.Encode("KBBB")) + ); + tr1.Set(subspace1.Encode("KBBB"), Value("VALUE_BBB_333")); + tr2.Set(subspace2.Encode("KCCC"), Value("VALUE_CCC_333")); + var task1 = tr1.CommitAsync(); + var task2 = tr2.CommitAsync(); + + try + { await Task.WhenAll(task1, task2); } - - await Task.Delay(500); - - // first: concurrent trans, read + set, conflict - using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) - using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) + catch (Exception e) { - var subspace1 = await db.Root.Resolve(tr1); - var subspace2 = await db.Root.Resolve(tr2); - - await Task.WhenAll( - tr1.GetAsync(subspace1.Encode("KCCC")), - tr2.GetAsync(subspace2.Encode("KBBB")) - ); - tr1.Set(subspace1.Encode("KBBB"), Value("VALUE_BBB_333")); - tr2.Set(subspace2.Encode("KCCC"), Value("VALUE_CCC_333")); - var task1 = tr1.CommitAsync(); - var task2 = tr2.CommitAsync(); - - try - { - await Task.WhenAll(task1, task2); - } - catch (Exception e) - { - Log(e.Message); - } + Log(e.Message); } - - Log("DONE!!!"); } + + Log("DONE!!!"); } } @@ -742,7 +726,7 @@ await Task.WhenAll( [Test][Ignore("This test requires the database to be stopped!")] public async Task Test_Case_17() { - using (var zedb = await OpenTestPartitionAsync()) + using (var db = await OpenTestPartitionAsync()) { //THIS TEST MUST BE PERFORMED WITH THE CLUSTER DOWN! (net stop fdbmonitor) @@ -751,14 +735,14 @@ public async Task Test_Case_17() // "future_version": ALWAYS ~10 ms // "not_committed": start with 5, 10, 15, etc... but after 4 or 5, then transition into a random number between 0 and 1 sec - using (var tr = await zedb.BeginReadOnlyTransactionAsync(this.Cancellation)) + using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) { await tr.OnErrorAsync(FdbError.PastVersion).ConfigureAwait(false); await tr.OnErrorAsync(FdbError.NotCommitted).ConfigureAwait(false); } - using (var tr = await zedb.BeginReadOnlyTransactionAsync(this.Cancellation)) + using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) { for (int i = 0; i < 20; i++) { @@ -778,8 +762,6 @@ public async Task Test_Case_17() } } } - - } } diff --git a/FoundationDB.Tests/Filters/LoggingFilterFacts.cs b/FoundationDB.Tests/Filters/LoggingFilterFacts.cs index 42503f661..4e3e61c4e 100644 --- a/FoundationDB.Tests/Filters/LoggingFilterFacts.cs +++ b/FoundationDB.Tests/Filters/LoggingFilterFacts.cs @@ -87,28 +87,29 @@ await db.WriteAsync(async (tr) => }, this.Cancellation); bool first = true; - Action logHandler = (tr) => + Action logHandler = (log) => { if (first) { - Log(tr.Log.GetCommandsReport()); + Log(log.GetCommandsReport()); first = false; } - Log(tr.Log.GetTimingsReport(true)); + Log(log.GetTimingsReport(true)); }; // create a logged version of the database - var logged = new FdbLoggedDatabase(db, false, false, logHandler); + db.SetDefaultLogHandler(logHandler); for (int k = 0; k < N; k++) { Log("==== " + k + " ==== "); Log(); - await logged.WriteAsync(async (tr) => + await db.WriteAsync(async (tr) => { - Assert.That(tr, Is.InstanceOf()); + Assert.That(tr.Log, Is.Not.Null); + Assert.That(tr.IsLogged(), Is.True); var subspace = await location.Resolve(tr); diff --git a/FoundationDB.Tests/Layers/DirectoryFacts.cs b/FoundationDB.Tests/Layers/DirectoryFacts.cs index c70cf1824..44c5e6362 100644 --- a/FoundationDB.Tests/Layers/DirectoryFacts.cs +++ b/FoundationDB.Tests/Layers/DirectoryFacts.cs @@ -59,9 +59,7 @@ public async Task Test_Allocator() #if ENABLE_LOGGING var list = new List(); - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { list.Add(tr.Log); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => list.Add(log)); #endif var hpa = new FdbHighContentionAllocator(location); @@ -69,7 +67,7 @@ public async Task Test_Allocator() var ids = new HashSet(); // allocate a single new id - long id = await hpa.ReadWriteAsync(logged, (tr, state) => state.AllocateAsync(tr), this.Cancellation); + long id = await hpa.ReadWriteAsync(db, (tr, state) => state.AllocateAsync(tr), this.Cancellation); ids.Add(id); await DumpSubspace(db, location); @@ -77,7 +75,7 @@ public async Task Test_Allocator() // allocate a batch of new ids for (int i = 0; i < 100; i++) { - id = await hpa.ReadWriteAsync(logged, (tr, state) => state.AllocateAsync(tr), this.Cancellation); + id = await hpa.ReadWriteAsync(db, (tr, state) => state.AllocateAsync(tr), this.Cancellation); if (ids.Contains(id)) { await DumpSubspace(db, location); @@ -108,9 +106,7 @@ public async Task Test_CreateOrOpen_Simple() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { Log(tr.Log.GetTimingsReport(true)); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif // put the nodes under (..,"DL",\xFE,) and the content under (..,"DL",) @@ -122,7 +118,7 @@ public async Task Test_CreateOrOpen_Simple() // first call should create a new subspace (with a random prefix) FdbDirectorySubspace foo; - using (var tr = await logged.BeginTransactionAsync(this.Cancellation)) + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { foo = await dl.CreateOrOpenAsync(tr, FdbPath.Parse("/Foo")); await tr.CommitAsync(); @@ -142,7 +138,7 @@ public async Task Test_CreateOrOpen_Simple() // second call should return the same subspace - var foo2 = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); + var foo2 = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); #if DEBUG Log("After opening 'Foo':"); await DumpSubspace(db, location); @@ -169,9 +165,7 @@ public async Task Test_CreateOrOpen_With_Layer() #if ENABLE_LOGGING var list = new List(); - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { list.Add(tr.Log); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => list.Add(log)); #endif // put the nodes under (..,"DL",\xFE,) and the content under (..,"DL",) @@ -181,7 +175,7 @@ public async Task Test_CreateOrOpen_With_Layer() Assert.That(dl.Content, Is.EqualTo(location)); // first call should create a new subspace (with a random prefix) - var foo = await logged.ReadWriteAsync(tr => dl.CreateOrOpenAsync(tr, FdbPath.Parse("/Foo[AcmeLayer]")), this.Cancellation); + var foo = await db.ReadWriteAsync(tr => dl.CreateOrOpenAsync(tr, FdbPath.Parse("/Foo[AcmeLayer]")), this.Cancellation); #if DEBUG await DumpSubspace(db, location); #endif @@ -196,7 +190,7 @@ public async Task Test_CreateOrOpen_With_Layer() Assert.That(foo.DirectoryLayer, Is.SameAs(dl)); // second call should return the same subspace - var foo2 = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo[AcmeLayer]")), this.Cancellation); + var foo2 = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo[AcmeLayer]")), this.Cancellation); Assert.That(foo2, Is.Not.Null); Assert.That(foo2.FullName, Is.EqualTo("/Foo")); Assert.That(foo2.Path, Is.EqualTo(FdbPath.Absolute(segFoo))); @@ -206,10 +200,10 @@ public async Task Test_CreateOrOpen_With_Layer() Assert.That(foo2.GetPrefix(), Is.EqualTo(foo.GetPrefix()), "Second call to CreateOrOpen should return the same subspace"); // opening it with wrong layer id should fail - Assert.That(async () => await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo[OtherLayer]")), this.Cancellation), Throws.InstanceOf(), "Opening with invalid layer id should fail"); + Assert.That(async () => await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo[OtherLayer]")), this.Cancellation), Throws.InstanceOf(), "Opening with invalid layer id should fail"); // opening without specifying a layer should disable the layer check - var foo3 = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); + var foo3 = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); Assert.That(foo3, Is.Not.Null); Assert.That(foo3.Layer, Is.EqualTo("AcmeLayer")); @@ -245,15 +239,13 @@ public async Task Test_CreateOrOpen_SubFolder() #if ENABLE_LOGGING var list = new List(); - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { list.Add(tr.Log); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => list.Add(log)); #endif // put the nodes under (..,"DL",\xFE,) and the content under (..,"DL",) var dl = FdbDirectoryLayer.Create(location); - using (var tr = await logged.BeginTransactionAsync(this.Cancellation)) + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { var folder = await dl.CreateOrOpenAsync(tr, FdbPath.Parse("/Foo/Bar/Baz")); Assert.That(folder, Is.Not.Null); @@ -266,16 +258,16 @@ public async Task Test_CreateOrOpen_SubFolder() #endif // all the parent folders should also now exist - var foo = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); + var foo = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); Assert.That(foo, Is.Not.Null); Assert.That(foo.FullName, Is.EqualTo("/Foo")); - var bar = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo/Bar")), this.Cancellation); + var bar = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo/Bar")), this.Cancellation); Assert.That(bar, Is.Not.Null); Assert.That(bar.FullName, Is.EqualTo("/Foo/Bar")); // We can also access /Foo/Bar via 'Foo' - using (var tr = await logged.BeginTransactionAsync(this.Cancellation)) + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { foo = await dl.OpenAsync(tr, FdbPath.Parse("/Foo")); Assert.That(foo, Is.Not.Null); @@ -332,14 +324,12 @@ public async Task Test_CreateOrOpen_AutoCreate_Nested_SubFolders() #if ENABLE_LOGGING var list = new List(); - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { list.Add(tr.Log); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => { list.Add(log); }); #endif var dl = FdbDirectoryLayer.Create(location); - var folder = await logged.ReadWriteAsync(tr => dl.CreateOrOpenAsync(tr, path), this.Cancellation); + var folder = await db.ReadWriteAsync(tr => dl.CreateOrOpenAsync(tr, path), this.Cancellation); #if DEBUG await DumpSubspace(db, location); #endif @@ -350,21 +340,21 @@ public async Task Test_CreateOrOpen_AutoCreate_Nested_SubFolders() // all the parent folders should also now exist Log("Checking parents from the root:"); - var outer = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer")), this.Cancellation); + var outer = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer")), this.Cancellation); Assert.That(outer, Is.Not.Null); Log($"- {outer} @ {outer.GetPrefixUnsafe()}"); Assert.That(outer.FullName, Is.EqualTo("/Outer")); Assert.That(outer.Layer, Is.EqualTo("partition")); Assert.That(outer.GetPrefixUnsafe().StartsWith(location.Prefix), Is.True, "Outer prefix {0} MUST starts with DL content location prefix {1} because it is contained in that partition", outer.GetPrefixUnsafe(), dl.Content.Prefix); - var foo = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo")), this.Cancellation); + var foo = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo")), this.Cancellation); Assert.That(foo, Is.Not.Null); Log($"- {foo} @ {foo.GetPrefixUnsafe()}"); Assert.That(foo.FullName, Is.EqualTo("/Outer/Foo")); Assert.That(foo.Layer, Is.EqualTo(string.Empty)); Assert.That(foo.GetPrefixUnsafe().StartsWith(outer.GetPrefixUnsafe()), Is.True, "Foo prefix {0} MUST starts with outer prefix {1} because it is contained in that partition", foo.GetPrefixUnsafe(), outer.GetPrefixUnsafe()); - var inner = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo/Inner")), this.Cancellation); + var inner = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo/Inner")), this.Cancellation); Assert.That(inner, Is.Not.Null); Log($"- {inner} @ {inner.GetPrefixUnsafe()}"); Assert.That(inner.FullName, Is.EqualTo("/Outer/Foo/Inner")); @@ -372,14 +362,14 @@ public async Task Test_CreateOrOpen_AutoCreate_Nested_SubFolders() Assert.That(inner.GetPrefixUnsafe().StartsWith(outer.GetPrefixUnsafe()), Is.True, "Inner prefix {0} MUST starts with outer prefix {1} because it is contained in that partition", inner.GetPrefixUnsafe(), outer.GetPrefixUnsafe()); Assert.That(inner.GetPrefixUnsafe().StartsWith(foo.GetPrefixUnsafe()), Is.False, "Inner prefix {0} MUST NOT starts with foo prefix {1} because they are both in the same partition", inner.GetPrefixUnsafe(), foo.GetPrefixUnsafe()); - var bar = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo/Inner/Bar")), this.Cancellation); + var bar = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo/Inner/Bar")), this.Cancellation); Assert.That(bar, Is.Not.Null); Log($"- {bar} @ {bar.GetPrefixUnsafe()}"); Assert.That(bar.FullName, Is.EqualTo("/Outer/Foo/Inner/Bar")); Assert.That(bar.Layer, Is.EqualTo("BarLayer")); Assert.That(bar.GetPrefixUnsafe().StartsWith(inner.GetPrefixUnsafe()), Is.True, "Bar prefix {0} MUST starts with inner prefix {1} because it is contained in that partition", bar.GetPrefixUnsafe(), inner.GetPrefixUnsafe()); - var baz = await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo/Inner/Bar/Baz")), this.Cancellation); + var baz = await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Outer/Foo/Inner/Bar/Baz")), this.Cancellation); Assert.That(baz, Is.Not.Null); Log($"- {baz} @ {baz.GetPrefixUnsafe()}"); Assert.That(baz.FullName, Is.EqualTo("/Outer/Foo/Inner/Bar/Baz")); @@ -409,16 +399,14 @@ public async Task Test_List_SubFolders() #if ENABLE_LOGGING var list = new List(); - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { list.Add(tr.Log); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => { list.Add(log); }); #endif Log("Creating directory tree..."); // linear subtree "/Foo/Bar/Baz" - await logged.ReadWriteAsync(tr => directory.CreateOrOpenAsync(tr, FdbPath.Parse("/Foo/Bar/Baz")), this.Cancellation); + await db.ReadWriteAsync(tr => directory.CreateOrOpenAsync(tr, FdbPath.Parse("/Foo/Bar/Baz")), this.Cancellation); // flat subtree "/numbers/0" to "/numbers/9" - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { for (int i = 0; i < 10; i++) { @@ -430,27 +418,27 @@ await logged.WriteAsync(async tr => #endif Log("List '/Foo':"); - var subdirs = await logged.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); + var subdirs = await db.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation); Assert.That(subdirs, Is.Not.Null); foreach (var subdir in subdirs) Log($"- " + subdir); Assert.That(subdirs.Count, Is.EqualTo(1)); Assert.That(subdirs[0], Is.EqualTo(FdbPath.Parse("/Foo/Bar"))); Log("List '/Foo/Bar':"); - subdirs = await logged.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/Foo/Bar")), this.Cancellation); + subdirs = await db.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/Foo/Bar")), this.Cancellation); Assert.That(subdirs, Is.Not.Null); foreach (var subdir in subdirs) Log($"- " + subdir); Assert.That(subdirs.Count, Is.EqualTo(1)); Assert.That(subdirs[0], Is.EqualTo(FdbPath.Parse("/Foo/Bar/Baz"))); Log("List '/Foo/Bar/Baz':"); - subdirs = await logged.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/Foo/Bar/Baz")), this.Cancellation); + subdirs = await db.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/Foo/Bar/Baz")), this.Cancellation); Assert.That(subdirs, Is.Not.Null); foreach (var subdir in subdirs) Log($"- " + subdir); Assert.That(subdirs.Count, Is.Zero); Log("List '/numbers':"); - subdirs = await logged.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/numbers")), this.Cancellation); + subdirs = await db.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Parse("/numbers")), this.Cancellation); Assert.That(subdirs, Is.Not.Null); foreach (var subdir in subdirs) Log($"- " + subdir); Assert.That(subdirs.Count, Is.EqualTo(10)); @@ -522,16 +510,14 @@ public async Task Test_Move_Folder() #if ENABLE_LOGGING var list = new List(); - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { list.Add(tr.Log); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => list.Add(log)); #endif // put the nodes under (..,"DL",\xFE,) and the content under (..,"DL",) var dl = FdbDirectoryLayer.Create(location); // create a folder at ('Foo',) - Slice originalPrefix = await logged.ReadWriteAsync(async tr => + Slice originalPrefix = await db.ReadWriteAsync(async tr => { var original = await dl.CreateOrOpenAsync(tr, FdbPath.Parse("/Foo")); #if DEBUG @@ -555,10 +541,10 @@ public async Task Test_Move_Folder() }, this.Cancellation); // opening the old path should fail - Assert.That(async () => await logged.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadAsync(tr => dl.OpenAsync(tr, FdbPath.Parse("/Foo")), this.Cancellation), Throws.InstanceOf()); // opening the new path should succeed - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var folder = await dl.OpenAsync(tr, FdbPath.Parse("/Bar")); Assert.That(folder, Is.Not.Null); @@ -591,21 +577,19 @@ public async Task Test_Remove_Folder() #if ENABLE_LOGGING var list = new List(); - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { list.Add(tr.Log); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => list.Add(log)); #endif // RemoveAsync var path = FdbPath.Parse("/CrashTestDummy"); - await logged.ReadWriteAsync(tr => dl.CreateAsync(tr, path), this.Cancellation); + await db.ReadWriteAsync(tr => dl.CreateAsync(tr, path), this.Cancellation); #if DEBUG await DumpSubspace(db, location); #endif // removing an existing folder should succeed - await logged.WriteAsync(tr => dl.RemoveAsync(tr, path), this.Cancellation); + await db.WriteAsync(tr => dl.RemoveAsync(tr, path), this.Cancellation); #if DEBUG await DumpSubspace(db, location); #endif @@ -613,27 +597,27 @@ public async Task Test_Remove_Folder() // Removing it a second time should fail Assert.That( - async () => await logged.WriteAsync(tr => dl.RemoveAsync(tr, path), this.Cancellation), + async () => await db.WriteAsync(tr => dl.RemoveAsync(tr, path), this.Cancellation), Throws.InstanceOf(), "Removing a non-existent directory should fail" ); // TryRemoveAsync - await logged.ReadWriteAsync(tr => dl.CreateAsync(tr, path), this.Cancellation); + await db.ReadWriteAsync(tr => dl.CreateAsync(tr, path), this.Cancellation); // attempting to remove a folder should return true - bool res = await logged.ReadWriteAsync(tr => dl.TryRemoveAsync(tr, path), this.Cancellation); + bool res = await db.ReadWriteAsync(tr => dl.TryRemoveAsync(tr, path), this.Cancellation); Assert.That(res, Is.True); // further attempts should return false - res = await logged.ReadWriteAsync(tr => dl.TryRemoveAsync(tr, path), this.Cancellation); + res = await db.ReadWriteAsync(tr => dl.TryRemoveAsync(tr, path), this.Cancellation); Assert.That(res, Is.False); // Corner Cases // removing the root folder is not allowed (too dangerous) - Assert.That(async () => await logged.WriteAsync(tr => dl.RemoveAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf(), "Attempting to remove the root directory should fail"); + Assert.That(async () => await db.WriteAsync(tr => dl.RemoveAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf(), "Attempting to remove the root directory should fail"); #if ENABLE_LOGGING foreach (var log in list) @@ -655,12 +639,10 @@ public async Task Test_Can_Change_Layer_Of_Existing_Directory() var directory = FdbDirectoryLayer.Create(location); #if ENABLE_LOGGING - var logged = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true))); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var folder = await directory.CreateAsync(tr, FdbPath.Root["Test", "foo"]); #if DEBUG @@ -681,7 +663,7 @@ await logged.WriteAsync(async tr => }, this.Cancellation); // opening the directory with the new layer should succeed - await logged.ReadAsync(async tr => + await db.ReadAsync(async tr => { var folder3 = await directory.OpenAsync(tr, FdbPath.Parse("/Test[bar]")); Assert.That(folder3, Is.Not.Null); @@ -690,7 +672,7 @@ await logged.ReadAsync(async tr => // opening the directory with the old layer should fail Assert.That( - async () => await logged.ReadAsync(tr => directory.OpenAsync(tr, FdbPath.Parse("/Test[foo]")), this.Cancellation), + async () => await db.ReadAsync(tr => directory.OpenAsync(tr, FdbPath.Parse("/Test[foo]")), this.Cancellation), Throws.InstanceOf() ); @@ -706,15 +688,13 @@ public async Task Test_Directory_Partitions() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true))); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif var dl = FdbDirectoryLayer.Create(location); Dump(dl); - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var segFoo = FdbPathSegment.Partition("Foo$"); var segBar = FdbPathSegment.Create("Bar"); @@ -777,15 +757,13 @@ public async Task Test_Directory_Cannot_Move_To_Another_Partition() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true))); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif var dl = FdbDirectoryLayer.Create(location); Dump(dl); - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { Log("Creating /Foo$ ..."); var foo = await dl.CreateAsync(tr, FdbPath.Absolute("Foo$[partition]")); @@ -814,7 +792,7 @@ await logged.WriteAsync(async tr => Log("Attempting to move /Foo$/Bar to /Bar ..."); Assert.That( - async () => await logged.ReadWriteAsync(tr => dl.MoveAsync(tr, FdbPath.Absolute("Foo$", "Bar"), FdbPath.Absolute("Bar")), this.Cancellation), + async () => await db.ReadWriteAsync(tr => dl.MoveAsync(tr, FdbPath.Absolute("Foo$", "Bar"), FdbPath.Absolute("Bar")), this.Cancellation), Throws.InstanceOf() ); } @@ -830,15 +808,13 @@ public async Task Test_Directory_Cannot_Move_To_A_Sub_Partition() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true))); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif var dl = FdbDirectoryLayer.Create(location); Dump(dl); - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var segOuter = FdbPathSegment.Partition("Outer"); var segInner = FdbPathSegment.Partition("Inner"); @@ -1036,35 +1012,33 @@ public async Task Test_Directory_Methods_Should_Fail_With_Empty_Paths() var directory = FdbDirectoryLayer.Create(location); - var logged = db; - // CreateOrOpen - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.CreateOrOpenAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.CreateOrOpenAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.CreateOrOpenAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.CreateOrOpenAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); // Create - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.CreateAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.CreateAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.CreateAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.CreateAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); // Open - Assert.That(async () => await logged.ReadAsync(tr => directory.OpenAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.ReadAsync(tr => directory.OpenAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadAsync(tr => directory.OpenAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadAsync(tr => directory.OpenAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); // Move - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Root, FdbPath.Parse("/foo")), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Parse("/foo"), FdbPath.Root), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Empty, FdbPath.Parse("/foo")), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Parse("/foo"), FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Root, FdbPath.Parse("/foo")), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Parse("/foo"), FdbPath.Root), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Empty, FdbPath.Parse("/foo")), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadWriteAsync(tr => directory.MoveAsync(tr, FdbPath.Parse("/foo"), FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); // Remove - Assert.That(async () => await logged.WriteAsync(tr => directory.RemoveAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.WriteAsync(tr => directory.RemoveAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); - Assert.That(async () => await logged.WriteAsync(tr => directory.RemoveAsync(tr, FdbPath.Absolute("Foo", " ", "Bar")), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.WriteAsync(tr => directory.RemoveAsync(tr, FdbPath.Root), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.WriteAsync(tr => directory.RemoveAsync(tr, FdbPath.Empty), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.WriteAsync(tr => directory.RemoveAsync(tr, FdbPath.Absolute("Foo", " ", "Bar")), this.Cancellation), Throws.InstanceOf()); // List - Assert.That(async () => await logged.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Root), this.Cancellation), Throws.Nothing); - Assert.That(async () => await logged.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Empty), this.Cancellation), Throws.Nothing); - Assert.That(async () => await logged.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Absolute("Foo", " ", "Bar")), this.Cancellation), Throws.InstanceOf()); + Assert.That(async () => await db.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Root), this.Cancellation), Throws.Nothing); + Assert.That(async () => await db.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Empty), this.Cancellation), Throws.Nothing); + Assert.That(async () => await db.ReadAsync(tr => directory.ListAsync(tr, FdbPath.Absolute("Foo", " ", "Bar")), this.Cancellation), Throws.InstanceOf()); } } @@ -1080,8 +1054,6 @@ public async Task Test_Directory_Partitions_Should_Disallow_Creation_Of_Direct_K var location = db.Root.ByKey("DL"); await CleanLocation(db, location); - var logged = db; - var dl = FdbDirectoryLayer.Create(location); Dump(dl); @@ -1096,7 +1068,7 @@ void ShouldPass(ActualValueDelegate del) Assert.That(del, Throws.Nothing); } - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var partition = await dl.CreateAsync(tr, FdbPath.Absolute("Foo[partition]")); Log($"Partition: {partition.Descriptor.Prefix:K}"); @@ -1176,23 +1148,19 @@ public async Task Test_Concurrent_Directory_Creation() var location = db.Root.ByKey("DL"); await CleanLocation(db, location); - var logged = db; - var dl = FdbDirectoryLayer.Create(location); Dump(dl); //to prevent any side effect from first time initialization of the directory layer, already create one dummy folder - await logged.ReadWriteAsync(tr => dl.CreateAsync(tr, FdbPath.Root["Zero"]), this.Cancellation); - - var logdb = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true))); + await db.ReadWriteAsync(tr => dl.CreateAsync(tr, FdbPath.Root["Zero"]), this.Cancellation); var f = FdbDirectoryLayer.AnnotateTransactions; try { FdbDirectoryLayer.AnnotateTransactions = true; - using (var tr1 = await logdb.BeginTransactionAsync(this.Cancellation)) - using (var tr2 = await logdb.BeginTransactionAsync(this.Cancellation)) + using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) + using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) { await Task.WhenAll( @@ -1237,8 +1205,6 @@ public async Task Test_Concurrent_Directory_Creation_With_Custom_Prefix() var location = db.Root.ByKey("DL"); await CleanLocation(db, location); - var logged = db; - // to keep the test db in a good shape, we will still use tuples for our custom prefix, // but using strings so that they do not collide with the integers used by the normal allocator // ie: regular prefix would be ("DL", 123) and our custom prefixes will be ("DL", "abc") @@ -1247,17 +1213,15 @@ public async Task Test_Concurrent_Directory_Creation_With_Custom_Prefix() Dump(dl); //to prevent any side effect from first time initialization of the directory layer, already create one dummy folder - await logged.ReadWriteAsync(tr => dl.CreateAsync(tr, FdbPath.Absolute("Zero")), this.Cancellation); - - var logdb = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true))); + await db.ReadWriteAsync(tr => dl.CreateAsync(tr, FdbPath.Absolute("Zero")), this.Cancellation); var f = FdbDirectoryLayer.AnnotateTransactions; try { FdbDirectoryLayer.AnnotateTransactions = true; - using (var tr1 = await logdb.BeginTransactionAsync(this.Cancellation)) - using (var tr2 = await logdb.BeginTransactionAsync(this.Cancellation)) + using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) + using (var tr2 = await db.BeginTransactionAsync(this.Cancellation)) { await Task.WhenAll( @@ -1319,9 +1283,7 @@ public async Task Test_TryOpenCached() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { Log(tr.Log.GetTimingsReport(true)); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif // put the nodes under (..,"DL",\xFE,) and the content under (..,"DL",) @@ -1345,7 +1307,7 @@ void Validate(FdbDirectorySubspace actual, FdbDirectorySubspace expected) // first, initialize the subspace Log("Creating 'Foo' ..."); - var foo = await logged.ReadWriteAsync(tr => dl.CreateAsync(tr, pathFoo), this.Cancellation); + var foo = await db.ReadWriteAsync(tr => dl.CreateAsync(tr, pathFoo), this.Cancellation); Assert.That(foo, Is.Not.Null); Assert.That(foo.FullName, Is.EqualTo("/Foo")); Assert.That(foo.Path, Is.EqualTo(pathFoo)); @@ -1359,7 +1321,7 @@ void Validate(FdbDirectorySubspace actual, FdbDirectorySubspace expected) // first transaction wants to open the folder, which is not in cache. // => we expect the subspace to be a new instance Log("OpenCached (#1)..."); - var foo1 = await logged.ReadAsync(async tr => + var foo1 = await db.ReadAsync(async tr => { var folder = await dl.TryOpenCachedAsync(tr, pathFoo); Validate(folder, foo); @@ -1373,7 +1335,7 @@ void Validate(FdbDirectorySubspace actual, FdbDirectorySubspace expected) // second transaction wants to open the same folder, which should be in cache // => we expect the subspace to be the SAME instance Log("OpenCached (#2)..."); - var foo2 = await logged.ReadAsync(async tr => + var foo2 = await db.ReadAsync(async tr => { var folder = await dl.TryOpenCachedAsync(tr, pathFoo); Validate(folder, foo); @@ -1384,11 +1346,11 @@ void Validate(FdbDirectorySubspace actual, FdbDirectorySubspace expected) // update the global metadata version of the database Log("Bump the global metadataVersion only..."); - await logged.WriteAsync(tr => tr.TouchMetadataVersionKey(), this.Cancellation); + await db.WriteAsync(tr => tr.TouchMetadataVersionKey(), this.Cancellation); // This should NOT bust the DL cache because the change is unrelated Log("OpenCached (#3)..."); - var foo3 = await logged.ReadAsync(async tr => + var foo3 = await db.ReadAsync(async tr => { var folder = await dl.TryOpenCachedAsync(tr, pathFoo); Validate(folder, foo); @@ -1403,14 +1365,14 @@ void Validate(FdbDirectorySubspace actual, FdbDirectorySubspace expected) // creating a subfolder /Foo/Bar should bust the cache of the partition Log("Creating 'Foo/Bar' ..."); - await logged.ReadWriteAsync(tr => dl.CreateAsync(tr, pathBar), this.Cancellation); + await db.ReadWriteAsync(tr => dl.CreateAsync(tr, pathBar), this.Cancellation); #if DEBUG Log("After creating 'Foo/Bar':"); await DumpSubspace(db, location); #endif Log("OpenCached (#4)..."); - var foo4 = await logged.ReadAsync(async tr => + var foo4 = await db.ReadAsync(async tr => { var folder = await dl.TryOpenCachedAsync(tr, pathFoo); Validate(folder, foo); @@ -1422,7 +1384,7 @@ void Validate(FdbDirectorySubspace actual, FdbDirectorySubspace expected) Log("Get 'Foo'..."); // now another read, this time should be a cached instance - var foo5 = await logged.ReadAsync(async tr => + var foo5 = await db.ReadAsync(async tr => { var folder = await dl.TryOpenCachedAsync(tr, pathFoo); Validate(folder, foo); @@ -1444,9 +1406,7 @@ public async Task Test_TryOpenCached_Sequential() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { Log(tr.Log.GetTimingsReport(true)); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif var dl = FdbDirectoryLayer.Create(location); @@ -1464,7 +1424,7 @@ public async Task Test_TryOpenCached_Sequential() for (int k = 1; k <= K; k++) { Log($"Creating Batch #{k}/{K}..."); - await logged.WriteAsync( + await db.WriteAsync( async tr => { for (int i = 0; i < N; i++) await dl.CreateAsync(tr, FdbPath.Absolute("Students", k.ToString(), i.ToString())); @@ -1475,7 +1435,7 @@ await logged.WriteAsync( for (int r = 0; r < R; r++) { var sw = Stopwatch.StartNew(); - var folders = await logged.ReadAsync(async tr => + var folders = await db.ReadAsync(async tr => { // reading sequentially should be slow without caching for the first request, but then should be very fast var res = new List(); @@ -1507,16 +1467,14 @@ public async Task Test_TryOpenCached_Poisoned_After_Mutation() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { Log(tr.Log.GetTimingsReport(true)); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif var dl = FdbDirectoryLayer.Create(location); - await logged.ReadWriteAsync(tr => dl.CreateAsync(tr, FdbPath.Absolute("Foo")), this.Cancellation); + await db.ReadWriteAsync(tr => dl.CreateAsync(tr, FdbPath.Absolute("Foo")), this.Cancellation); - var fooCached = await logged.ReadAsync(async tr => + var fooCached = await db.ReadAsync(async tr => { var folder = await dl.TryOpenCachedAsync(tr, FdbPath.Absolute("Foo")); Assert.That(folder.Context, Is.InstanceOf()); @@ -1526,7 +1484,7 @@ public async Task Test_TryOpenCached_Poisoned_After_Mutation() // the fooCached instance should dead outside the transaction! Assert.That(() => fooCached.GetPrefix(), Throws.InstanceOf(), "Accessing a cached subspace outside the transaction should throw"); - var fooUncached = await logged.ReadAsync(tr => dl.TryOpenAsync(tr, FdbPath.Absolute("Foo")), this.Cancellation); + var fooUncached = await db.ReadAsync(tr => dl.TryOpenAsync(tr, FdbPath.Absolute("Foo")), this.Cancellation); Assert.That(() => fooUncached.Context.EnsureIsValid(), Throws.Nothing, "Accessing a non-cached subspace outside the transaction should not throw"); } } @@ -1541,9 +1499,7 @@ public async Task Test_SubspacePath_Resolve() await CleanLocation(db, location); #if ENABLE_LOGGING - var logged = new FdbLoggedDatabase(db, false, false, (tr) => { Log(tr.Log.GetTimingsReport(true)); }); -#else - var logged = db; + db.SetDefaultLogHandler((log) => Log(log.GetTimingsReport(true))); #endif var directoryLayer = FdbDirectoryLayer.Create(location); @@ -1552,33 +1508,28 @@ public async Task Test_SubspacePath_Resolve() // create the corresponding location Log("Creating " + dir); - var prefix = await logged.ReadWriteAsync(async tr => (await directoryLayer.CreateAsync(tr, dir.Path)).GetPrefix(), this.Cancellation); + var prefix = await db.ReadWriteAsync(async tr => (await directoryLayer.CreateAsync(tr, dir.Path)).GetPrefix(), this.Cancellation); Log("> Created under " + prefix); await DumpSubspace(db, location); // resolve the location - await logged.ReadAsync( - async tr => - { - Log("Resolving " + dir); - var subspace = await dir.Resolve(tr, directoryLayer); - Assert.That(subspace, Is.Not.Null); - Assert.That(subspace.Path, Is.EqualTo(dir.Path), ".Path"); - Log("> Found under " + subspace.GetPrefix()); - Assert.That(subspace.GetPrefix(), Is.EqualTo(prefix), ".Prefix"); - }, - this.Cancellation); + await db.ReadAsync(async tr => + { + Log("Resolving " + dir); + var subspace = await dir.Resolve(tr, directoryLayer); + Assert.That(subspace, Is.Not.Null); + Assert.That(subspace.Path, Is.EqualTo(dir.Path), ".Path"); + Log("> Found under " + subspace.GetPrefix()); + Assert.That(subspace.GetPrefix(), Is.EqualTo(prefix), ".Prefix"); + }, this.Cancellation); // resolving again should return a cached version - await logged.ReadAsync( - async tr => - { - var subspace = await dir.Resolve(tr, directoryLayer); - Assert.That(subspace.GetPrefix(), Is.EqualTo(prefix), ".Prefix"); - }, - this.Cancellation); - + await db.ReadAsync(async tr => + { + var subspace = await dir.Resolve(tr, directoryLayer); + Assert.That(subspace.GetPrefix(), Is.EqualTo(prefix), ".Prefix"); + }, this.Cancellation); } } diff --git a/FoundationDB.Tests/TestHelpers.cs b/FoundationDB.Tests/TestHelpers.cs index f7f5d5acc..1d8e28834 100644 --- a/FoundationDB.Tests/TestHelpers.cs +++ b/FoundationDB.Tests/TestHelpers.cs @@ -86,8 +86,10 @@ public static async Task CleanLocation(IFdbDatabase db, ISubspaceLocation locati // if the prefix part is empty, then we simply recursively remove the corresponding sub-directory tree // If it is not empty, we only remove the corresponding subspace (without touching the sub-directories!) - await db.WithoutLogging().WriteAsync(async tr => + await db.WriteAsync(async tr => { + tr.StopLogging(); + if (location.Path.Count == 0) { // subspace under the root of the partition @@ -119,11 +121,9 @@ public static async Task DumpSubspace(IFdbDatabase db, IKeySubspace subspace, Ca { Assert.That(db, Is.Not.Null); - // do not log - db = db.WithoutLogging(); - using (var tr = await db.BeginTransactionAsync(ct)) { + tr.StopLogging(); await DumpSubspace(tr, subspace).ConfigureAwait(false); } } @@ -132,11 +132,10 @@ public static async Task DumpLocation(IFdbDatabase db, ISubspaceLocation path, C { Assert.That(db, Is.Not.Null); - // do not log - db = db.WithoutLogging(); - using (var tr = await db.BeginTransactionAsync(ct)) { + tr.StopLogging(); + var subspace = await path.Resolve(tr); if (subspace == null) { diff --git a/FoundationDB.Tests/TransactionFacts.cs b/FoundationDB.Tests/TransactionFacts.cs index 2a60bc2a8..8f26c9898 100644 --- a/FoundationDB.Tests/TransactionFacts.cs +++ b/FoundationDB.Tests/TransactionFacts.cs @@ -39,11 +39,7 @@ namespace FoundationDB.Client.Tests using System.Text; using System.Threading; using System.Threading.Tasks; - using FoundationDB.Filters.Logging; using NUnit.Framework; -#if ENABLE_LOGGING - using FoundationDB.Filters.Logging; -#endif [TestFixture] public class TransactionFacts : FdbTest @@ -106,6 +102,8 @@ public async Task Test_Creating_A_ReadOnly_Transaction_Throws_When_Writing() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginReadOnlyTransactionAsync(this.Cancellation)) { Assert.That(tr, Is.Not.Null); @@ -132,6 +130,8 @@ public async Task Test_Creating_Concurrent_Transactions_Are_Independent() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + IFdbTransaction tr1 = null; IFdbTransaction tr2 = null; try @@ -174,6 +174,8 @@ public async Task Test_Commiting_An_Empty_Transaction_Does_Nothing() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { Assert.That(tr, Is.InstanceOf()); @@ -193,6 +195,8 @@ public async Task Test_Resetting_An_Empty_Transaction_Does_Nothing() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { // do nothing with it @@ -212,6 +216,8 @@ public async Task Test_Cancelling_An_Empty_Transaction_Does_Nothing() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { Assert.That(tr, Is.InstanceOf()); @@ -235,6 +241,8 @@ public async Task Test_Cancelling_Transaction_Before_Commit_Should_Throw_Immedia var location = db.Root.ByKey("test").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr); @@ -264,6 +272,8 @@ public async Task Test_Cancelling_Transaction_During_Commit_Should_Abort_Task() var rnd = new Random(); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr); @@ -302,6 +312,8 @@ public async Task Test_Cancelling_Token_During_Commit_Should_Abort_Task() var location = db.Root.ByKey("test").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + var rnd = new Random(); using(var cts = new CancellationTokenSource()) @@ -333,6 +345,8 @@ public async Task Test_Can_Get_Transaction_Read_Version() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { long ver = await tr.GetReadVersionAsync(); @@ -359,6 +373,8 @@ public async Task Test_Write_And_Read_Simple_Keys() var location = db.Root.ByKey("test").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // write a bunch of keys using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { @@ -426,6 +442,8 @@ public async Task Test_Can_Resolve_Key_Selector() } #endregion + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr); @@ -572,6 +590,8 @@ public async Task Test_Get_Multiple_Values() await tr.CommitAsync(); } + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr); @@ -621,6 +641,8 @@ public async Task Test_Get_Multiple_Keys() } #endregion + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr); @@ -699,11 +721,10 @@ public async Task Test_Can_Perform_Atomic_Operations() var location = db.Root.ByKey("test", "atomic"); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + //note: we take a risk by reading the key separately, but this simplifies the rest of the code ! - Task ResolveKey(string name) - { - return db.ReadAsync(async tr => (await location.Resolve(tr)).Encode(name), this.Cancellation); - } + Task ResolveKey(string name) => db.ReadAsync(async tr => (await location.Resolve(tr)).Encode(name), this.Cancellation); Slice key; @@ -781,6 +802,8 @@ public async Task Test_Can_AtomicAdd32() Log(location); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // setup await db.WriteAsync(async (tr) => { @@ -829,6 +852,8 @@ public async Task Test_Can_AtomicIncrement32() var location = db.Root.ByKey("test", "atomic").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // setup await db.WriteAsync(async tr => { @@ -872,6 +897,8 @@ public async Task Test_Can_AtomicAdd64() var location = db.Root.ByKey("test", "atomic").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // setup await db.WriteAsync(async (tr) => { @@ -915,6 +942,8 @@ public async Task Test_Can_AtomicIncrement64() var location = db.Root.ByKey("test", "atomic").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // setup await db.WriteAsync(async (tr) => { @@ -958,6 +987,8 @@ public async Task Test_Can_AtomicCompareAndClear() var location = db.Root.ByKey("test", "atomic").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // setup await db.WriteAsync(async (tr) => { @@ -1016,6 +1047,8 @@ await db.WriteAsync(async (tr) => //EEE does not exist }, this.Cancellation); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // execute await db.WriteAsync(async (tr) => { @@ -1050,6 +1083,8 @@ public async Task Test_Can_Snapshot_Read() var location = db.Root.ByKey("test").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // write a bunch of keys await db.WriteAsync(async (tr) => { @@ -1194,6 +1229,8 @@ await db.WriteAsync(async (tr) => tr.Set(subspace["foo"], Value("foo")); }, this.Cancellation); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var trA = await db.BeginTransactionAsync(this.Cancellation)) using (var trB = await db.BeginTransactionAsync(this.Cancellation)) { @@ -1230,6 +1267,8 @@ public async Task Test_Snapshot_Read_With_Concurrent_Change_Should_Not_Conflict( var location = db.Root.ByKey("test").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + await db.WriteAsync(async (tr) => { var subspace = await location.Resolve(tr); @@ -1271,6 +1310,8 @@ await db.WriteAsync(async (tr) => tr.Set(subspace.Encode("foo", 50), Value("fifty")); }, this.Cancellation); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // we will read the first key from [0, 100), expected 50 // but another transaction will insert 42, in effect changing the result of our range // => this should conflict the GetRange @@ -1353,6 +1394,8 @@ await db.WriteAsync(async (tr) => tr.Set(subspace.Encode("foo", 50), Value("fifty")); }, this.Cancellation); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // we will ask for the first key from >= 0, expecting 50, but if another transaction inserts something BEFORE 50, our key selector would have returned a different result, causing a conflict using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) @@ -1526,6 +1569,8 @@ public async Task Test_Read_Isolation() var location = db.Root.ByKey("test").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + await db.WriteAsync(async (tr) => { var subspace = await db.Root.Resolve(tr); @@ -1626,6 +1671,8 @@ await db.WriteAsync(async (tr) => Log("Initial db state:"); await DumpSubspace(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr); @@ -1729,6 +1776,8 @@ public async Task Test_ReadYourWritesDisable_Isolation() var location = db.Root.ByKey("test"); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + #region Default behaviour... // By default, a transaction see its own writes with non-snapshot reads @@ -1809,6 +1858,8 @@ public async Task Test_Can_Set_Read_Version() long committedVersion; + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // create first version using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) { @@ -1852,6 +1903,8 @@ public async Task Test_Has_Access_To_System_Keys() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { @@ -1955,6 +2008,8 @@ public async Task Test_Transaction_RetryLoop_Respects_DefaultRetryLimit_Value() // But if the DefaultRetryLimit and DefaultTimeout are set on the database instance, they should automatically be re-applied inside transaction loops! db.DefaultRetryLimit = 3; + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + int counter = 0; var t = db.ReadAsync((tr) => { @@ -1992,6 +2047,8 @@ public async Task Test_Transaction_RetryLoop_Resets_RetryLimit_And_Timeout() { using (var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { // simulate a first error @@ -2026,6 +2083,8 @@ public async Task Test_Can_Add_Read_Conflict_Range() var location = db.Root.ByKey("conflict").AsTyped(); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr1); @@ -2065,6 +2124,8 @@ public async Task Test_Can_Add_Write_Conflict_Range() var location = db.Root.ByKey("conflict"); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using (var tr1 = await db.BeginTransactionAsync(this.Cancellation)) { var subspace = await location.Resolve(tr1); @@ -2105,6 +2166,8 @@ public async Task Test_Can_Setup_And_Cancel_Watches() var location = db.Root.ByKey("test", "bigbrother"); await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + await db.WriteAsync(async tr => { var subspace = await location.Resolve(tr); @@ -2266,6 +2329,8 @@ await db.WriteAsync(async (tr) => tr.Set(subspace.Encode(1), Value("one")); }, this.Cancellation); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + // look for the address of key1 using (var tr = await db.BeginTransactionAsync(this.Cancellation)) { @@ -2395,6 +2460,8 @@ public async Task Test_Simple_Read_Transaction() { using(var db = await OpenTestDatabaseAsync()) { + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + using(var tr = await db.BeginTransactionAsync(this.Cancellation)) { _ = await tr.GetReadVersionAsync(); @@ -2519,6 +2586,8 @@ public async Task Test_VersionStamp_Operations() await CleanLocation(db, location); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); + VersionStamp vsActual; // will contain the actual version stamp used by the database Log("Inserting keys with version stamps:"); @@ -2647,17 +2716,13 @@ public async Task Test_GetMetadataVersion() { // reading the mv twice in _should_ return the same value, unless the test cluster is used by another application! -#if ENABLE_LOGGING - var logged = db.Logged(tr => Log(tr.Log.GetTimingsReport(true))); -#else - var logged = db; -#endif + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); - var version1 = await logged.ReadAsync(tr => tr.GetMetadataVersionKeyAsync(), this.Cancellation); + var version1 = await db.ReadAsync(tr => tr.GetMetadataVersionKeyAsync(), this.Cancellation); Assert.That(version1, Is.Not.Null, "Version should be valid"); Log($"Version1: {version1}"); - var version2 = await logged.ReadAsync(tr => tr.GetMetadataVersionKeyAsync(), this.Cancellation); + var version2 = await db.ReadAsync(tr => tr.GetMetadataVersionKeyAsync(), this.Cancellation); Assert.That(version1, Is.Not.Null, "Version should be valid"); Log($"Version2: {version2}"); @@ -2667,12 +2732,12 @@ public async Task Test_GetMetadataVersion() Log("Changing version..."); await db.WriteAsync(tr => tr.TouchMetadataVersionKey(), this.Cancellation); - var version3 = await logged.ReadAsync(tr => tr.GetMetadataVersionKeyAsync(), this.Cancellation); + var version3 = await db.ReadAsync(tr => tr.GetMetadataVersionKeyAsync(), this.Cancellation); Log($"Version3: {version3}"); Assert.That(version3, Is.Not.Null.And.Not.EqualTo(version2), "Metadata version should have changed"); // changing the metadata version and then reading it back from the same transaction should return - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { // We can read the version before var before = await tr.GetMetadataVersionKeyAsync(); @@ -2702,11 +2767,7 @@ public async Task Test_GetMetadataVersion_Custom_Keys() { using (var db = await OpenTestDatabaseAsync()) { -#if ENABLE_LOGGING - var logged = db.Logged(tr => Log(tr.Log.GetTimingsReport(true))); -#else - var logged = db; -#endif + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); const string foo = "Foo"; const string bar = "Bar"; @@ -2717,24 +2778,24 @@ public async Task Test_GetMetadataVersion_Custom_Keys() // - Bar: different version stamp // - Baz: _missing_ - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var subspace = await db.Root.Resolve(tr); tr.TouchMetadataVersionKey(subspace.Encode(foo)); }, this.Cancellation); - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var subspace = await db.Root.Resolve(tr); tr.TouchMetadataVersionKey(subspace.Encode(bar)); }, this.Cancellation); - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var subspace = await db.Root.Resolve(tr); tr.Clear(subspace.Encode(baz)); }, this.Cancellation); // changing the metadata version and then reading it back from the same transaction CANNOT WORK! - await logged.WriteAsync(async tr => + await db.WriteAsync(async tr => { var subspace = await db.Root.Resolve(tr); @@ -2922,13 +2983,13 @@ public async Task Test_Value_Checks() //NOTE: this test is vulnerable to transient errors that could happen to the cluster while it runs! (timeouts, etc...) //TODO: we should use a more robust way to "skip" the retries that are for unrelated reasons? - using (var zdb = await OpenTestDatabaseAsync()) + using (var db = await OpenTestDatabaseAsync()) { - var location = zdb.Root.ByKey("value_checks"); + var location = db.Root.ByKey("value_checks"); - await CleanLocation(zdb, location); + await CleanLocation(db, location); - var db = zdb.Logged(tr => Log(tr.Log.GetTimingsReport(true))); + db.SetDefaultLogHandler(log => Log(log.GetTimingsReport(true))); var initialA = Slice.FromStringAscii("Initial value of AAA"); var initialB = Slice.FromStringAscii("Initial value of BBB"); @@ -2936,7 +2997,7 @@ public async Task Test_Value_Checks() async Task RunCheck(Func test, Func handler, bool shouldCommit) { // read previous witness value - await zdb.WriteAsync(async tr => + await db.WriteAsync(async tr => { var subspace = (await location.Resolve(tr))!; @@ -2958,10 +3019,10 @@ await db.WriteAsync(async tr => await handler(tr, subspace); tr.Set(subspace.Encode("Witness"), Slice.FromStringAscii("New witness value")); }, this.Cancellation); - await DumpSubspace(zdb, location); + await DumpSubspace(db, location); // read back the witness key to see if commit happened or not. - var actual = await zdb.ReadAsync(async tr => + var actual = await db.ReadAsync(async tr => { var subspace = await location.Resolve(tr); return await tr.GetAsync(subspace.Encode("Witness"));