From 5fc8c9dc6a50e708d8682c85f56a806b52907274 Mon Sep 17 00:00:00 2001 From: Badrish Chandramouli Date: Sat, 19 Feb 2022 13:45:47 -0800 Subject: [PATCH] [C#] [WIP] Status return code v2 (#638) * Prototype of status as struct * improving status a bit * fix build break, expose underlying (raw) status code to user * misc * Update StatusCode * Update Remote to new Status(Code); fix test cases; fix StatusCode layout; fine-tune Status(Code) names; make StatusCode internal * Update StatusCode and Status to final spec * Update Remote to new Status spec * unrelated nit Co-authored-by: TedHartMS <15467143+TedHartMS@users.noreply.github.com> --- cs/benchmark/Functions.cs | 2 +- cs/playground/AsyncStress/FasterWrapper.cs | 12 +- cs/playground/AsyncStress/Program.cs | 2 +- .../AsyncStress/SerializedFasterWrapper.cs | 13 +- .../AsyncStress/SerializedUpdaters.cs | 4 +- cs/playground/CacheStoreConcurrent/Program.cs | 19 +- cs/playground/CacheStoreConcurrent/Types.cs | 2 +- .../ClassRecoveryDurability/Program.cs | 57 +++-- cs/playground/SumStore/ConcurrencyTest.cs | 4 +- cs/playground/SumStore/RecoveryTest.cs | 12 +- cs/remote/samples/FixedLenServer/Types.cs | 2 +- .../src/FASTER.server/BinaryServerSession.cs | 34 +-- .../src/FASTER.server/ServerKVFunctions.cs | 2 +- .../FASTER.server/WebsocketServerSession.cs | 36 +-- cs/samples/AzureBackedStore/Functions.cs | 2 +- cs/samples/AzureBackedStore/Program.cs | 8 +- cs/samples/CacheStore/Program.cs | 58 ++--- cs/samples/CacheStore/Types.cs | 2 +- cs/samples/HelloWorld/Program.cs | 30 +-- cs/samples/MemOnlyCache/Program.cs | 35 ++- cs/samples/ReadAddress/VersionedReadApp.cs | 4 +- cs/samples/SecondaryReaderStore/Program.cs | 2 +- cs/samples/StoreAsyncApi/Program.cs | 8 +- cs/samples/StoreAsyncApi/Types.cs | 2 +- cs/samples/StoreCheckpointRecover/Program.cs | 7 +- cs/samples/StoreCustomTypes/Program.cs | 10 +- cs/samples/StoreDiskReadBenchmark/Program.cs | 20 +- cs/samples/StoreDiskReadBenchmark/Types.cs | 2 +- cs/samples/StoreLogCompaction/Types.cs | 2 +- cs/samples/StoreVarLenTypes/AsciiSumSample.cs | 4 +- .../StoreVarLenTypes/CustomMemoryFunctions.cs | 2 +- .../CustomSpanByteFunctions.cs | 2 +- .../StoreVarLenTypes/MemoryByteSample.cs | 6 +- .../StoreVarLenTypes/MemoryIntSample.cs | 4 +- cs/samples/StoreVarLenTypes/SpanByteSample.cs | 6 +- cs/src/core/Async/DeleteAsync.cs | 15 +- cs/src/core/Async/RMWAsync.cs | 32 +-- cs/src/core/Async/ReadAsync.cs | 24 +- cs/src/core/Async/UpdateAsync.cs | 10 +- cs/src/core/Async/UpsertAsync.cs | 16 +- cs/src/core/ClientSession/ClientSession.cs | 2 +- cs/src/core/ClientSession/IFasterContext.cs | 16 +- .../ClientSession/LockableUnsafeContext.cs | 2 +- cs/src/core/ClientSession/UnsafeContext.cs | 2 +- cs/src/core/Compaction/FASTERCompaction.cs | 6 +- .../core/Compaction/LogCompactionFunctions.cs | 4 +- cs/src/core/Index/Common/Contexts.cs | 39 +++- cs/src/core/Index/FASTER/FASTER.cs | 54 +---- cs/src/core/Index/FASTER/FASTERBase.cs | 4 +- cs/src/core/Index/FASTER/FASTERImpl.cs | 137 +++++------ cs/src/core/Index/FASTER/FASTERIterator.cs | 2 +- cs/src/core/Index/FASTER/FASTERThread.cs | 45 +--- cs/src/core/Index/FASTER/ReadFlags.cs | 2 +- cs/src/core/Index/Interfaces/FunctionsBase.cs | 2 +- .../core/Index/Interfaces/IFasterSession.cs | 2 +- cs/src/core/Index/Interfaces/IFunctions.cs | 3 +- cs/src/core/Utilities/CompletionEvent.cs | 1 + cs/src/core/Utilities/Status.cs | 99 +++++++- cs/src/core/Utilities/StatusCode.cs | 126 ++++++++++ cs/test/AdvancedLockTests.cs | 22 +- cs/test/AsyncLargeObjectTests.cs | 2 +- cs/test/AsyncTests.cs | 4 +- cs/test/BasicDiskFASTERTests.cs | 4 +- cs/test/BasicFASTERTests.cs | 66 +++--- cs/test/BasicLockTests.cs | 6 +- cs/test/BlittableLogCompactionTests.cs | 24 +- cs/test/CompletePendingTests.cs | 12 +- cs/test/ExpirationTests.cs | 215 ++++++++++-------- cs/test/GenericByteArrayTests.cs | 2 +- cs/test/GenericDiskDeleteTests.cs | 29 ++- cs/test/GenericLogCompactionTests.cs | 49 ++-- cs/test/GenericStringTests.cs | 14 +- cs/test/InputOutputParameterTests.cs | 16 +- cs/test/LargeObjectTests.cs | 2 +- cs/test/LockableUnsafeContextTests.cs | 194 ++++++++++------ cs/test/LowMemAsyncTests.cs | 8 +- cs/test/MemoryLogCompactionTests.cs | 16 +- cs/test/MiscFASTERTests.cs | 36 +-- cs/test/NativeReadCacheTests.cs | 20 +- cs/test/NeedCopyUpdateTests.cs | 48 ++-- cs/test/ObjectFASTERTests.cs | 53 +++-- cs/test/ObjectReadCacheTests.cs | 26 +-- cs/test/ObjectRecoveryTest2.cs | 20 +- cs/test/ObjectRecoveryTest3.cs | 16 +- cs/test/ObjectTestTypes.cs | 23 +- cs/test/PostOperationsTests.cs | 2 +- cs/test/ReadAddressTests.cs | 28 +-- cs/test/ReadCacheChainTests.cs | 176 ++++++++------ cs/test/RecoverContinueTests.cs | 4 +- cs/test/RecoveryChecks.cs | 56 ++--- cs/test/RecoveryTests.cs | 8 +- cs/test/ReproReadCacheTest.cs | 9 +- cs/test/SessionFASTERTests.cs | 97 ++++---- cs/test/SharedDirectoryTests.cs | 3 +- cs/test/SimpleAsyncTests.cs | 54 ++--- cs/test/SimpleRecoveryTest.cs | 8 +- cs/test/SingleWriterTests.cs | 6 +- cs/test/SpanByteTests.cs | 4 +- cs/test/StateMachineTests.cs | 4 +- cs/test/TestTypes.cs | 17 +- cs/test/TestUtils.cs | 24 +- cs/test/UnsafeContextTests.cs | 52 ++--- cs/test/VLTestTypes.cs | 10 +- cs/test/VariableLengthStructFASTERTests.cs | 35 ++- docs/_docs/20-fasterkv-basics.md | 6 +- 105 files changed, 1436 insertions(+), 1156 deletions(-) create mode 100644 cs/src/core/Utilities/StatusCode.cs diff --git a/cs/benchmark/Functions.cs b/cs/benchmark/Functions.cs index c4fc12c1e..7260dd67e 100644 --- a/cs/benchmark/Functions.cs +++ b/cs/benchmark/Functions.cs @@ -83,7 +83,7 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va newValue.value = input.value + oldValue.value; } - public bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => true; + public void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) { } public bool NeedInitialUpdate(ref Key key, ref Input input, ref Output output) => true; diff --git a/cs/playground/AsyncStress/FasterWrapper.cs b/cs/playground/AsyncStress/FasterWrapper.cs index fec299ccd..ad7f651e0 100644 --- a/cs/playground/AsyncStress/FasterWrapper.cs +++ b/cs/playground/AsyncStress/FasterWrapper.cs @@ -56,7 +56,7 @@ public FasterWrapper(bool isRefType, bool useLargeLog, bool useOsReadBuffering = async ValueTask CompleteAsync(FasterKV.UpsertAsyncResult result) { var numPending = 0; - for (; result.Status == Status.PENDING; ++numPending) + for (; result.Status.Pending; ++numPending) result = await result.CompleteAsync().ConfigureAwait(false); return numPending; } @@ -64,7 +64,7 @@ async ValueTask CompleteAsync(FasterKV.UpsertAsyncResult CompleteAsync(FasterKV.RmwAsyncResult result) { var numPending = 0; - for (; result.Status == Status.PENDING; ++numPending) + for (; result.Status.Pending; ++numPending) result = await result.CompleteAsync().ConfigureAwait(false); return numPending; } @@ -82,7 +82,7 @@ public void Upsert(Key key, Value value) if (!_sessionPool.TryGet(out var session)) session = _sessionPool.GetAsync().GetAwaiter().GetResult(); var status = session.Upsert(key, value); - Assert.True(status != Status.PENDING); + Assert.False(status.Pending); _sessionPool.Return(session); } @@ -109,7 +109,7 @@ public void RMW(Key key, Value value) if (!_sessionPool.TryGet(out var session)) session = _sessionPool.GetAsync().GetAwaiter().GetResult(); var status = session.RMW(key, value); - Assert.True(status != Status.PENDING); + Assert.False(status.Pending); _sessionPool.Return(session); } @@ -137,14 +137,14 @@ public async ValueTask RMWChunkAsync((Key, Value)[] chunk, int offset, int count if (!_sessionPool.TryGet(out var session)) session = _sessionPool.GetAsync().GetAwaiter().GetResult(); var result = session.Read(key); - if (result.status == Status.PENDING) + if (result.status.Pending) { session.CompletePendingWithOutputs(out var completedOutputs, wait: true); int count = 0; for (; completedOutputs.Next(); ++count) { Assert.Equal(key, completedOutputs.Current.Key); - result = (Status.OK, completedOutputs.Current.Output); + result = (completedOutputs.Current.Status, completedOutputs.Current.Output); } completedOutputs.Dispose(); Assert.Equal(1, count); diff --git a/cs/playground/AsyncStress/Program.cs b/cs/playground/AsyncStress/Program.cs index c13040c6c..908f17337 100644 --- a/cs/playground/AsyncStress/Program.cs +++ b/cs/playground/AsyncStress/Program.cs @@ -267,7 +267,7 @@ async ValueTask doUpdates() Console.WriteLine(" Verifying read results ..."); Parallel.For(0, numOperations, i => { - Assert.Equal(Status.OK, results[i].Item1); + Assert.True(results[i].Item1.Found); Assert.Equal(database[i].Item2, results[i].Item2); }); Console.WriteLine(" Results verified"); diff --git a/cs/playground/AsyncStress/SerializedFasterWrapper.cs b/cs/playground/AsyncStress/SerializedFasterWrapper.cs index 4a08fb3e9..296489249 100644 --- a/cs/playground/AsyncStress/SerializedFasterWrapper.cs +++ b/cs/playground/AsyncStress/SerializedFasterWrapper.cs @@ -121,7 +121,7 @@ public void Update(TUpdater updater, Key key, Value valu } } - Assert.True(status != Status.PENDING); + Assert.False(status.Pending); _sessionPool.Return(session); } @@ -173,10 +173,11 @@ public async ValueTask UpdateChunkAsync(TUpdater updater var (status, output) = (await task.ConfigureAwait(false)).Complete(); _sessionPool.Return(session); + Assert.True(status.CompletedSuccessfully); using IMemoryOwner memoryOwner = output.Memory; - return (status, status != Status.OK ? default : MessagePackSerializer.Deserialize(memoryOwner.Memory)); + return (status, status.Found ? MessagePackSerializer.Deserialize(memoryOwner.Memory) : default); } public ValueTask<(Status, Value)> Read(Key key) @@ -197,14 +198,15 @@ public async ValueTask UpdateChunkAsync(TUpdater updater } } - if (result.Item1 == Status.PENDING) + if (result.Item1.Pending) { session.CompletePendingWithOutputs(out var completedOutputs, wait: true); int count = 0; for (; completedOutputs.Next(); ++count) { using IMemoryOwner memoryOwner = completedOutputs.Current.Output.Memory; - userResult = (completedOutputs.Current.Status, completedOutputs.Current.Status != Status.OK ? default : MessagePackSerializer.Deserialize(memoryOwner.Memory)); + Assert.True(completedOutputs.Current.Status.CompletedSuccessfully); + userResult = (completedOutputs.Current.Status, completedOutputs.Current.Status.Found ? MessagePackSerializer.Deserialize(memoryOwner.Memory) : default); } completedOutputs.Dispose(); Assert.Equal(1, count); @@ -212,7 +214,8 @@ public async ValueTask UpdateChunkAsync(TUpdater updater else { using IMemoryOwner memoryOwner = result.Item2.Memory; - userResult = (result.Item1, result.Item1 != Status.OK ? default : MessagePackSerializer.Deserialize(memoryOwner.Memory)); + Assert.True(result.Item1.CompletedSuccessfully); + userResult = (result.Item1, result.Item1.Found ? MessagePackSerializer.Deserialize(memoryOwner.Memory) : default); } _sessionPool.Return(session); return new ValueTask<(Status, Value)>(userResult); diff --git a/cs/playground/AsyncStress/SerializedUpdaters.cs b/cs/playground/AsyncStress/SerializedUpdaters.cs index 606eb1a3f..e2a051e45 100644 --- a/cs/playground/AsyncStress/SerializedUpdaters.cs +++ b/cs/playground/AsyncStress/SerializedUpdaters.cs @@ -26,7 +26,7 @@ public ValueTask.UpsertAsyncResult CompleteAsync(FasterKV.UpsertAsyncResult result) { var numPending = 0; - for (; result.Status == Status.PENDING; ++numPending) + for (; result.Status.Pending; ++numPending) result = await result.CompleteAsync().ConfigureAwait(false); return numPending; } @@ -43,7 +43,7 @@ public ValueTask.RmwAsyncResult CompleteAsync(FasterKV.RmwAsyncResult result) { var numPending = 0; - for (; result.Status == Status.PENDING; ++numPending) + for (; result.Status.Pending; ++numPending) result = await result.CompleteAsync().ConfigureAwait(false); return numPending; } diff --git a/cs/playground/CacheStoreConcurrent/Program.cs b/cs/playground/CacheStoreConcurrent/Program.cs index 6faa6043d..6a3931bed 100644 --- a/cs/playground/CacheStoreConcurrent/Program.cs +++ b/cs/playground/CacheStoreConcurrent/Program.cs @@ -175,18 +175,17 @@ private static void RandomReadWorkload(int threadid) var key = new CacheKey(k); var status = hts.Read(ref key, ref output); - switch (status) + if (status.Pending) { - case Status.PENDING: - statusPending++; - break; - case Status.OK: - if (output.value != key.key) - throw new Exception("Read error!"); - break; - default: - throw new Exception("Error!"); + statusPending++; + } + else if (status.Found) + { + if (output.value != key.key) + throw new Exception("Read error!"); } + else + throw new Exception("Error!"); i++; } /* diff --git a/cs/playground/CacheStoreConcurrent/Types.cs b/cs/playground/CacheStoreConcurrent/Types.cs index 531d15fb0..bfed6a2f0 100644 --- a/cs/playground/CacheStoreConcurrent/Types.cs +++ b/cs/playground/CacheStoreConcurrent/Types.cs @@ -85,7 +85,7 @@ public override void ReadCompletionCallback(ref CacheKey key, ref CacheValue inp { long ticks = Stopwatch.GetTimestamp() - ctx.ticks; - if (status == Status.NOTFOUND) + if (!status.Found) Console.WriteLine("Async: Value not found, latency = {0}ms", 1000 * (ticks - ctx.ticks) / (double)Stopwatch.Frequency); if (output.value != key.key) diff --git a/cs/playground/ClassRecoveryDurability/Program.cs b/cs/playground/ClassRecoveryDurability/Program.cs index f35319cbc..75b766c7b 100644 --- a/cs/playground/ClassRecoveryDurability/Program.cs +++ b/cs/playground/ClassRecoveryDurability/Program.cs @@ -4,18 +4,17 @@ using System; using System.Linq; using System.Threading.Tasks; -using FASTER.core; namespace ClassRecoveryDurablity { class Program { static bool stop; - static int deleteWindow = 5; - static int indexAhead = 10000000; - static int startDeleteHeight = 20; - static int addCount = 100; - static int deletePosition = 20; + static readonly int deleteWindow = 5; + static readonly int indexAhead = 10000000; + static readonly int startDeleteHeight = 20; + static readonly int addCount = 100; + static readonly int deletePosition = 20; static void Main(string[] args) { @@ -39,7 +38,7 @@ static void Main(string[] args) static void Filldb(int stopAfterIteration = 5) { - Storedb store = new Storedb(@"C:\FasterTest\data"); + Storedb store = new(@"C:\FasterTest\data"); Console.WriteLine("call init db"); var first = store.InitAndRecover(); @@ -52,7 +51,7 @@ static void Filldb(int stopAfterIteration = 5) var ss = store.db.For(new Types.StoreFunctions()).NewSession(); lastBlockvalue = new Types.StoreValue { value = BitConverter.GetBytes(0) }; - Types.StoreContext context1 = new Types.StoreContext(); + Types.StoreContext context1 = new(); ss.Upsert(ref lastblockKey, ref lastBlockvalue, context1, 1); ss.CompletePending(true); @@ -70,10 +69,10 @@ static void Filldb(int stopAfterIteration = 5) while (stop == false) { - Types.StoreInput input = new Types.StoreInput(); - Types.StoreOutput output = new Types.StoreOutput(); + Types.StoreInput input = new(); + Types.StoreOutput output = new(); lastblockKey = new Types.StoreKey { tableType = "L", key = new byte[1] { 0 } }; - Types.StoreContext context1 = new Types.StoreContext(); + Types.StoreContext context1 = new(); var blkStatus = session.Read(ref lastblockKey, ref input, ref output, context1, 1); var blockHeight = BitConverter.ToUInt32(output.value.value); blockHeight += 1; @@ -86,12 +85,12 @@ static void Filldb(int stopAfterIteration = 5) var upsertKey = new Types.StoreKey { tableType = "C", key = data.key }; var upsertValue = new Types.StoreValue { value = data.data }; - Types.StoreContext context2 = new Types.StoreContext(); + Types.StoreContext context2 = new(); var addStatus = session.Upsert(ref upsertKey, ref upsertValue, context2, 1); // Console.WriteLine("add=" + i); - if (addStatus != Status.OK) + if (!addStatus.InPlaceUpdatedRecord) throw new Exception(); } @@ -106,11 +105,11 @@ static void Filldb(int stopAfterIteration = 5) var data = Generate(i); var deteletKey = new Types.StoreKey { tableType = "C", key = data.key }; - Types.StoreContext context2 = new Types.StoreContext(); + Types.StoreContext context2 = new(); var deleteStatus = session.Delete(ref deteletKey, context2, 1); // Console.WriteLine("delete=" + i); - if (deleteStatus != Status.OK) + if (!deleteStatus.InPlaceUpdatedRecord) throw new Exception(); } } @@ -120,7 +119,7 @@ static void Filldb(int stopAfterIteration = 5) lastBlockvalue = new Types.StoreValue { value = BitConverter.GetBytes(blockHeight) }; lastblockKey = new Types.StoreKey { tableType = "L", key = new byte[1] { 0 } }; - Types.StoreContext context = new Types.StoreContext(); + Types.StoreContext context = new(); session.Upsert(ref lastblockKey, ref lastBlockvalue, context, 1); session.CompletePending(true); @@ -164,10 +163,10 @@ static void TestData(Storedb store, int count) var lastBlockvalue = new Types.StoreValue(); // test all data up to now. - Types.StoreInput input = new Types.StoreInput(); - Types.StoreOutput output = new Types.StoreOutput(); + Types.StoreInput input = new(); + Types.StoreOutput output = new(); lastblockKey = new Types.StoreKey { tableType = "L", key = new byte[1] { 0 } }; - Types.StoreContext context1 = new Types.StoreContext(); + Types.StoreContext context1 = new(); var blkStatus = session.Read(ref lastblockKey, ref input, ref output, context1, 1); var blockHeight = BitConverter.ToUInt32(output.value.value); @@ -188,41 +187,41 @@ static void TestData(Storedb store, int count) { var data = Generate(j); - Types.StoreInput input1 = new Types.StoreInput(); - Types.StoreOutput output1 = new Types.StoreOutput(); - Types.StoreContext context = new Types.StoreContext(); + Types.StoreInput input1 = new(); + Types.StoreOutput output1 = new(); + Types.StoreContext context = new(); var readKey = new Types.StoreKey { tableType = "C", key = data.key }; var deleteStatus = session.Read(ref readKey, ref input1, ref output1, context, 1); //Console.WriteLine("test delete=" + i); - if (deleteStatus == Status.PENDING) + if (deleteStatus.Pending) { session.CompletePending(true); context.FinalizeRead(ref deleteStatus, ref output1); } - if (deleteStatus != Status.NOTFOUND) + if (deleteStatus.Found) throw new Exception(); } else { var data = Generate(i); - Types.StoreInput input1 = new Types.StoreInput(); - Types.StoreOutput output1 = new Types.StoreOutput(); - Types.StoreContext context = new Types.StoreContext(); + Types.StoreInput input1 = new(); + Types.StoreOutput output1 = new(); + Types.StoreContext context = new(); var readKey = new Types.StoreKey { tableType = "C", key = data.key }; var addStatus = session.Read(ref readKey, ref input1, ref output1, context, 1); //Console.WriteLine("test add=" + i); - if (addStatus == Status.PENDING) + if (addStatus.Pending) { session.CompletePending(true); context.FinalizeRead(ref addStatus, ref output1); } - if (addStatus != Status.OK) + if (!addStatus.Found) throw new Exception(); if (output1.value.value.SequenceEqual(data.data) == false) diff --git a/cs/playground/SumStore/ConcurrencyTest.cs b/cs/playground/SumStore/ConcurrencyTest.cs index 4dd79e90e..8a1c06434 100644 --- a/cs/playground/SumStore/ConcurrencyTest.cs +++ b/cs/playground/SumStore/ConcurrencyTest.cs @@ -119,7 +119,7 @@ private void PopulateWorker(int threadId) { var status = session.RMW(ref inputArray[i].adId, ref inputArray[i], Empty.Default, i); - if (status != Status.OK && status != Status.NOTFOUND) + if (!status.CompletedSuccessfully) throw new Exception(); if (i % completePendingInterval == 0) @@ -156,7 +156,7 @@ public void Test() Input input = default; Output output = default; var status = session.Read(ref inputArray[i].adId, ref input, ref output, Empty.Default, i); - if (status == Status.PENDING) + if (status.Pending) throw new NotImplementedException(); inputArray[i].numClicks = output.value; diff --git a/cs/playground/SumStore/RecoveryTest.cs b/cs/playground/SumStore/RecoveryTest.cs index a600475a3..24a8b9f44 100644 --- a/cs/playground/SumStore/RecoveryTest.cs +++ b/cs/playground/SumStore/RecoveryTest.cs @@ -2,9 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; using System.Threading; using FASTER.core; @@ -167,8 +164,7 @@ private void PeriodicCheckpoints() Thread.Sleep(checkpointInterval); fht.TryInitiateFullCheckpoint(out Guid token, CheckpointType.Snapshot); - - fht.CompleteCheckpointAsync().GetAwaiter().GetResult(); + fht.CompleteCheckpointAsync().AsTask().GetAwaiter().GetResult(); Console.WriteLine("Completed checkpoint {0}", token); } @@ -176,7 +172,7 @@ private void PeriodicCheckpoints() private void Test() { - List sno = new List(); + List sno = new(); for (int i = 0; i < threadCount; i++) { @@ -191,7 +187,7 @@ private void Test() Input[] inputArray = new Input[numUniqueKeys]; for (int i = 0; i < numUniqueKeys; i++) { - inputArray[i].adId.adId = (i % numUniqueKeys); + inputArray[i].adId.adId = i % numUniqueKeys; inputArray[i].numClicks.numClicks = 0; } @@ -203,7 +199,7 @@ private void Test() { Output output = default; var status = session.Read(ref inputArray[i].adId, ref inputArray[i], ref output, Empty.Default, i); - Debug.Assert(status == Status.OK || status == Status.NOTFOUND); + Debug.Assert(status.CompletedSuccessfully); inputArray[i].numClicks.numClicks = output.value.numClicks; } diff --git a/cs/remote/samples/FixedLenServer/Types.cs b/cs/remote/samples/FixedLenServer/Types.cs index aa7fa2a33..45334b041 100644 --- a/cs/remote/samples/FixedLenServer/Types.cs +++ b/cs/remote/samples/FixedLenServer/Types.cs @@ -116,7 +116,7 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => true; + public void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) { } public bool NeedInitialUpdate(ref Key key, ref Input input, ref Output output) => true; diff --git a/cs/remote/src/FASTER.server/BinaryServerSession.cs b/cs/remote/src/FASTER.server/BinaryServerSession.cs index 9c95d8dda..e86401931 100644 --- a/cs/remote/src/FASTER.server/BinaryServerSession.cs +++ b/cs/remote/src/FASTER.server/BinaryServerSession.cs @@ -72,7 +72,7 @@ public override void CompleteRead(ref Output output, long ctx, Status status) hrw.Write((MessageType)(ctx >> 32), ref dcurr, (int)(dend - dcurr)); Write((int)(ctx & 0xffffffff), ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status != Status.NOTFOUND) + if (status.Found) serializer.Write(ref output, ref dcurr, (int)(dend - dcurr)); msgnum++; } @@ -89,7 +89,7 @@ public override void CompleteRMW(ref Output output, long ctx, Status status) hrw.Write((MessageType)(ctx >> 32), ref dcurr, (int)(dend - dcurr)); Write((int)(ctx & 0xffffffff), ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status == Status.OK || status == Status.NOTFOUND) + if (status.CompletedSuccessfully) serializer.Write(ref output, ref dcurr, (int)(dend - dcurr)); msgnum++; } @@ -167,9 +167,9 @@ private unsafe void ProcessBatch(byte* buf, int offset) hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status == Status.PENDING) + if (status.Pending) Write(pendingSeqNo++, ref dcurr, (int)(dend - dcurr)); - else if (status == Status.OK) + else if (status.Found) serializer.SkipOutput(ref dcurr); break; @@ -186,9 +186,9 @@ private unsafe void ProcessBatch(byte* buf, int offset) hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status == Status.PENDING) + if (status.Pending) Write(pendingSeqNo++, ref dcurr, (int)(dend - dcurr)); - else if (status == Status.OK || status == Status.NOTFOUND) + else if (status.CompletedSuccessfully) serializer.SkipOutput(ref dcurr); subscribeKVBroker?.Publish(keyPtr); @@ -269,11 +269,11 @@ private unsafe void Publish(ref byte* keyPtr, int keyLength, ref byte* valPtr, r else outputDcurr = dcurr + 6; - var status = Status.OK; + Status status = Status.CreateFound(); if (valPtr == null) status = session.Read(ref key, ref serializer.ReadInputByRef(ref inputPtr), ref serializer.AsRefOutput(outputDcurr, (int)(dend - dcurr)), ctx, 0); - if (status != Status.PENDING) + if (!status.Pending) { // Write six bytes (message | status | sid) hrw.Write(message, ref dcurr, (int)(dend - dcurr)); @@ -286,7 +286,7 @@ private unsafe void Publish(ref byte* keyPtr, int keyLength, ref byte* valPtr, r ref Value value = ref serializer.ReadValueByRef(ref valPtr); serializer.Write(ref value, ref dcurr, (int)(dend - dcurr)); } - else if (status == Status.OK) + else if (status.Found) serializer.SkipOutput(ref dcurr); } else @@ -305,15 +305,15 @@ private unsafe void Publish(ref byte* keyPtr, int keyLength, ref byte* valPtr, r } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool Write(ref Status s, ref byte* dst, int length) + private static unsafe bool Write(ref Status s, ref byte* dst, int length) { if (length < 1) return false; - *dst++ = (byte)s; + *dst++ = s.Value; return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool Write(int seqNo, ref byte* dst, int length) + private static unsafe bool Write(int seqNo, ref byte* dst, int length) { if (length < sizeof(int)) return false; *(int*)dst = seqNo; @@ -361,7 +361,7 @@ private bool HandlePubSub(MessageType message, ref byte* src, ref byte* d, ref b serializer.ReadInputByRef(ref src); int sid = subscribeKVBroker.Subscribe(ref keyStart, ref inputStart, this); - var status = Status.PENDING; + Status status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); Write(sid, ref dcurr, (int)(dend - dcurr)); @@ -383,7 +383,7 @@ private bool HandlePubSub(MessageType message, ref byte* src, ref byte* d, ref b serializer.ReadInputByRef(ref src); sid = subscribeKVBroker.PSubscribe(ref keyStart, ref inputStart, this); - status = Status.PENDING; + status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); Write(sid, ref dcurr, (int)(dend - dcurr)); @@ -401,7 +401,7 @@ private bool HandlePubSub(MessageType message, ref byte* src, ref byte* d, ref b ref Value val = ref serializer.ReadValueByRef(ref src); int valueLength = (int)(src - valPtr); - status = Status.OK; + status = Status.CreateFound(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); @@ -419,7 +419,7 @@ private bool HandlePubSub(MessageType message, ref byte* src, ref byte* d, ref b serializer.ReadKeyByRef(ref src); sid = subscribeBroker.Subscribe(ref keyStart, this); - status = Status.PENDING; + status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); Write(sid, ref dcurr, (int)(dend - dcurr)); @@ -435,7 +435,7 @@ private bool HandlePubSub(MessageType message, ref byte* src, ref byte* d, ref b serializer.ReadKeyByRef(ref src); sid = subscribeBroker.PSubscribe(ref keyStart, this); - status = Status.PENDING; + status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); Write(sid, ref dcurr, (int)(dend - dcurr)); diff --git a/cs/remote/src/FASTER.server/ServerKVFunctions.cs b/cs/remote/src/FASTER.server/ServerKVFunctions.cs index 210b8bfef..99c4d8145 100644 --- a/cs/remote/src/FASTER.server/ServerKVFunctions.cs +++ b/cs/remote/src/FASTER.server/ServerKVFunctions.cs @@ -43,7 +43,7 @@ public bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue, ref public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); - public bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) + public void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => functions.PostCopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); public void DeleteCompletionCallback(ref Key key, long ctx) diff --git a/cs/remote/src/FASTER.server/WebsocketServerSession.cs b/cs/remote/src/FASTER.server/WebsocketServerSession.cs index 781094289..28b1ef6b5 100644 --- a/cs/remote/src/FASTER.server/WebsocketServerSession.cs +++ b/cs/remote/src/FASTER.server/WebsocketServerSession.cs @@ -89,7 +89,7 @@ public override void CompleteRead(ref Output output, long ctx, core.Status statu hrw.Write((MessageType)(ctx >> 32), ref dcurr, (int)(dend - dcurr)); Write((int)(ctx & 0xffffffff), ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status != core.Status.NOTFOUND) + if (status.Found) serializer.Write(ref output, ref dcurr, (int)(dend - dcurr)); msgnum++; } @@ -106,7 +106,7 @@ public override void CompleteRMW(ref Output output, long ctx, Status status) hrw.Write((MessageType)(ctx >> 32), ref dcurr, (int)(dend - dcurr)); Write((int)(ctx & 0xffffffff), ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status == Status.OK || status == Status.NOTFOUND) + if (status.CompletedSuccessfully) serializer.Write(ref output, ref dcurr, (int)(dend - dcurr)); msgnum++; @@ -126,7 +126,7 @@ private bool TryReadMessages(out int offset) return true; } - private unsafe void CreateSendPacketHeader(ref byte* d, int payloadLen) + private static unsafe void CreateSendPacketHeader(ref byte* d, int payloadLen) { if (payloadLen < 126) { @@ -384,9 +384,9 @@ private unsafe bool ProcessBatch(byte* buf, int length, int offset) hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status == core.Status.PENDING) + if (status.Pending) Write(pendingSeqNo++, ref dcurr, (int)(dend - dcurr)); - else if (status == core.Status.OK) + else if (status.Found) serializer.SkipOutput(ref dcurr); break; @@ -403,7 +403,7 @@ private unsafe bool ProcessBatch(byte* buf, int length, int offset) hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); - if (status == Status.PENDING) + if (status.Pending) Write(pendingSeqNo++, ref dcurr, (int)(dend - dcurr)); if (subscribeKVBroker != null) @@ -439,7 +439,7 @@ private unsafe bool ProcessBatch(byte* buf, int length, int offset) ref Input input = ref serializer.ReadInputByRef(ref src); int sid = subscribeKVBroker.Subscribe(ref keyStart, ref inputStart, this); - status = Status.PENDING; + status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); @@ -461,7 +461,7 @@ private unsafe bool ProcessBatch(byte* buf, int length, int offset) input = ref serializer.ReadInputByRef(ref src); sid = subscribeKVBroker.PSubscribe(ref keyStart, ref inputStart, this); - status = Status.PENDING; + status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); @@ -482,7 +482,7 @@ private unsafe bool ProcessBatch(byte* buf, int length, int offset) ref Value val = ref serializer.ReadValueByRef(ref src); int valueLength = (int)(src - valPtr); - status = Status.OK; + status = Status.CreateFound(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); @@ -500,7 +500,7 @@ private unsafe bool ProcessBatch(byte* buf, int length, int offset) serializer.ReadKeyByRef(ref src); sid = subscribeBroker.Subscribe(ref keyStart, this); - status = Status.PENDING; + status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); Write(sid, ref dcurr, (int)(dend - dcurr)); @@ -516,7 +516,7 @@ private unsafe bool ProcessBatch(byte* buf, int length, int offset) serializer.ReadKeyByRef(ref src); sid = subscribeBroker.PSubscribe(ref keyStart, this); - status = Status.PENDING; + status = Status.CreatePending(); hrw.Write(message, ref dcurr, (int)(dend - dcurr)); Write(ref status, ref dcurr, (int)(dend - dcurr)); Write(sid, ref dcurr, (int)(dend - dcurr)); @@ -585,11 +585,11 @@ private unsafe void Publish(ref byte* keyPtr, int keyLength, ref byte* valPtr, r else outputDcurr = dcurr + 6; - var status = Status.OK; + Status status = Status.CreateFound(); if (valPtr == null) status = session.Read(ref key, ref serializer.ReadInputByRef(ref inputPtr), ref serializer.AsRefOutput(outputDcurr, (int)(dend - dcurr)), ctx, 0); - if (status != Status.PENDING) + if (!status.Pending) { // Write six bytes (message | status | sid) hrw.Write(message, ref dcurr, (int)(dend - dcurr)); @@ -602,7 +602,7 @@ private unsafe void Publish(ref byte* keyPtr, int keyLength, ref byte* valPtr, r ref Value value = ref serializer.ReadValueByRef(ref valPtr); serializer.Write(ref value, ref dcurr, (int)(dend - dcurr)); } - else if (status == Status.OK) + else if (status.Found) serializer.SkipOutput(ref dcurr); } else @@ -622,7 +622,7 @@ private unsafe void Publish(ref byte* keyPtr, int keyLength, ref byte* valPtr, r [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool WriteOpSeqId(ref int o, ref byte* dst, int length) + private static unsafe bool WriteOpSeqId(ref int o, ref byte* dst, int length) { if (length < sizeof(int)) return false; *(int*)dst = o; @@ -632,15 +632,15 @@ private unsafe bool WriteOpSeqId(ref int o, ref byte* dst, int length) [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool Write(ref Status s, ref byte* dst, int length) + private static unsafe bool Write(ref Status s, ref byte* dst, int length) { if (length < 1) return false; - *dst++ = (byte)s; + *dst++ = s.Value; return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool Write(int seqNo, ref byte* dst, int length) + private static unsafe bool Write(int seqNo, ref byte* dst, int length) { if (length < sizeof(int)) return false; *(int*)dst = seqNo; diff --git a/cs/samples/AzureBackedStore/Functions.cs b/cs/samples/AzureBackedStore/Functions.cs index 9df21ba37..e55d11f6f 100644 --- a/cs/samples/AzureBackedStore/Functions.cs +++ b/cs/samples/AzureBackedStore/Functions.cs @@ -10,7 +10,7 @@ public class Functions : SimpleFunctions { public override void ReadCompletionCallback(ref long key, ref string input, ref string output, string ctx, Status status, RecordMetadata recordMetadata) { - if (status == Status.OK && output == ctx) + if (status.Found && output == ctx) Console.WriteLine("Success!"); else Console.WriteLine("Error!"); diff --git a/cs/samples/AzureBackedStore/Program.cs b/cs/samples/AzureBackedStore/Program.cs index 9d2f2c9ee..2b7370891 100644 --- a/cs/samples/AzureBackedStore/Program.cs +++ b/cs/samples/AzureBackedStore/Program.cs @@ -52,9 +52,9 @@ static void Main() var status = s.Read(ref key, ref output, context); - if (status != Status.PENDING) + if (!status.Pending) { - if (status == Status.OK && output == context) + if (status.Found && output == context) Console.WriteLine("Success!"); else Console.WriteLine("Error!"); @@ -94,9 +94,9 @@ static void Main() var status = s.Read(ref key, ref output, context); - if (status != Status.PENDING) + if (!status.Pending) { - if (status == Status.OK && output == context) + if (status.Found && output == context) Console.WriteLine("Success!"); else Console.WriteLine("Error!"); diff --git a/cs/samples/CacheStore/Program.cs b/cs/samples/CacheStore/Program.cs index 0bdc404d0..2e6398bf7 100644 --- a/cs/samples/CacheStore/Program.cs +++ b/cs/samples/CacheStore/Program.cs @@ -105,7 +105,7 @@ private static void PopulateStore(FasterKV store) using var s = store.For(new CacheFunctions()).NewSession(); Console.WriteLine("Writing keys from 0 to {0} to FASTER", numKeys); - Stopwatch sw = new Stopwatch(); + Stopwatch sw = new(); sw.Start(); for (int i = 0; i < numKeys; i++) { @@ -134,7 +134,7 @@ private static void RandomReadWorkload(FasterKV store, int int statusPending = 0; var output = default(CacheValue); - Stopwatch sw = new Stopwatch(); + Stopwatch sw = new(); sw.Start(); for (int i = 0; i < max; i++) @@ -144,20 +144,20 @@ private static void RandomReadWorkload(FasterKV store, int var key = new CacheKey(k); var status = s.Read(ref key, ref output); - switch (status) + if (status.Pending) { - case Status.PENDING: - statusPending++; - if (statusPending % 100 == 0) - s.CompletePending(false); - break; - case Status.OK: - if (output.value != key.key) - throw new Exception("Read error!"); - break; - default: - throw new Exception("Error!"); + statusPending++; + if (statusPending % 100 == 0) + s.CompletePending(false); + break; } + else if (status.Found) + { + if (output.value != key.key) + throw new Exception("Read error!"); + } + else + throw new Exception("Error!"); } s.CompletePending(true); sw.Stop(); @@ -186,22 +186,22 @@ private static void InteractiveReadWorkload(FasterKV store context.ticks = Stopwatch.GetTimestamp(); var status = s.Read(ref key, ref output, context); - switch (status) + if (status.Pending) + { + s.CompletePending(true); + } + else if (status.Found) + { + long ticks = Stopwatch.GetTimestamp(); + if (output.value != key.key) + Console.WriteLine("Sync: Incorrect value {0} found, latency = {1}ms", output.value, 1000 * (ticks - context.ticks) / (double)Stopwatch.Frequency); + else + Console.WriteLine("Sync: Correct value {0} found, latency = {1}ms", output.value, 1000 * (ticks - context.ticks) / (double)Stopwatch.Frequency); + } + else { - case Status.PENDING: - s.CompletePending(true); - break; - case Status.OK: - long ticks = Stopwatch.GetTimestamp(); - if (output.value != key.key) - Console.WriteLine("Sync: Incorrect value {0} found, latency = {1}ms", output.value, 1000*(ticks - context.ticks)/(double)Stopwatch.Frequency); - else - Console.WriteLine("Sync: Correct value {0} found, latency = {1}ms", output.value, 1000 * (ticks - context.ticks) / (double)Stopwatch.Frequency); - break; - default: - ticks = Stopwatch.GetTimestamp() - context.ticks; - Console.WriteLine("Sync: Value not found, latency = {0}ms", new TimeSpan(ticks).TotalMilliseconds); - break; + long ticks = Stopwatch.GetTimestamp() - context.ticks; + Console.WriteLine("Sync: Value not found, latency = {0}ms", new TimeSpan(ticks).TotalMilliseconds); } } } diff --git a/cs/samples/CacheStore/Types.cs b/cs/samples/CacheStore/Types.cs index 2729b6511..322883e95 100644 --- a/cs/samples/CacheStore/Types.cs +++ b/cs/samples/CacheStore/Types.cs @@ -93,7 +93,7 @@ public override void ReadCompletionCallback(ref CacheKey key, ref CacheValue inp { long ticks = Stopwatch.GetTimestamp() - ctx.ticks; - if (status == Status.NOTFOUND) + if (!status.Found) Console.WriteLine("Async: Value not found, latency = {0}ms", 1000 * (ticks - ctx.ticks) / (double)Stopwatch.Frequency); if (output.value != key.key) diff --git a/cs/samples/HelloWorld/Program.cs b/cs/samples/HelloWorld/Program.cs index 5c630aae7..d82fd4e0e 100644 --- a/cs/samples/HelloWorld/Program.cs +++ b/cs/samples/HelloWorld/Program.cs @@ -47,7 +47,7 @@ static void InMemorySample() // Reads are served back from memory and return synchronously var status = session.Read(ref key, ref output); - if (status == Status.OK && output == value) + if (status.Found && output == value) Console.WriteLine("(1) Success!"); else Console.WriteLine("(1) Error!"); @@ -56,10 +56,10 @@ static void InMemorySample() session.Delete(ref key); status = session.Read(ref key, ref output); - if (status == Status.NOTFOUND) - Console.WriteLine("(2) Success!"); - else + if (status.Found) Console.WriteLine("(2) Error!"); + else + Console.WriteLine("(2) Success!"); // (4) Perform two read-modify-writes (summation), verify result key = 2; @@ -70,7 +70,7 @@ static void InMemorySample() status = session.Read(ref key, ref output); - if (status == Status.OK && output == input1 + input2) + if (status.Found && output == input1 + input2) Console.WriteLine("(3) Success!"); else Console.WriteLine("(3) Error!"); @@ -89,7 +89,7 @@ static void InMemorySample() // Read, result should be input1 (first TryAdd) var status3 = session.Read(ref key, ref output); - if (status == Status.NOTFOUND && status2 == Status.OK && status3 == Status.OK && output == input1) + if (!status.Found && status2.Found && status3.Found && output == input1) Console.WriteLine("(4) Success!"); else Console.WriteLine("(4) Error!"); @@ -127,7 +127,7 @@ static void DiskSample() // Take checkpoint so data is persisted for recovery Console.WriteLine("Taking full checkpoint"); store.TryInitiateFullCheckpoint(out _, CheckpointType.Snapshot); - store.CompleteCheckpointAsync().GetAwaiter().GetResult(); + store.CompleteCheckpointAsync().AsTask().GetAwaiter().GetResult(); } else { @@ -136,7 +136,7 @@ static void DiskSample() // Reads are served back from memory and return synchronously var status = session.Read(ref key, ref output); - if (status == Status.OK && output == value) + if (status.Found && output == value) Console.WriteLine("(1) Success!"); else Console.WriteLine("(1) Error!"); @@ -147,12 +147,12 @@ static void DiskSample() // Reads from disk will return PENDING status, result available via either asynchronous IFunctions callback // or on this thread via CompletePendingWithOutputs, shown below status = session.Read(ref key, ref output); - if (status == Status.PENDING) + if (status.Pending) { session.CompletePendingWithOutputs(out var iter, true); while (iter.Next()) { - if (iter.Current.Status == Status.OK && iter.Current.Output == value) + if (iter.Current.Status.Found && iter.Current.Output == value) Console.WriteLine("(2) Success!"); else Console.WriteLine("(2) Error!"); @@ -166,10 +166,10 @@ static void DiskSample() session.Delete(ref key); status = session.Read(ref key, ref output); - if (status == Status.NOTFOUND) - Console.WriteLine("(3) Success!"); - else + if (status.Found) Console.WriteLine("(3) Error!"); + else + Console.WriteLine("(3) Success!"); // (4) Perform two read-modify-writes (summation), verify result key = 2; @@ -180,7 +180,7 @@ static void DiskSample() status = session.Read(ref key, ref output); - if (status == Status.OK && output == input1 + input2) + if (status.Found && output == input1 + input2) Console.WriteLine("(4) Success!"); else Console.WriteLine("(4) Error!"); @@ -199,7 +199,7 @@ static void DiskSample() // Read, result should be input1 (first TryAdd) var status3 = session.Read(ref key, ref output); - if (status == Status.NOTFOUND && status2 == Status.OK && status3 == Status.OK && output == input1) + if (!status.Found && status2.Found && status3.Found && output == input1) Console.WriteLine("(5) Success!"); else Console.WriteLine("(5) Error!"); diff --git a/cs/samples/MemOnlyCache/Program.cs b/cs/samples/MemOnlyCache/Program.cs index 24dc8f2bc..259247f93 100644 --- a/cs/samples/MemOnlyCache/Program.cs +++ b/cs/samples/MemOnlyCache/Program.cs @@ -104,7 +104,7 @@ private static void PopulateStore(int count) { using var s = h.For(new CacheFunctions(sizeTracker)).NewSession(); - Random r = new Random(0); + Random r = new(0); Console.WriteLine("Writing random keys to fill cache"); for (int i = 0; i < count; i++) @@ -127,7 +127,7 @@ private static void ContinuousRandomWorkload() for (int i = 0; i < kNumThreads; i++) threads[i].Start(); - Stopwatch sw = new Stopwatch(); + Stopwatch sw = new(); sw.Start(); var _lastReads = totalReads; var _lastTime = sw.ElapsedMilliseconds; @@ -205,24 +205,23 @@ private static void RandomWorkload(int threadid) { var status = session.Read(ref key, ref output); - switch (status) + if (!status.Found) { - case Status.NOTFOUND: - localStatusNotFound++; - if (UpsertOnCacheMiss) - { - var value = new CacheValue(1 + rnd.Next(MaxValueSize - 1), (byte)key.key); - session.Upsert(ref key, ref value); - } - break; - case Status.OK: - localStatusFound++; - if (output.value[0] != (byte)key.key) - throw new Exception("Read error!"); - break; - default: - throw new Exception("Error!"); + localStatusNotFound++; + if (UpsertOnCacheMiss) + { + var value = new CacheValue(1 + rnd.Next(MaxValueSize - 1), (byte)key.key); + session.Upsert(ref key, ref value); + } } + else if (status.Found) + { + localStatusFound++; + if (output.value[0] != (byte)key.key) + throw new Exception("Read error!"); + } + else + throw new Exception("Error!"); } i++; } diff --git a/cs/samples/ReadAddress/VersionedReadApp.cs b/cs/samples/ReadAddress/VersionedReadApp.cs index fbabec403..9799d388b 100644 --- a/cs/samples/ReadAddress/VersionedReadApp.cs +++ b/cs/samples/ReadAddress/VersionedReadApp.cs @@ -157,7 +157,7 @@ private static void ScanStore(FasterKV store, int keyValue) var status = session.Read(ref key, ref input, ref output, ref recordMetadata, ReadFlags.SkipCopyReads, serialNo: maxLap + 1); // This will wait for each retrieved record; not recommended for performance-critical code or when retrieving multiple records unless necessary. - if (status == Status.PENDING) + if (status.Pending) { session.CompletePendingWithOutputs(out var completedOutputs, wait: true); using (completedOutputs) @@ -195,7 +195,7 @@ private static async Task ScanStoreAsync(FasterKV store, int keyValu private static bool ProcessRecord(FasterKV store, Status status, RecordInfo recordInfo, int lap, ref Value output) { - Debug.Assert((status == Status.NOTFOUND) == recordInfo.Tombstone); + Debug.Assert(status.Found == !recordInfo.Tombstone); Debug.Assert((lap == deleteLap) == recordInfo.Tombstone); var value = recordInfo.Tombstone ? "" : output.value.ToString(); Console.WriteLine($" {value}; PrevAddress: {recordInfo.PreviousAddress}"); diff --git a/cs/samples/SecondaryReaderStore/Program.cs b/cs/samples/SecondaryReaderStore/Program.cs index 59e3f939f..cc4d9531b 100644 --- a/cs/samples/SecondaryReaderStore/Program.cs +++ b/cs/samples/SecondaryReaderStore/Program.cs @@ -90,7 +90,7 @@ static void SecondaryReader() while (true) { var status = s1.Read(ref key, ref output); - if (status == Status.NOTFOUND) + if (!status.Found) { Console.WriteLine($"Key {key} not found at secondary; performing recovery to catch up"); Thread.Sleep(500); diff --git a/cs/samples/StoreAsyncApi/Program.cs b/cs/samples/StoreAsyncApi/Program.cs index bd8f49ae8..5068f45df 100644 --- a/cs/samples/StoreAsyncApi/Program.cs +++ b/cs/samples/StoreAsyncApi/Program.cs @@ -60,7 +60,7 @@ static void Main() static async Task AsyncOperator(int id) { using var session = faster.For(new CacheFunctions()).NewSession(id.ToString()); - Random rand = new Random(id); + Random rand = new(id); bool batched = true; // whether we batch upserts on session bool asyncUpsert = false; // whether we use sync or async upsert calls @@ -87,7 +87,7 @@ static async Task AsyncOperator(int id) if (asyncUpsert) { var r = await session.UpsertAsync(ref key, ref value, context, seqNo++); - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); } else @@ -130,7 +130,7 @@ static async Task AsyncOperator(int id) for (int i = 0; i < batchSize; i++) { var r = await taskBatch[i]; - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); } } @@ -147,7 +147,7 @@ static void ReportThread() long lastTime = 0; long lastValue = numOps; - Stopwatch sw = new Stopwatch(); + Stopwatch sw = new(); sw.Start(); while (true) diff --git a/cs/samples/StoreAsyncApi/Types.cs b/cs/samples/StoreAsyncApi/Types.cs index 2510a49f0..e0243572e 100644 --- a/cs/samples/StoreAsyncApi/Types.cs +++ b/cs/samples/StoreAsyncApi/Types.cs @@ -104,7 +104,7 @@ public override void ReadCompletionCallback(ref CacheKey key, ref CacheInput inp { long ticks = DateTime.Now.Ticks - ctx.ticks; - if (status == Status.NOTFOUND) + if (!status.Found) Console.WriteLine("Async: Value not found, latency = {0}ms", new TimeSpan(ticks).TotalMilliseconds); if (output.value.value != key.key) diff --git a/cs/samples/StoreCheckpointRecover/Program.cs b/cs/samples/StoreCheckpointRecover/Program.cs index ff3c94268..80281dcd1 100644 --- a/cs/samples/StoreCheckpointRecover/Program.cs +++ b/cs/samples/StoreCheckpointRecover/Program.cs @@ -51,15 +51,14 @@ static void Main() MyOutput g1 = default; var status = s.Read(ref key, ref g1); - if (status == Status.OK && g1.value.value == key.key) + if (status.Found && g1.value.value == key.key) Console.WriteLine("Success!"); else Console.WriteLine("Error!"); } // Take index + fold-over checkpoint of FASTER, wait to complete - store.TakeFullCheckpointAsync(CheckpointType.FoldOver) - .GetAwaiter().GetResult(); + store.TakeFullCheckpointAsync(CheckpointType.FoldOver).AsTask().GetAwaiter().GetResult(); // Dispose store instance store.Dispose(); @@ -89,7 +88,7 @@ static void Main() MyOutput g1 = default; var status = s.Read(ref key, ref g1); - if (status == Status.OK && g1.value.value == key.key) + if (status.Found && g1.value.value == key.key) Console.WriteLine("Success!"); else Console.WriteLine("Error!"); diff --git a/cs/samples/StoreCustomTypes/Program.cs b/cs/samples/StoreCustomTypes/Program.cs index f8d8dbce2..dcd6802d8 100644 --- a/cs/samples/StoreCustomTypes/Program.cs +++ b/cs/samples/StoreCustomTypes/Program.cs @@ -53,19 +53,19 @@ static void Main() var key = new MyKey { key = 23 }; var input = default(MyInput); - MyOutput g1 = new MyOutput(); + MyOutput g1 = new(); var status = s.Read(ref key, ref input, ref g1, context, 0); - if (status == Status.OK && g1.value.value == key.key) + if (status.Found && g1.value.value == key.key) Console.WriteLine("Success!"); else Console.WriteLine("Error!"); - MyOutput g2 = new MyOutput(); + MyOutput g2 = new(); key = new MyKey { key = 46 }; status = s.Read(ref key, ref input, ref g2, context, 0); - if (status == Status.OK && g2.value.value == key.key) + if (status.Found && g2.value.value == key.key) Console.WriteLine("Success!"); else Console.WriteLine("Error!"); @@ -78,7 +78,7 @@ static void Main() status = s.Read(ref key, ref input, ref g2, context, 0); // We will receive the result via ReadCompletionCallback in Functions - if (status != Status.PENDING) + if (!status.Pending) Console.WriteLine("Error!"); // End session when done diff --git a/cs/samples/StoreDiskReadBenchmark/Program.cs b/cs/samples/StoreDiskReadBenchmark/Program.cs index bde4b67d2..6f9684c3e 100644 --- a/cs/samples/StoreDiskReadBenchmark/Program.cs +++ b/cs/samples/StoreDiskReadBenchmark/Program.cs @@ -118,7 +118,7 @@ static async Task AsyncUpsertOperator(int id) static async Task AsyncReadOperator(int id) { using var session = faster.For(new MyFuncs()).NewSession(id.ToString() + "read"); - Random rand = new Random(id); + Random rand = new(id); await Task.Yield(); @@ -141,8 +141,8 @@ static async Task AsyncReadOperator(int id) } else { - var result = (await session.ReadAsync(ref key, ref input)).Complete(); - if (result.Item1 != Status.OK || result.Item2.value.vfield1 != key.key) + var (status, output) = (await session.ReadAsync(ref key, ref input)).Complete(); + if (!status.Found || output.value.vfield1 != key.key) { if (!simultaneousReadWrite) throw new Exception("Wrong value found"); @@ -151,11 +151,11 @@ static async Task AsyncReadOperator(int id) } else { - Output output = new Output(); + Output output = new(); var result = session.Read(ref key, ref input, ref output, Empty.Default, 0); if (readBatching) { - if (result != Status.PENDING) + if (!result.Pending) { if (output.value.vfield1 != key.key) { @@ -166,7 +166,7 @@ static async Task AsyncReadOperator(int id) } else { - if (result == Status.PENDING) + if (result.Pending) { session.CompletePending(true); } @@ -187,11 +187,11 @@ static async Task AsyncReadOperator(int id) { for (int j = 0; j < readBatchSize; j++) { - var result = (await tasks[j].Item2).Complete(); - if (result.Item1 != Status.OK || result.Item2.value.vfield1 != tasks[j].Item1) + var (status, output) = (await tasks[j].Item2).Complete(); + if (!status.Found || output.value.vfield1 != tasks[j].Item1) { if (!simultaneousReadWrite) - throw new Exception($"Wrong value found. Found: {result.Item2.value.vfield1}, Expected: {tasks[j].Item1}"); + throw new Exception($"Wrong value found. Found: {output.value.vfield1}, Expected: {tasks[j].Item1}"); } } } @@ -218,7 +218,7 @@ static void ReportThread() long lastTime = 0; long lastValue = numOps; - Stopwatch sw = new Stopwatch(); + Stopwatch sw = new(); sw.Start(); while (true) diff --git a/cs/samples/StoreDiskReadBenchmark/Types.cs b/cs/samples/StoreDiskReadBenchmark/Types.cs index 88725e219..99676c4c6 100644 --- a/cs/samples/StoreDiskReadBenchmark/Types.cs +++ b/cs/samples/StoreDiskReadBenchmark/Types.cs @@ -79,7 +79,7 @@ public override bool InPlaceUpdater(ref Key key, ref Input input, ref Value valu // Completion callbacks public override void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Empty ctx, Status status, RecordMetadata recordMetadata) { - if (status != Status.OK || output.value.vfield1 != key.key) + if (!status.Found || output.value.vfield1 != key.key) { if (!Program.simultaneousReadWrite) throw new Exception("Wrong value found"); diff --git a/cs/samples/StoreLogCompaction/Types.cs b/cs/samples/StoreLogCompaction/Types.cs index 815627f0d..1cffad4eb 100644 --- a/cs/samples/StoreLogCompaction/Types.cs +++ b/cs/samples/StoreLogCompaction/Types.cs @@ -110,7 +110,7 @@ public override void ReadCompletionCallback(ref CacheKey key, ref CacheInput inp { long ticks = DateTime.Now.Ticks - ctx.ticks; - if (status == Status.NOTFOUND) + if (!status.Found) Console.WriteLine("Async: Value not found, latency = {0}ms", new TimeSpan(ticks).TotalMilliseconds); if (output.value.value != key.key) diff --git a/cs/samples/StoreVarLenTypes/AsciiSumSample.cs b/cs/samples/StoreVarLenTypes/AsciiSumSample.cs index 09d9ec016..bfe46a8af 100644 --- a/cs/samples/StoreVarLenTypes/AsciiSumSample.cs +++ b/cs/samples/StoreVarLenTypes/AsciiSumSample.cs @@ -59,7 +59,7 @@ public static void Run() store.Log.FlushAndEvict(true); // Flush and evict all records to disk var _status = s.RMW(_key, _input); // CopyUpdater to 270 (due to immutable source value on disk) - if (_status != Status.PENDING) + if (_status.Pending) { Console.WriteLine("Error!"); return; @@ -79,7 +79,7 @@ public static void Run() // Read does not go pending, and the output should fit in the provided space (10 bytes) // Hence, no Memory will be allocated by FASTER - if (status != Status.OK || !outputWrapper.IsSpanByte) + if (!status.Found || !outputWrapper.IsSpanByte) { Console.WriteLine("Error!"); return; diff --git a/cs/samples/StoreVarLenTypes/CustomMemoryFunctions.cs b/cs/samples/StoreVarLenTypes/CustomMemoryFunctions.cs index 04d72118a..6dfb1b715 100644 --- a/cs/samples/StoreVarLenTypes/CustomMemoryFunctions.cs +++ b/cs/samples/StoreVarLenTypes/CustomMemoryFunctions.cs @@ -21,7 +21,7 @@ public CustomMemoryFunctions(MemoryPool memoryPool = default) /// public override void ReadCompletionCallback(ref ReadOnlyMemory key, ref Memory input, ref (IMemoryOwner, int) output, T ctx, Status status, RecordMetadata recordMetadata) { - if (status != Status.OK) + if (!status.Found) { Console.WriteLine("Error!"); return; diff --git a/cs/samples/StoreVarLenTypes/CustomSpanByteFunctions.cs b/cs/samples/StoreVarLenTypes/CustomSpanByteFunctions.cs index 0914df2d6..693575ef6 100644 --- a/cs/samples/StoreVarLenTypes/CustomSpanByteFunctions.cs +++ b/cs/samples/StoreVarLenTypes/CustomSpanByteFunctions.cs @@ -18,7 +18,7 @@ public CustomSpanByteFunctions() : base() { } // Read completion callback public override void ReadCompletionCallback(ref SpanByte key, ref SpanByte input, ref byte[] output, byte ctx, Status status, RecordMetadata recordMetadata) { - if (status != Status.OK) + if (!status.Found) { Console.WriteLine("Error!"); return; diff --git a/cs/samples/StoreVarLenTypes/MemoryByteSample.cs b/cs/samples/StoreVarLenTypes/MemoryByteSample.cs index 0689e5a25..38f4b8b6a 100644 --- a/cs/samples/StoreVarLenTypes/MemoryByteSample.cs +++ b/cs/samples/StoreVarLenTypes/MemoryByteSample.cs @@ -26,7 +26,7 @@ public static void Run() // and byte as Context (to verify read result in callback) var s = store.For(new CustomMemoryFunctions()).NewSession>(); - Random r = new Random(100); + Random r = new(100); // Allocate space for key and value operations var keyMem = new Memory(new byte[1000]); @@ -61,11 +61,11 @@ public static void Run() var expectedValue = valueMem.Slice(0, valLen); expectedValue.Span.Fill((byte)valLen); - if (status == Status.PENDING) + if (status.Pending) s.CompletePending(true); else { - if ((status != Status.OK) || (!output.Item1.Memory.Slice(0, output.Item2).Span.SequenceEqual(expectedValue.Span))) + if (!status.Found || (!output.Item1.Memory.Slice(0, output.Item2).Span.SequenceEqual(expectedValue.Span))) { output.Item1.Dispose(); success = false; diff --git a/cs/samples/StoreVarLenTypes/MemoryIntSample.cs b/cs/samples/StoreVarLenTypes/MemoryIntSample.cs index 0575a4ae6..cbe40ac62 100644 --- a/cs/samples/StoreVarLenTypes/MemoryIntSample.cs +++ b/cs/samples/StoreVarLenTypes/MemoryIntSample.cs @@ -61,11 +61,11 @@ public static void Run() var expectedValue = valueMem.Slice(0, valLen); expectedValue.Span.Fill(valLen); - if (status == Status.PENDING) + if (status.Pending) s.CompletePending(true); else { - if ((status != Status.OK) || (!output.Item1.Memory.Slice(0, output.Item2).Span.SequenceEqual(expectedValue.Span))) + if (!status.Found || (!output.Item1.Memory.Slice(0, output.Item2).Span.SequenceEqual(expectedValue.Span))) { output.Item1.Dispose(); success = false; diff --git a/cs/samples/StoreVarLenTypes/SpanByteSample.cs b/cs/samples/StoreVarLenTypes/SpanByteSample.cs index 45a7a8640..623cd6b96 100644 --- a/cs/samples/StoreVarLenTypes/SpanByteSample.cs +++ b/cs/samples/StoreVarLenTypes/SpanByteSample.cs @@ -32,7 +32,7 @@ public static void Run() // Create session var s = store.For(new CustomSpanByteFunctions()).NewSession(); - Random r = new Random(100); + Random r = new(100); // Here, stackalloc implies fixed, so it can be used directly with SpanByte // For Span over heap data (e.g., strings or byte[]), make sure to use @@ -91,13 +91,13 @@ public static void Run() var expectedValue = valueMem.Slice(0, valLen); expectedValue.Fill((byte)valLen); - if (status == Status.PENDING) + if (status.Pending) { s.CompletePending(true); } else { - if ((status != Status.OK) || (!output.SequenceEqual(expectedValue.ToArray()))) + if (!status.Found || (!output.SequenceEqual(expectedValue.ToArray()))) { success = false; break; diff --git a/cs/src/core/Async/DeleteAsync.cs b/cs/src/core/Async/DeleteAsync.cs index da151bae9..84078cfac 100644 --- a/cs/src/core/Async/DeleteAsync.cs +++ b/cs/src/core/Async/DeleteAsync.cs @@ -61,7 +61,7 @@ internal DeleteAsyncResult(Status status) internal DeleteAsyncResult(FasterKV fasterKV, IFasterSession fasterSession, FasterExecutionContext currentCtx, PendingContext pendingContext, ExceptionDispatchInfo exceptionDispatchInfo) { - this.Status = Status.PENDING; + this.Status = new(StatusCode.Pending); updateAsyncInternal = new UpdateAsyncInternal, DeleteAsyncResult>( fasterKV, fasterSession, currentCtx, pendingContext, exceptionDispatchInfo, new DeleteAsyncOperation()); } @@ -69,13 +69,13 @@ internal DeleteAsyncResult(FasterKV fasterKV, IFasterSessionComplete the Delete operation, issuing additional allocation asynchronously if needed. It is usually preferable to use Complete() instead of this. /// ValueTask for Delete result. User needs to await again if result status is Status.PENDING. public ValueTask> CompleteAsync(CancellationToken token = default) - => this.Status != Status.PENDING - ? new ValueTask>(new DeleteAsyncResult(this.Status)) - : updateAsyncInternal.CompleteAsync(token); + => this.Status.Pending + ? updateAsyncInternal.CompleteAsync(token) + : new ValueTask>(new DeleteAsyncResult(this.Status)); /// Complete the Delete operation, issuing additional I/O synchronously if needed. /// Status of Delete operation - public Status Complete() => this.Status != Status.PENDING ? this.Status : updateAsyncInternal.Complete().Status; + public Status Complete() => this.Status.Pending ? updateAsyncInternal.Complete().Status : this.Status; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -102,8 +102,9 @@ internal ValueTask> DeleteAsync>(new DeleteAsyncResult((Status)internalStatus)); + if (OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) + return new ValueTask>(new DeleteAsyncResult(new(internalStatus))); + Debug.Assert(internalStatus == OperationStatus.ALLOCATE_FAILED); } finally diff --git a/cs/src/core/Async/RMWAsync.cs b/cs/src/core/Async/RMWAsync.cs index 3a0f57d82..5bf408429 100644 --- a/cs/src/core/Async/RMWAsync.cs +++ b/cs/src/core/Async/RMWAsync.cs @@ -32,7 +32,7 @@ public Status DoFastOperation(FasterKV fasterKV, ref PendingContext< pendingContext.serialNum, asyncOp, out flushEvent, out newDiskRequest); output = pendingContext.output; - if (status == Status.PENDING && !newDiskRequest.IsDefault()) + if (status.Pending && !newDiskRequest.IsDefault()) { flushEvent = default; this.diskRequest = newDiskRequest; @@ -53,10 +53,10 @@ public bool CompletePendingIO(IFasterSession // CompletePending() may encounter OperationStatus.ALLOCATE_FAILED; if so, we don't have a more current flushEvent to pass back. fasterSession.CompletePendingWithOutputs(out var completedOutputs, wait: true, spinWaitForCommit: false); - var status = completedOutputs.Next() ? completedOutputs.Current.Status : Status.ERROR; + var status = completedOutputs.Next() ? completedOutputs.Current.Status : new(StatusCode.Error); completedOutputs.Dispose(); this.diskRequest = default; - return status != Status.PENDING; + return !status.Pending; } /// @@ -81,7 +81,7 @@ public struct RmwAsyncResult /// Current status of the RMW operation public Status Status { get; } - /// Output of the RMW operation if current status is not + /// Output of the RMW operation if current status is not pending public TOutput Output { get; } /// Metadata of the updated record @@ -99,7 +99,7 @@ internal RmwAsyncResult(FasterKV fasterKV, IFasterSession currentCtx, PendingContext pendingContext, AsyncIOContext diskRequest, ExceptionDispatchInfo exceptionDispatchInfo) { - Status = Status.PENDING; + Status = new(StatusCode.Pending); this.Output = default; this.RecordMetadata = default; updateAsyncInternal = new UpdateAsyncInternal, RmwAsyncResult>( @@ -107,11 +107,11 @@ internal RmwAsyncResult(FasterKV fasterKV, IFasterSessionComplete the RMW operation, issuing additional (rare) I/O asynchronously if needed. It is usually preferable to use Complete() instead of this. - /// ValueTask for RMW result. User needs to await again if result status is . + /// ValueTask for RMW result. User needs to await again if result status is pending. public ValueTask> CompleteAsync(CancellationToken token = default) - => this.Status != Status.PENDING - ? new ValueTask>(new RmwAsyncResult(this.Status, this.Output, this.RecordMetadata)) - : updateAsyncInternal.CompleteAsync(token); + => this.Status.Pending + ? updateAsyncInternal.CompleteAsync(token) + : new ValueTask>(new RmwAsyncResult(this.Status, this.Output, this.RecordMetadata)); /// Complete the RMW operation, issuing additional (rare) I/O synchronously if needed. /// Status of RMW operation @@ -122,7 +122,7 @@ public ValueTask> CompleteAsync(Cancella /// Status of RMW operation public (Status status, TOutput output) Complete(out RecordMetadata recordMetadata) { - if (this.Status != Status.PENDING) + if (!this.Status.Pending) { recordMetadata = this.RecordMetadata; return (this.Status, this.Output); @@ -154,7 +154,7 @@ internal ValueTask> RmwAsync>(new RmwAsyncResult(status, output, new RecordMetadata(pcontext.recordInfo, pcontext.logicalAddress))); } finally @@ -180,15 +180,15 @@ private Status CallInternalRMW(IFasterSession> SlowRmwAsync( diff --git a/cs/src/core/Async/ReadAsync.cs b/cs/src/core/Async/ReadAsync.cs index 30de194e5..11f4fab10 100644 --- a/cs/src/core/Async/ReadAsync.cs +++ b/cs/src/core/Async/ReadAsync.cs @@ -53,7 +53,7 @@ internal ReadAsyncInternal(FasterKV fasterKV, IFasterSession currentCtx, PendingContext pendingContext, AsyncIOContext diskRequest, ExceptionDispatchInfo exceptionDispatchInfo) { - status = Status.PENDING; + status = new(StatusCode.Pending); output = default; this.recordMetadata = default; readAsyncInternal = new ReadAsyncInternal(fasterKV, fasterSession, currentCtx, pendingContext, diskRequest, exceptionDispatchInfo); @@ -126,9 +126,8 @@ internal ReadAsyncResult( /// The read result, or throws an exception if error encountered. public (Status status, Output output) Complete() { - if (status != Status.PENDING) + if (!status.Pending) return (status, output); - return readAsyncInternal.Complete(); } @@ -138,7 +137,7 @@ internal ReadAsyncResult( /// The read result and the previous address in the Read key's hash chain, or throws an exception if error encountered. public (Status status, Output output) Complete(out RecordMetadata recordMetadata) { - if (status != Status.PENDING) + if (!status.Pending) { recordMetadata = this.recordMetadata; return (status, output); @@ -167,17 +166,12 @@ internal ValueTask> ReadAsync>(new ReadAsyncResult((Status)internalStatus, output, new RecordMetadata(pcontext.recordInfo, pcontext.logicalAddress))); - } - else - { - var status = HandleOperationStatus(currentCtx, currentCtx, ref pcontext, fasterSession, internalStatus, true, out diskRequest); + if (OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) + return new ValueTask>(new ReadAsyncResult(new(internalStatus), output, new RecordMetadata(pcontext.recordInfo, pcontext.logicalAddress))); - if (status != Status.PENDING) - return new ValueTask>(new ReadAsyncResult(status, output, new RecordMetadata(pcontext.recordInfo, pcontext.logicalAddress))); - } + status = HandleOperationStatus(currentCtx, currentCtx, ref pcontext, fasterSession, internalStatus, true, out diskRequest); + if (!status.Pending) + return new ValueTask>(new ReadAsyncResult(status, output, new RecordMetadata(pcontext.recordInfo, pcontext.logicalAddress))); } finally { diff --git a/cs/src/core/Async/UpdateAsync.cs b/cs/src/core/Async/UpdateAsync.cs index 9729c03ff..d0579fbb5 100644 --- a/cs/src/core/Async/UpdateAsync.cs +++ b/cs/src/core/Async/UpdateAsync.cs @@ -20,7 +20,7 @@ internal interface IUpdateAsyncOperation /// /// This creates an instance of the , for example /// - /// The status code; for this variant of intantiation, this will not be + /// The status code; for this variant of intantiation, this will not be pending /// The completed output of the operation, if any /// The record metadata from the operation (currently used by RMW only) /// @@ -157,7 +157,7 @@ private bool TryCompleteSync(bool asyncOp, out CompletionEvent flushEvent, out T { Status status = _asyncOperation.DoFastOperation(_fasterKV, ref _pendingContext, _fasterSession, _currentCtx, asyncOp, out flushEvent, out Output output); - if (status != Status.PENDING) + if (!status.Pending) { _pendingContext.Dispose(); asyncResult = _asyncOperation.CreateResult(status, output, new RecordMetadata(_pendingContext.recordInfo, _pendingContext.logicalAddress)); @@ -182,10 +182,10 @@ private bool TryCompleteSync(bool asyncOp, out CompletionEvent flushEvent, out T [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Status TranslateStatus(OperationStatus internalStatus) { - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - return (Status)internalStatus; + if (OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) + return status; Debug.Assert(internalStatus == OperationStatus.ALLOCATE_FAILED); - return Status.PENDING; + return new(StatusCode.Pending); } private static async ValueTask WaitForFlushCompletionAsync(FasterKV @this, FasterExecutionContext currentCtx, CompletionEvent flushEvent, CancellationToken token) diff --git a/cs/src/core/Async/UpsertAsync.cs b/cs/src/core/Async/UpsertAsync.cs index c590e03d0..de29cac3b 100644 --- a/cs/src/core/Async/UpsertAsync.cs +++ b/cs/src/core/Async/UpsertAsync.cs @@ -70,7 +70,7 @@ internal UpsertAsyncResult(Status status, TOutput output, RecordMetadata recordM internal UpsertAsyncResult(FasterKV fasterKV, IFasterSession fasterSession, FasterExecutionContext currentCtx, PendingContext pendingContext, ExceptionDispatchInfo exceptionDispatchInfo) { - this.Status = Status.PENDING; + this.Status = new(StatusCode.Pending); this.Output = default; this.RecordMetadata = default; updateAsyncInternal = new UpdateAsyncInternal, UpsertAsyncResult>( @@ -80,19 +80,19 @@ internal UpsertAsyncResult(FasterKV fasterKV, IFasterSessionComplete the Upsert operation, issuing additional allocation asynchronously if needed. It is usually preferable to use Complete() instead of this. /// ValueTask for Upsert result. User needs to await again if result status is Status.PENDING. public ValueTask> CompleteAsync(CancellationToken token = default) - => this.Status != Status.PENDING - ? new ValueTask>(new UpsertAsyncResult(this.Status, this.Output, this.RecordMetadata)) - : updateAsyncInternal.CompleteAsync(token); + => this.Status.Pending + ? updateAsyncInternal.CompleteAsync(token) + : new ValueTask>(new UpsertAsyncResult(this.Status, this.Output, this.RecordMetadata)); /// Complete the Upsert operation, issuing additional I/O synchronously if needed. /// Status of Upsert operation - public Status Complete() => this.Status != Status.PENDING ? this.Status : updateAsyncInternal.Complete().Status; + public Status Complete() => this.Status.Pending ? updateAsyncInternal.Complete().Status : this.Status; /// Complete the Upsert operation, issuing additional I/O synchronously if needed. /// Status and Output of Upsert operation public (Status status, TOutput output) Complete(out RecordMetadata recordMetadata) { - if (this.Status != Status.PENDING) + if (!this.Status.Pending) { recordMetadata = this.RecordMetadata; return (this.Status, this.Output); @@ -128,8 +128,8 @@ private ValueTask> UpsertAsync>(new UpsertAsyncResult((Status)internalStatus, output, new RecordMetadata(pcontext.recordInfo, pcontext.logicalAddress))); + if (OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) + return new ValueTask>(new UpsertAsyncResult(status, output, new RecordMetadata(pcontext.recordInfo, pcontext.logicalAddress))); Debug.Assert(internalStatus == OperationStatus.ALLOCATE_FAILED); } finally diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index 431874653..1deb06f0d 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -937,7 +937,7 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va => _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) + public void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => _clientSession.functions.PostCopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); #endregion CopyUpdater diff --git a/cs/src/core/ClientSession/IFasterContext.cs b/cs/src/core/ClientSession/IFasterContext.cs index 1bd132b75..4228d84e7 100644 --- a/cs/src/core/ClientSession/IFasterContext.cs +++ b/cs/src/core/ClientSession/IFasterContext.cs @@ -284,7 +284,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.UpsertAsyncResult> UpsertAsync(ref Key key, ref Value desiredValue, Context userContext = default, long serialNo = 0, CancellationToken token = default); @@ -303,7 +303,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.UpsertAsyncResult> UpsertAsync(ref Key key, ref Input input, ref Value desiredValue, Context userContext = default, long serialNo = 0, CancellationToken token = default); @@ -321,7 +321,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.UpsertAsyncResult> UpsertAsync(Key key, Value desiredValue, Context userContext = default, long serialNo = 0, CancellationToken token = default); @@ -340,7 +340,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.UpsertAsyncResult> UpsertAsync(Key key, Input input, Value desiredValue, Context userContext = default, long serialNo = 0, CancellationToken token = default); @@ -411,7 +411,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.RmwAsyncResult> RMWAsync(ref Key key, ref Input input, Context context = default, long serialNo = 0, CancellationToken token = default); @@ -428,7 +428,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.RmwAsyncResult> RMWAsync(Key key, Input input, Context context = default, long serialNo = 0, CancellationToken token = default); @@ -462,7 +462,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.DeleteAsyncResult> DeleteAsync(ref Key key, Context userContext = default, long serialNo = 0, CancellationToken token = default); @@ -478,7 +478,7 @@ ValueTask.ReadAsyncResult> ReadAtAd /// The caller must await the return value to obtain the result, then call one of /// /// result. - /// result = await result. while result.Status == + /// result = await result. while result.Status is /// /// to complete the Upsert operation. Failure to complete the operation will result in leaked allocations. ValueTask.DeleteAsyncResult> DeleteAsync(Key key, Context userContext = default, long serialNo = 0, CancellationToken token = default); diff --git a/cs/src/core/ClientSession/LockableUnsafeContext.cs b/cs/src/core/ClientSession/LockableUnsafeContext.cs index 62e6bbbc3..7762666f6 100644 --- a/cs/src/core/ClientSession/LockableUnsafeContext.cs +++ b/cs/src/core/ClientSession/LockableUnsafeContext.cs @@ -559,7 +559,7 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va => _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) + public void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => _clientSession.functions.PostCopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); #endregion CopyUpdater diff --git a/cs/src/core/ClientSession/UnsafeContext.cs b/cs/src/core/ClientSession/UnsafeContext.cs index a45aa6d93..11afad122 100644 --- a/cs/src/core/ClientSession/UnsafeContext.cs +++ b/cs/src/core/ClientSession/UnsafeContext.cs @@ -507,7 +507,7 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va => _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) + public void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => _clientSession.functions.PostCopyUpdater(ref key, ref input, ref oldValue, ref newValue, ref output, ref recordInfo, address); #endregion CopyUpdater diff --git a/cs/src/core/Compaction/FASTERCompaction.cs b/cs/src/core/Compaction/FASTERCompaction.cs index 4e70ea7dd..831b47d04 100644 --- a/cs/src/core/Compaction/FASTERCompaction.cs +++ b/cs/src/core/Compaction/FASTERCompaction.cs @@ -61,7 +61,7 @@ private long CompactLookup= iter1.NextAddress)) + if (status.Found || recordMetadata.Address >= iter1.NextAddress) break; copyStatus = fhtSession.CompactionCopyToTail(ref key, ref input, ref value, ref output, checkedAddress); @@ -161,7 +161,7 @@ private long CompactScan LogScanForValidity(ref untilAddress, scanUntil, tempKvSession); // If record is not the latest in memory - if (tempKvSession.ContainsKeyInMemory(ref iter3.GetKey(), out long tempKeyAddress) == Status.OK) + if (tempKvSession.ContainsKeyInMemory(ref iter3.GetKey(), out long tempKeyAddress).Found) { if (iter3.CurrentAddress != tempKeyAddress) continue; diff --git a/cs/src/core/Compaction/LogCompactionFunctions.cs b/cs/src/core/Compaction/LogCompactionFunctions.cs index 86c35ae66..fa4846249 100644 --- a/cs/src/core/Compaction/LogCompactionFunctions.cs +++ b/cs/src/core/Compaction/LogCompactionFunctions.cs @@ -37,8 +37,8 @@ public void PostSingleDeleter(ref Key key, ref RecordInfo recordInfo, long addre public bool ConcurrentWriter(ref Key key, ref Input input, ref Value src, ref Value dst, ref Output output, ref RecordInfo recordInfo, long address) => true; public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) { } - - public bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => true; + + public void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) { } public void DeleteCompletionCallback(ref Key key, Context ctx) { } diff --git a/cs/src/core/Index/Common/Contexts.cs b/cs/src/core/Index/Common/Contexts.cs index cce9a809c..dce0cd7a0 100644 --- a/cs/src/core/Index/Common/Contexts.cs +++ b/cs/src/core/Index/Common/Contexts.cs @@ -21,6 +21,7 @@ internal enum OperationType DELETE } + [Flags] internal enum OperationStatus { SUCCESS, @@ -31,7 +32,43 @@ internal enum OperationStatus SUCCESS_UNMARK, CPR_SHIFT_DETECTED, CPR_PENDING_DETECTED, - ALLOCATE_FAILED + ALLOCATE_FAILED, + BASIC_MASK = 0xFF, // Leave plenty of space for future expansion + + ADVANCED_MASK = 0x700, // Coordinate any changes with OperationStatusUtils.OpStatusToStatusCodeShif + CREATED_RECORD = (int)StatusCode.CreatedRecord << OperationStatusUtils.OpStatusToStatusCodeShift, + COPY_UPDATED_RECORD = (int)StatusCode.CopyUpdatedRecord << OperationStatusUtils.OpStatusToStatusCodeShift + } + + internal static class OperationStatusUtils + { + // StatusCode has this in the high nybble of the first (only) byte; put it in the low nybble of the second byte here). + // Coordinate any changes with OperationStatus.ADVANCED_MASK. + internal const int OpStatusToStatusCodeShift = 4; + + internal static OperationStatus BasicOpCode(OperationStatus status) => status & OperationStatus.BASIC_MASK; + + internal static OperationStatus AdvancedOpCode(OperationStatus status, StatusCode advancedStatusCode) => status | (OperationStatus)((int)advancedStatusCode << OpStatusToStatusCodeShift); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TryConvertToStatusCode(OperationStatus advInternalStatus, out Status statusCode) + { + var internalStatus = BasicOpCode(advInternalStatus); + if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) + { + statusCode = new(advInternalStatus); + return true; + } + statusCode = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsAppend(OperationStatus internalStatus) + { + var advInternalStatus = internalStatus & OperationStatus.ADVANCED_MASK; + return advInternalStatus == OperationStatus.CREATED_RECORD || advInternalStatus == OperationStatus.COPY_UPDATED_RECORD; + } } public partial class FasterKV : FasterBase, IFasterKV diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 3671dec4d..61db2377c 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -568,15 +568,8 @@ internal Status ContextRead(ref Key key, internalStatus = InternalRead(ref key, ref input, ref output, Constants.kInvalidAddress, ref context, ref pcontext, fasterSession, sessionCtx, serialNo); while (internalStatus == OperationStatus.RETRY_NOW); - Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - { - status = (Status)internalStatus; - } - else - { + if (!OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); - } Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); sessionCtx.serialNum = serialNo; @@ -595,12 +588,8 @@ internal Status ContextRead(ref Key key, internalStatus = InternalRead(ref key, ref input, ref output, recordMetadata.RecordInfo.PreviousAddress, ref context, ref pcontext, fasterSession, sessionCtx, serialNo); while (internalStatus == OperationStatus.RETRY_NOW); - Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - { + if (OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) recordMetadata = new(pcontext.recordInfo, pcontext.logicalAddress); - status = (Status)internalStatus; - } else { recordMetadata = default; @@ -625,15 +614,8 @@ internal Status ContextReadAtAddress(long internalStatus = InternalRead(ref key, ref input, ref output, address, ref context, ref pcontext, fasterSession, sessionCtx, serialNo); while (internalStatus == OperationStatus.RETRY_NOW); - Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - { - status = (Status)internalStatus; - } - else - { + if (!OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); - } Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); sessionCtx.serialNum = serialNo; @@ -652,15 +634,8 @@ internal Status ContextUpsert(ref Key key internalStatus = InternalUpsert(ref key, ref input, ref value, ref output, ref context, ref pcontext, fasterSession, sessionCtx, serialNo); while (internalStatus == OperationStatus.RETRY_NOW); - Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - { - status = (Status)internalStatus; - } - else - { + if (!OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); - } Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); sessionCtx.serialNum = serialNo; @@ -679,12 +654,8 @@ internal Status ContextUpsert(ref Key key internalStatus = InternalUpsert(ref key, ref input, ref value, ref output, ref context, ref pcontext, fasterSession, sessionCtx, serialNo); while (internalStatus == OperationStatus.RETRY_NOW); - Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - { + if (OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) recordMetadata = new(pcontext.recordInfo, pcontext.logicalAddress); - status = (Status)internalStatus; - } else { recordMetadata = default; @@ -714,12 +685,8 @@ internal Status ContextRMW(ref Key key, r internalStatus = InternalRMW(ref key, ref input, ref output, ref context, ref pcontext, fasterSession, sessionCtx, serialNo); while (internalStatus == OperationStatus.RETRY_NOW); - Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - { + if (OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) recordMetadata = new(pcontext.recordInfo, pcontext.logicalAddress); - status = (Status)internalStatus; - } else { recordMetadata = default; @@ -747,15 +714,8 @@ internal Status ContextDelete( internalStatus = InternalDelete(ref key, ref context, ref pcontext, fasterSession, sessionCtx, serialNo); while (internalStatus == OperationStatus.RETRY_NOW); - Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) - { - status = (Status)internalStatus; - } - else - { + if (!OperationStatusUtils.TryConvertToStatusCode(internalStatus, out Status status)) status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); - } Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); sessionCtx.serialNum = serialNo; diff --git a/cs/src/core/Index/FASTER/FASTERBase.cs b/cs/src/core/Index/FASTER/FASTERBase.cs index 4a5482bab..be3501c8b 100644 --- a/cs/src/core/Index/FASTER/FASTERBase.cs +++ b/cs/src/core/Index/FASTER/FASTERBase.cs @@ -278,7 +278,7 @@ internal Status Free() Free(1); epoch.Dispose(); overflowBucketsAllocator.Dispose(); - return Status.OK; + return new(StatusCode.OK); } private Status Free(int version) @@ -288,7 +288,7 @@ private Status Free(int version) state[version].tableRaw = null; state[version].tableAligned = null; - return Status.OK; + return new(StatusCode.OK); } /// diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index b2549ba7a..b27edf389 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -240,8 +240,12 @@ internal OperationStatus InternalRead( if (CopyReadsToTail == CopyReadsToTail.FromReadOnly && !pendingContext.SkipCopyReadsToTail) { var container = hlog.GetValueContainer(ref hlog.GetValue(physicalAddress)); - InternalTryCopyToTail(sessionCtx, ref pendingContext, ref key, ref input, ref container.Get(), ref output, logicalAddress, fasterSession, sessionCtx, WriteReason.CopyToTail); + do + status = InternalTryCopyToTail(sessionCtx, ref pendingContext, ref key, ref input, ref container.Get(), ref output, logicalAddress, fasterSession, sessionCtx, WriteReason.CopyToTail); + while (status == OperationStatus.RETRY_NOW); container.Dispose(); + if (status == OperationStatus.SUCCESS) + return OperationStatusUtils.AdvancedOpCode(OperationStatus.SUCCESS, StatusCode.CopiedRecord); } return OperationStatus.SUCCESS; } @@ -416,7 +420,7 @@ internal OperationStatus InternalUpsert( hlog.MarkPage(logicalAddress, sessionCtx.version); pendingContext.recordInfo = recordInfo; pendingContext.logicalAddress = logicalAddress; - return OperationStatus.SUCCESS; + return OperationStatusUtils.AdvancedOpCode(OperationStatus.SUCCESS, StatusCode.InPlaceUpdatedRecord); } // ConcurrentWriter failed (e.g. insufficient space). Another thread may come along to do this update in-place; Seal it to prevent that. @@ -445,9 +449,7 @@ internal OperationStatus InternalUpsert( ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); ref Value recordValue = ref hlog.GetValue(physicalAddress); if (recordInfo.IsIntermediate(out status)) - { goto LatchRelease; // Release shared latch (if acquired) - } if (!recordInfo.Tombstone) { @@ -459,7 +461,7 @@ internal OperationStatus InternalUpsert( hlog.MarkPageAtomic(logicalAddress, sessionCtx.version); pendingContext.recordInfo = recordInfo; pendingContext.logicalAddress = logicalAddress; - status = OperationStatus.SUCCESS; + status = OperationStatusUtils.AdvancedOpCode(OperationStatus.SUCCESS, StatusCode.InPlaceUpdatedRecord); goto LatchRelease; // Release shared latch (if acquired) } @@ -511,8 +513,10 @@ internal OperationStatus InternalUpsert( // Immutable region or new record status = CreateNewRecordUpsert(ref key, ref input, ref value, ref output, ref pendingContext, fasterSession, sessionCtx, bucket, slot, tag, entry, latestLogicalAddress, prevHighestReadCacheLogicalAddress, lowestReadCachePhysicalAddress, unsealPhysicalAddress); - if (status != OperationStatus.SUCCESS) + if (!OperationStatusUtils.IsAppend(status)) { + // We should never return "SUCCESS" for a new record operation: it returns NOTFOUND on success. + Debug.Assert(OperationStatusUtils.BasicOpCode(status) != OperationStatus.SUCCESS); if (unsealPhysicalAddress != Constants.kInvalidAddress) { // Operation failed, so unseal the old record. @@ -692,7 +696,7 @@ private OperationStatus CreateNewRecordUpsert( hlog.MarkPage(logicalAddress, sessionCtx.version); pendingContext.recordInfo = recordInfo; pendingContext.logicalAddress = logicalAddress; - return OperationStatus.SUCCESS; + return OperationStatusUtils.AdvancedOpCode(OperationStatus.SUCCESS, StatusCode.InPlaceUpdatedRecord); } // InPlaceUpdater failed (e.g. insufficient space). Another thread may come along to do this update in-place; Seal it to prevent that. @@ -860,7 +864,7 @@ internal OperationStatus InternalRMW( hlog.MarkPageAtomic(logicalAddress, sessionCtx.version); pendingContext.recordInfo = recordInfo; pendingContext.logicalAddress = logicalAddress; - status = OperationStatus.SUCCESS; + status = OperationStatusUtils.AdvancedOpCode(OperationStatus.SUCCESS, StatusCode.CopiedRecord); goto LatchRelease; // Release shared latch (if acquired) } @@ -922,8 +926,9 @@ internal OperationStatus InternalRMW( { status = CreateNewRecordRMW(ref key, ref input, ref output, ref pendingContext, fasterSession, sessionCtx, bucket, slot, logicalAddress, physicalAddress, tag, entry, latestLogicalAddress, prevHighestReadCacheLogicalAddress, lowestReadCachePhysicalAddress, unsealPhysicalAddress); - if (status != OperationStatus.SUCCESS) + if (!OperationStatusUtils.IsAppend(status)) { + // OperationStatus.SUCCESS is OK here; it means NeedCopyUpdate or NeedInitialUpdate returned false if (unsealPhysicalAddress != Constants.kInvalidAddress) { // Operation failed, so unseal the old record. @@ -1052,7 +1057,7 @@ private OperationStatus CreateNewRecordRMW= hlog.HeadAddress) { @@ -1085,14 +1090,14 @@ private OperationStatus CreateNewRecordRMW( Interlocked.CompareExchange(ref bucket->bucket_entries[slot], updatedEntry.word, entry.word); } - status = OperationStatus.SUCCESS; + status = OperationStatusUtils.AdvancedOpCode(OperationStatus.SUCCESS, StatusCode.InPlaceUpdatedRecord); goto LatchRelease; // Release shared latch (if acquired) } else if (logicalAddress >= hlog.HeadAddress) @@ -1471,7 +1472,7 @@ internal OperationStatus InternalDelete( recordInfo.SetTentativeAtomic(false); pendingContext.recordInfo = recordInfo; pendingContext.logicalAddress = newLogicalAddress; - status = OperationStatus.SUCCESS; + status = OperationStatusUtils.AdvancedOpCode(OperationStatus.NOTFOUND, StatusCode.CreatedRecord); goto LatchRelease; } else @@ -1696,22 +1697,22 @@ internal Status InternalContainsKeyInMemoryPending context corresponding to operation. /// Callback functions. /// - internal void InternalContinuePendingReadCopyToTail( + internal OperationStatus InternalContinuePendingReadCopyToTail( FasterExecutionContext opCtx, AsyncIOContext request, ref PendingContext pendingContext, @@ -1802,9 +1802,13 @@ internal void InternalContinuePendingReadCopyToTail @@ -1856,9 +1860,9 @@ internal OperationStatus InternalContinuePendingRMWCurrent context /// Internal context of the operation. /// Callback functions. - /// Internal status of the trial. + /// Internal status of the trial. /// When operation issued via async call /// IO request, if operation went pending /// @@ -2067,18 +2066,18 @@ internal Status HandleOperationStatus( FasterExecutionContext currentCtx, ref PendingContext pendingContext, FasterSession fasterSession, - OperationStatus status, bool asyncOp, out AsyncIOContext request) + OperationStatus operationStatus, bool asyncOp, out AsyncIOContext request) where FasterSession : IFasterSession { request = default; - if (status == OperationStatus.CPR_SHIFT_DETECTED) + if (operationStatus == OperationStatus.CPR_SHIFT_DETECTED) { SynchronizeEpoch(opCtx, currentCtx, ref pendingContext, fasterSession); } // RMW now suppports RETRY_NOW due to Sealed records. - if (status == OperationStatus.CPR_SHIFT_DETECTED || status == OperationStatus.RETRY_NOW || (asyncOp && status == OperationStatus.RETRY_LATER)) + if (operationStatus == OperationStatus.CPR_SHIFT_DETECTED || operationStatus == OperationStatus.RETRY_NOW || (asyncOp && operationStatus == OperationStatus.RETRY_LATER)) { #region Retry as (v+1) Operation var internalStatus = default(OperationStatus); @@ -2119,15 +2118,14 @@ ref pendingContext.input.Get(), } while (internalStatus == OperationStatus.RETRY_NOW || (asyncOp && internalStatus == OperationStatus.RETRY_LATER)); // Note that we spin in case of { async op + strict CPR } which is fine as this combination is rare/discouraged - status = internalStatus; + operationStatus = internalStatus; #endregion } - if (status == OperationStatus.SUCCESS || status == OperationStatus.NOTFOUND) - { - return (Status)status; - } - else if (status == OperationStatus.RECORD_ON_DISK) + if (OperationStatusUtils.TryConvertToStatusCode(operationStatus, out Status status)) + return status; + + if (operationStatus == OperationStatus.RECORD_ON_DISK) { //Add context to dictionary pendingContext.id = opCtx.totalPending++; @@ -2148,17 +2146,17 @@ ref pendingContext.input.Get(), hlog.GetAverageRecordSize(), request); - return Status.PENDING; + return new(StatusCode.Pending); } - else if (status == OperationStatus.RETRY_LATER) + else if (operationStatus == OperationStatus.RETRY_LATER) { Debug.Assert(!asyncOp); opCtx.retryRequests.Enqueue(pendingContext); - return Status.PENDING; + return new(StatusCode.Pending); } else { - return Status.ERROR; + return new(StatusCode.Error); } } @@ -2427,6 +2425,7 @@ internal OperationStatus InternalTryCopyToTail= fht.Log.BeginAddress) { - if (tempKvSession.ContainsKeyInMemory(ref key, out _) == Status.OK) + if (tempKvSession.ContainsKeyInMemory(ref key, out _).Found) { tempKvSession.Delete(ref key); } diff --git a/cs/src/core/Index/FASTER/FASTERThread.cs b/cs/src/core/Index/FASTER/FASTERThread.cs index 139465730..811dc7bda 100644 --- a/cs/src/core/Index/FASTER/FASTERThread.cs +++ b/cs/src/core/Index/FASTER/FASTERThread.cs @@ -189,19 +189,11 @@ internal void InternalCompleteRetryRequest - /// The minimum address at which to resolve the Key; return if the key is not found at this address or higher + /// The minimum address at which to resolve the Key; return false if the key is not found at this address or higher /// MinAddress = 0x00000002, diff --git a/cs/src/core/Index/Interfaces/FunctionsBase.cs b/cs/src/core/Index/Interfaces/FunctionsBase.cs index 97054b271..2e5b40cd0 100644 --- a/cs/src/core/Index/Interfaces/FunctionsBase.cs +++ b/cs/src/core/Index/Interfaces/FunctionsBase.cs @@ -40,7 +40,7 @@ public virtual void PostInitialUpdater(ref Key key, ref Input input, ref Value v /// public virtual void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) { } /// - public virtual bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) => true; + public virtual void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address) { } /// public virtual bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref Output output, ref RecordInfo recordInfo, long address) => true; diff --git a/cs/src/core/Index/Interfaces/IFasterSession.cs b/cs/src/core/Index/Interfaces/IFasterSession.cs index 51aa9ea2b..1694ec9ec 100644 --- a/cs/src/core/Index/Interfaces/IFasterSession.cs +++ b/cs/src/core/Index/Interfaces/IFasterSession.cs @@ -54,7 +54,7 @@ internal interface IFasterSession : IFasterS #region CopyUpdater bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue, ref Output output); void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address); - bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address); + void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address); #endregion CopyUpdater #region InPlaceUpdater diff --git a/cs/src/core/Index/Interfaces/IFunctions.cs b/cs/src/core/Index/Interfaces/IFunctions.cs index e2e6491ea..ea26cde11 100644 --- a/cs/src/core/Index/Interfaces/IFunctions.cs +++ b/cs/src/core/Index/Interfaces/IFunctions.cs @@ -165,8 +165,7 @@ public interface IFunctions /// The location where is to be copied /// A reference to the header of the record /// The logical address of the record being copied into; used as a RecordId by indexing - /// True if the value was successfully updated, else false (e.g. the value was expired) - bool PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address); + void PostCopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, ref Output output, ref RecordInfo recordInfo, long address); #endregion CopyUpdater #region InPlaceUpdater diff --git a/cs/src/core/Utilities/CompletionEvent.cs b/cs/src/core/Utilities/CompletionEvent.cs index 7b24946db..d01c3490b 100644 --- a/cs/src/core/Utilities/CompletionEvent.cs +++ b/cs/src/core/Utilities/CompletionEvent.cs @@ -20,6 +20,7 @@ internal void Set() while (true) { var tempSemaphore = this.semaphore; + if (tempSemaphore == null) break; if (Interlocked.CompareExchange(ref this.semaphore, newSemaphore, tempSemaphore) == tempSemaphore) { // Release all waiting threads diff --git a/cs/src/core/Utilities/Status.cs b/cs/src/core/Utilities/Status.cs index f2cc20584..a06cd7624 100644 --- a/cs/src/core/Utilities/Status.cs +++ b/cs/src/core/Utilities/Status.cs @@ -1,30 +1,107 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + namespace FASTER.core { /// /// Status result of operation on FASTER /// - public enum Status + [StructLayout(LayoutKind.Explicit, Size = 1)] + public struct Status { + [FieldOffset(0)] + private readonly StatusCode statusCode; + + /// + /// Create status from given status code + /// + /// + internal Status(StatusCode statusCode) => this.statusCode = statusCode; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Status(OperationStatus operationStatus) + { + var basicOperationStatus = OperationStatusUtils.BasicOpCode(operationStatus); + Debug.Assert(basicOperationStatus == OperationStatus.SUCCESS || basicOperationStatus == OperationStatus.NOTFOUND); + statusCode = (StatusCode)basicOperationStatus | (StatusCode)((int)operationStatus >> OperationStatusUtils.OpStatusToStatusCodeShift); + } + + /// + /// Create a Status value. + /// + public static Status CreateFound() => new(StatusCode.OK); + + /// + /// Create a Status value. Use the Is* properties to query. + /// + public static Status CreatePending() => new(StatusCode.Pending); + + /// + /// Whether a Read or RMW completed successfully (is not currently pending). Either may have appended, especially if the operation completed after going Pending. + /// Note that Pending must be checked for and handled by the app, because CompletePendingWithOutputs() will return a non-Pending status. + /// + public bool Found => (statusCode & StatusCode.BasicMask) == StatusCode.OK; + + /// + /// Whether the operation went pending + /// + public bool Pending => statusCode == StatusCode.Pending; + /// - /// For Read and RMW, item being read was found, and - /// the operation completed successfully - /// For Upsert, item was upserted successfully + /// Whether the operation is in an error state /// - OK, + public bool Faulted => statusCode == StatusCode.Error; + /// - /// For Read and RMW, item being read was not found + /// Whether the operation completed successfully, i.e., it is not pending and did not error out /// - NOTFOUND, + public bool CompletedSuccessfully + { + get + { + var basicCode = statusCode & StatusCode.BasicMask; + return basicCode != StatusCode.Pending && basicCode != StatusCode.Error; + } + } + + #region Advanced status /// - /// Operation went pending (async) + /// Whether a new record for a previously non-existent key was appended to the log. + /// Indicates that an existing record was updated in place. /// - PENDING, + public bool CreatedRecord => (statusCode & StatusCode.AdvancedMask) == StatusCode.CreatedRecord; + /// - /// Operation resulted in some error + /// Whether existing record was updated in place. /// - ERROR + public bool InPlaceUpdatedRecord => (statusCode & StatusCode.AdvancedMask) == StatusCode.InPlaceUpdatedRecord; + + /// + /// Whether an existing record key was copied, updated, and appended to the log. + /// + public bool CopyUpdatedRecord => (statusCode & StatusCode.AdvancedMask) == StatusCode.CopyUpdatedRecord; + + /// + /// Whether an existing record key was copied and appended to the log. + /// + public bool CopiedRecord => (statusCode & StatusCode.AdvancedMask) == StatusCode.CopiedRecord; + + /// + /// Whether an existing record key was copied, updated, and added to the readcache. + /// + public bool CopiedRecordToReadCache => (statusCode & StatusCode.AdvancedMask) == StatusCode.CopiedRecordToReadCache; + #endregion + + /// + /// Get the underlying status code value + /// + public byte Value => (byte)statusCode; + + /// + public override string ToString() => this.statusCode.ToString(); } } diff --git a/cs/src/core/Utilities/StatusCode.cs b/cs/src/core/Utilities/StatusCode.cs new file mode 100644 index 000000000..8f697cc26 --- /dev/null +++ b/cs/src/core/Utilities/StatusCode.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace FASTER.core +{ + /// + /// Return status code for FASTER operations + /// These are the basic codes that correspond to the old Status values, but *do not* compare to these directly; use the IsXxx functions. + /// + [Flags] + internal enum StatusCode : byte + { + #region Basic status codes + /// + /// General success indicator. By itself it means they key for the operation was found; it may have been updated in place or copied to the log tail. + /// + /// + /// + /// Upsert ConcurrentWriter: | + /// RMW InPlaceUpdater: | + /// RMW CopyUpdater: | + /// + /// If NeedCopyUpdate returns false: + /// + /// Delete ConcurrentDeleter: | + /// Read ConcurrentReader: + /// + /// If in immutable region and copying to tail: | + /// + /// Read Pending to SingleReader: + /// + /// If copying to tail: | + /// If copying to readCache: | + /// + /// + /// + OK = 0x00, + + /// + /// The key for the operation was not found. For Read, that is all that is returned for an unfound key. For other operations, see + /// the advanced enum values for more detailed information. + /// + /// + /// + /// Upsert SingleWriter (not found in mutable region): | + /// RMW InitialUpdater (not found in mutable, immutable, or on-disk regions): | + /// + /// If NeedInitialUpdate returns false: + /// + /// Delete SingleDeleter (not found in mutable region): | + /// + /// + NotFound = 0x01, + + /// + /// The Read or RMW operation went pending for I/O. This is not combined with advanced enum values; however, the application should + /// use this to issue CompletePending operations, and then can apply knowledge of this to the advanced enum values to know whether, + /// for example, a was a copy of a record from the ReadOnly in-memory region or from Storage. + /// + Pending = 0x02, + + /// + /// An error occurred. This is not combined with advanced enum values. + /// + Error = 0x03, + + // Values 0x03-0x0F are reserved for future use + + // Masking to extract the basic values + BasicMask = 0x0F, + #endregion + + // These are the advanced codes for additional info such as "did we CopyToTail?" or detailed info like "how exactly did this operation achieve its OK status?" + #region Advanced status codes + // Placeholder for "no advanced bit set"; we want nothing to show in debugger for this ("OK" is shown if the StatusCode is 0) + // None = 0x00, + + /// + /// Indicates that a new record for a previously non-existent key was appended to the log. + /// + /// + /// See basic codes for details of usage. + /// + CreatedRecord = 0x10, + + /// + /// Indicates that an existing record was updated in place. + /// + /// + /// See basic codes for details of usage. + /// + InPlaceUpdatedRecord = 0x20, + + /// + /// Indicates that an existing record key was copied, updated, and appended to the log. + /// + /// + /// See basic codes for details of usage. + /// + CopyUpdatedRecord = 0x30, + + /// + /// Indicates that an existing record key was copied and appended to the log. + /// + /// + /// See basic codes for details of usage. + /// + CopiedRecord = 0x40, + + /// + /// Indicates that an existing record key was copied, updated, and added to the readcache. + /// + /// + /// See basic codes for details of usage. + /// + CopiedRecordToReadCache = 0x50, + + // Values 0x30-0xF0 are reserved for future use + + // Mask to extract the advanced values + AdvancedMask = 0xF0 + #endregion + } +} diff --git a/cs/test/AdvancedLockTests.cs b/cs/test/AdvancedLockTests.cs index ef4a8809f..74f1be1fc 100644 --- a/cs/test/AdvancedLockTests.cs +++ b/cs/test/AdvancedLockTests.cs @@ -6,6 +6,8 @@ using System; using System.Threading; using FASTER.test.ReadCacheTests; +using static FASTER.test.TestUtils; +using System.Threading.Tasks; namespace FASTER.test.LockTests { @@ -116,21 +118,23 @@ void Populate(bool evict = false) [Test] [Category(TestUtils.FasterKVTestCategory)] [Category(TestUtils.LockTestCategory)] - public void SameKeyInsertAndCTTTest() + public async ValueTask SameKeyInsertAndCTTTest() { Populate(evict: true); Functions functions = new(); using var session = fkv.NewSession(functions); var iter = 0; - - TestUtils.DoTwoThreadTest(numKeys, + + await DoTwoThreadRandomKeyTest(numKeys, key => { int output = 0; var sleepFlag = (iter % 5 == 0) ? LockFunctionFlags.None : LockFunctionFlags.SleepAfterEventOperation; Input input = new() { flags = LockFunctionFlags.WaitForEvent | sleepFlag, sleepRangeMs = 10 }; var status = session.Upsert(key, input, key + valueAdd * 2, ref output); - Assert.AreEqual(Status.OK, status, $"Key = {key}"); + + // Don't test for .Found because we are doing random keys so may upsert one we have already seen, even on iter == 0 + //Assert.IsTrue(status.Found, $"Key = {key}, status = {status}"); }, key => { @@ -143,19 +147,23 @@ public void SameKeyInsertAndCTTTest() var status = session.Read(ref key, ref functions.readCacheInput, ref output, ref recordMetadata); // If the Upsert completed before the Read started, we may Read() the Upserted value. - if (status == Status.OK) + if (!status.Pending) + { + Assert.IsTrue(status.Found, $"Key = {key}, status {status}"); Assert.AreEqual(key + valueAdd * 2, output, $"Key = {key}"); + } else { - Assert.AreEqual(Status.PENDING, status, $"Key = {key}"); + Assert.IsTrue(status.Pending, $"Key = {key}, status = {status}"); session.CompletePending(wait: true); + // Output is not clear here and we are testing only threading aspects, so don't verify } }, key => { int output = default; var status = session.Read(ref key, ref output); - Assert.AreEqual(Status.OK, status, $"Key = {key}"); + Assert.IsTrue(status.Found, $"Key = {key}, status = {status}"); Assert.AreEqual(key + valueAdd * 2, output, $"Key = {key}"); functions.mres.Reset(); ++iter; diff --git a/cs/test/AsyncLargeObjectTests.cs b/cs/test/AsyncLargeObjectTests.cs index 0c0604bc9..21cc766d6 100644 --- a/cs/test/AsyncLargeObjectTests.cs +++ b/cs/test/AsyncLargeObjectTests.cs @@ -84,7 +84,7 @@ public async Task LargeObjectTest([Values]CheckpointType checkpointType) var key = new MyKey { key = keycnt }; var status = s2.Read(ref key, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) await s2.CompletePendingAsync(); else { diff --git a/cs/test/AsyncTests.cs b/cs/test/AsyncTests.cs index 4c7a4f9db..002d186ae 100644 --- a/cs/test/AsyncTests.cs +++ b/cs/test/AsyncTests.cs @@ -96,7 +96,7 @@ public async Task AsyncRecoveryTest1(CheckpointType checkpointType) { var status = s3.Read(ref inputArray[key], ref inputArg, ref output, Empty.Default, s3.SerialNo); - if (status == Status.PENDING) + if (status.Pending) s3.CompletePending(true,true); else { @@ -115,7 +115,7 @@ public class AdSimpleFunctions : FunctionsBase completedOutputs, ref KeyStruct keyStruct) { Assert.IsTrue(completedOutputs.Next()); - Assert.AreEqual(Status.NOTFOUND, completedOutputs.Current.Status); + Assert.IsFalse(completedOutputs.Current.Status.Found); Assert.AreEqual(keyStruct, completedOutputs.Current.Key); Assert.IsFalse(completedOutputs.Next()); completedOutputs.Dispose(); @@ -131,7 +131,7 @@ public async ValueTask ReadAndCompleteWithPendingOutput([Values]bool useRMW, [Va using var session = fht.For(new FunctionsWithContext()).NewSession>(); Assert.IsNull(session.completedOutputs); // Do not instantiate until we need it - ProcessPending processPending = new ProcessPending(); + ProcessPending processPending = new(); for (var key = 0; key < numRecords; ++key) { @@ -157,7 +157,7 @@ public async ValueTask ReadAndCompleteWithPendingOutput([Values]bool useRMW, [Va { var ksUnfound = keyStruct; ksUnfound.kfield1 += numRecords * 10; - if (session.Read(ref ksUnfound, ref inputStruct, ref outputStruct, contextStruct) == Status.PENDING) + if (session.Read(ref ksUnfound, ref inputStruct, ref outputStruct, contextStruct).Pending) { CompletedOutputIterator completedOutputs; if (isAsync) @@ -172,7 +172,7 @@ public async ValueTask ReadAndCompleteWithPendingOutput([Values]bool useRMW, [Va var status = useRMW ? session.RMW(ref keyStruct, ref inputStruct, ref outputStruct, contextStruct) : session.Read(ref keyStruct, ref inputStruct, ref outputStruct, contextStruct); - if (status == Status.PENDING) + if (status.Pending) { if (processPending.IsFirst()) { @@ -192,7 +192,7 @@ public async ValueTask ReadAndCompleteWithPendingOutput([Values]bool useRMW, [Va } continue; } - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); } processPending.VerifyNoDeferredPending(); @@ -210,7 +210,7 @@ public async ValueTask ReadAndCompleteWithPendingOutput([Values]bool useRMW, [Va // This should not be pending since we've not flushed. var localKey = key; var status = session.Read(ref localKey, ref inputStruct, ref outputStruct, ref recordMetadata); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending); Assert.AreEqual(address, recordMetadata.Address); } } diff --git a/cs/test/ExpirationTests.cs b/cs/test/ExpirationTests.cs index 66777d3c6..573c8c00e 100644 --- a/cs/test/ExpirationTests.cs +++ b/cs/test/ExpirationTests.cs @@ -63,33 +63,33 @@ internal enum TestOp { ExpireDelete, // Increment a counter and expire the record by deleting it // Mutable: // IPU sets tombstone and returns true; we see this and TryRemoveDeletedHashEntry and return SUCCESS - // Immutable: + // OnDisk: // CU sets tombstone; operation proceeds as normal otherwise ExpireRollover, // Increment a counter and expire the record by rolling it over (back to 1) // Mutable // IPU - InPlace (input len <= record len): Execute IU logic in current space; return true from IPU // - (TODO with revivication) NewRecord (input len > record len): Return false from IPU, true from NCU, set in CU - // Immutable: + // OnDisk: // CU - executes IU logic SetIfKeyExists, // Update a record if the key exists, but do not create a new record // Mutable: // Exists: update and return true from IPU // NotExists: return false from NIU - // Immutable: + // OnDisk: // Exists: return true from NCU, update in CU // NotExists: return false from NIU SetIfKeyNotExists, // Create a new record if the key does not exist, but do not update an existing record // Mutable: // Exists: no-op and return true from IPU // NotExists: return true from NIU, set in IU - // Immutable: + // OnDisk: // Exists: return false from NCU // NotExists: return true from NIU, set in IU SetIfValueEquals, // Update the record for a key if the current value equals a specified value // Mutable: // Equals: update and return true from IPU // NotEquals: no-op and return true from IPU - // Immutable: + // OnDisk: // Equals: return true from NCU, update in CU // NotEquals: return false from NCU // NotExists: Return false from NIU @@ -97,7 +97,7 @@ internal enum TestOp { // Mutable: // Equals: no-op and return true from IPU // NotEquals: update and return true from IPU - // Immutable: + // OnDisk: // Equals: return false from NCU // NotEquals: return true from NCU, update in CU // NotExists: Return false from NIU @@ -105,7 +105,7 @@ internal enum TestOp { // Mutable: // Equals: Same as ExpireDelete // NotEquals: no-op and return true from IPU - // Immutable: + // OnDisk: // Equals: Same as ExpireDelete // NotEquals: return false from NCU // NotExists: Return false from NIU @@ -477,15 +477,15 @@ private unsafe void Populate(Random rng) } } - private ExpirationOutput GetRecord(int key, Status expectedStatus, bool isImmutable) + private ExpirationOutput GetRecord(int key, Status expectedStatus, bool isOnDisk) { ExpirationInput input = default; ExpirationOutput output = new(); var status = session.Read(ref key, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - Assert.IsTrue(isImmutable); + Assert.IsTrue(isOnDisk); session.CompletePendingWithOutputs(out var completedOutputs, wait:true); (status, output) = TestUtils.GetSinglePendingResult(completedOutputs); } @@ -494,13 +494,13 @@ private ExpirationOutput GetRecord(int key, Status expectedStatus, bool isImmuta return output; } - private ExpirationOutput ExecuteRMW(int key, ref ExpirationInput input, bool isImmutable, Status expectedStatus = Status.OK) + private ExpirationOutput ExecuteRMW(int key, ref ExpirationInput input, bool isOnDisk, Status expectedStatus = default) { ExpirationOutput output = new (); var status = session.RMW(ref key, ref input, ref output); - if (status == Status.PENDING) + if (status.Pending) { - Assert.IsTrue(isImmutable); + Assert.IsTrue(isOnDisk); session.CompletePendingWithOutputs(out var completedOutputs, wait: true); (status, output) = TestUtils.GetSinglePendingResult(completedOutputs); } @@ -509,302 +509,319 @@ private ExpirationOutput ExecuteRMW(int key, ref ExpirationInput input, bool isI return output; } + private Status GetMutableVsOnDiskStatus(bool isOnDisk) + { + // The behavior is different for OnDisk vs. mutable: + // - OnDisk results in a call to NeedCopyUpdate which returns false, so RMW returns OK + // - Mutable results in a call to IPU which returns true, so RMW returns InPlaceUpdatedRecord. + return new(isOnDisk ? StatusCode.OK : StatusCode.InPlaceUpdatedRecord); + } + void InitialIncrement() { Populate(new Random(RandSeed)); - InitialRead(isImmutable: false, afterIncrement: false); - IncrementValue(TestOp.Increment, isImmutable: false); + InitialRead(isOnDisk: false, afterIncrement: false); + IncrementValue(TestOp.Increment, isOnDisk: false); } - private void InitialRead(bool isImmutable, bool afterIncrement) + private void InitialRead(bool isOnDisk, bool afterIncrement) { - var output = GetRecord(ModifyKey, Status.OK, isImmutable); + var output = GetRecord(ModifyKey, new(StatusCode.OK), isOnDisk); Assert.AreEqual(GetValue(ModifyKey) + (afterIncrement ? 1 : 0), output.retrievedValue); - Assert.AreEqual(isImmutable ? (Funcs.SingleReader | Funcs.ReadCompletionCallback) : Funcs.ConcurrentReader, output.functionsCalled); + Assert.AreEqual(isOnDisk ? (Funcs.SingleReader | Funcs.ReadCompletionCallback) : Funcs.ConcurrentReader, output.functionsCalled); } - private void IncrementValue(TestOp testOp, bool isImmutable) + private void IncrementValue(TestOp testOp, bool isOnDisk) { var key = ModifyKey; ExpirationInput input = new() { testOp = testOp }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.Incremented, output.result); } - private void MaybeMakeImmutable(bool isImmutable) + private void MaybeEvict(bool isOnDisk) { - if (isImmutable) + if (isOnDisk) { fht.Log.FlushAndEvict(wait: true); - InitialRead(isImmutable, afterIncrement: true); + InitialRead(isOnDisk, afterIncrement: true); } } - private void VerifyKeyNotCreated(TestOp testOp, bool isImmutable) + private void VerifyKeyNotCreated(TestOp testOp, bool isOnDisk) { var key = NoKey; // Key doesn't exist - no-op ExpirationInput input = new() { testOp = testOp, value = NoValue, comparisonValue = GetValue(key) + 1 }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); + + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, new(StatusCode.NotFound)); Assert.AreEqual(Funcs.NeedInitialUpdate, output.functionsCalled); Assert.AreEqual(ExpirationResult.None, output.result); Assert.AreEqual(0, output.retrievedValue); // Verify it's not there - GetRecord(key, Status.NOTFOUND, isImmutable); + GetRecord(key, new(StatusCode.NotFound), isOnDisk); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void PassiveExpireTest([Values]bool isImmutable) + public void PassiveExpireTest([Values]bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); - IncrementValue(TestOp.PassiveExpire, isImmutable); - GetRecord(ModifyKey, Status.NOTFOUND, isImmutable); + MaybeEvict(isOnDisk); + IncrementValue(TestOp.PassiveExpire, isOnDisk); + GetRecord(ModifyKey, new(StatusCode.NotFound), isOnDisk); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void ExpireDeleteTest([Values] bool isImmutable) + public void ExpireDeleteTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.ExpireDelete; var key = ModifyKey; + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); // Increment/Delete it ExpirationInput input = new() { testOp = testOp }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.ExpireDelete, output.result); // Verify it's not there - GetRecord(key, Status.NOTFOUND, isImmutable); + GetRecord(key, new(StatusCode.NotFound), isOnDisk); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void ExpireRolloverTest([Values] bool isImmutable) + public void ExpireRolloverTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.ExpireRollover; var key = ModifyKey; + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); // Increment/Rollover to initial state ExpirationInput input = new() { testOp = testOp }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.ExpireRollover, output.result); Assert.AreEqual(GetValue(key), output.retrievedValue); // Verify it's there with initial state - output = GetRecord(key, Status.OK, isImmutable:false /* update was appended */); + output = GetRecord(key, new(StatusCode.OK), isOnDisk:false /* update was appended */); Assert.AreEqual(GetValue(key), output.retrievedValue); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void SetIfKeyExistsTest([Values] bool isImmutable) + public void SetIfKeyExistsTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.SetIfKeyExists; var key = ModifyKey; + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); // Key exists - update it ExpirationInput input = new() { testOp = testOp, value = GetValue(key) + SetIncrement }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.Updated, output.result); Assert.AreEqual(input.value, output.retrievedValue); // Verify it's there with updated value - output = GetRecord(key, Status.OK, isImmutable: false /* update was appended */); + output = GetRecord(key, new(StatusCode.OK), isOnDisk: false /* update was appended */); Assert.AreEqual(input.value, output.retrievedValue); // Key doesn't exist - no-op key += SetIncrement; input = new() { testOp = testOp, value = GetValue(key) }; - output = ExecuteRMW(key, ref input, isImmutable); + output = ExecuteRMW(key, ref input, isOnDisk, new(StatusCode.NotFound)); Assert.AreEqual(Funcs.NeedInitialUpdate, output.functionsCalled); // Verify it's not there - GetRecord(key, Status.NOTFOUND, isImmutable); + GetRecord(key, new(StatusCode.NotFound), isOnDisk); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void SetIfKeyNotExistsTest([Values] bool isImmutable) + public void SetIfKeyNotExistsTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.SetIfKeyNotExists; var key = ModifyKey; // Key exists - no-op ExpirationInput input = new() { testOp = testOp, value = GetValue(key) + SetIncrement }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, GetMutableVsOnDiskStatus(isOnDisk)); + Assert.AreEqual(isOnDisk ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); // Verify it's there with unchanged value - output = GetRecord(key, Status.OK, isImmutable); + output = GetRecord(key, new(StatusCode.OK), isOnDisk); Assert.AreEqual(GetValue(key) + 1, output.retrievedValue); // Key doesn't exist - create it key += SetIncrement; input = new() { testOp = testOp, value = GetValue(key) }; - output = ExecuteRMW(key, ref input, isImmutable, Status.NOTFOUND); + output = ExecuteRMW(key, ref input, isOnDisk, new(StatusCode.NotFound | StatusCode.CreatedRecord)); Assert.AreEqual(Funcs.InitialUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.Updated, output.result); Assert.AreEqual(input.value, output.retrievedValue); // Verify it's there with specified value - output = GetRecord(key, Status.OK, isImmutable: false /* was just added */); + output = GetRecord(key, new(StatusCode.OK), isOnDisk: false /* was just added */); Assert.AreEqual(input.value, output.retrievedValue); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void SetIfValueEqualsTest([Values] bool isImmutable) + public void SetIfValueEqualsTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.SetIfValueEquals; var key = ModifyKey; + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); - VerifyKeyNotCreated(testOp, isImmutable); + VerifyKeyNotCreated(testOp, isOnDisk); // Value equals - update it ExpirationInput input = new() { testOp = testOp, value = GetValue(key) + SetIncrement, comparisonValue = GetValue(key) + 1 }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.Updated, output.result); Assert.AreEqual(input.value, output.retrievedValue); // Verify it's there with updated value - output = GetRecord(key, Status.OK, isImmutable); + output = GetRecord(key, new(StatusCode.OK), isOnDisk); Assert.AreEqual(input.value, output.retrievedValue); // Value doesn't equal - no-op key += 1; // We modified ModifyKey so get the next-higher key input = new() { testOp = testOp, value = -2, comparisonValue = -1 }; - output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); - Assert.AreEqual(isImmutable ? ExpirationResult.None : ExpirationResult.NotUpdated, output.result); - Assert.AreEqual(isImmutable ? 0 : GetValue(key), output.retrievedValue); + output = ExecuteRMW(key, ref input, isOnDisk, GetMutableVsOnDiskStatus(isOnDisk)); + Assert.AreEqual(isOnDisk ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + Assert.AreEqual(isOnDisk ? ExpirationResult.None : ExpirationResult.NotUpdated, output.result); + Assert.AreEqual(isOnDisk ? 0 : GetValue(key), output.retrievedValue); // Verify it's there with unchanged value; note that it has not been InitialIncrement()ed - output = GetRecord(key, Status.OK, isImmutable); + output = GetRecord(key, new(StatusCode.OK), isOnDisk); Assert.AreEqual(GetValue(key), output.retrievedValue); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void SetIfValueNotEqualsTest([Values] bool isImmutable) + public void SetIfValueNotEqualsTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.SetIfValueNotEquals; var key = ModifyKey; + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); - VerifyKeyNotCreated(testOp, isImmutable); + VerifyKeyNotCreated(testOp, isOnDisk); // Value equals ExpirationInput input = new() { testOp = testOp, value = -2, comparisonValue = GetValue(key) + 1 }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); - Assert.AreEqual(isImmutable ? ExpirationResult.None : ExpirationResult.NotUpdated, output.result); - Assert.AreEqual(isImmutable ? 0 : GetValue(key) + 1, output.retrievedValue); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, GetMutableVsOnDiskStatus(isOnDisk)); + Assert.AreEqual(isOnDisk ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + Assert.AreEqual(isOnDisk ? ExpirationResult.None : ExpirationResult.NotUpdated, output.result); + Assert.AreEqual(isOnDisk ? 0 : GetValue(key) + 1, output.retrievedValue); - output = GetRecord(key, Status.OK, isImmutable); + output = GetRecord(key, new(StatusCode.OK), isOnDisk); Assert.AreEqual(GetValue(key) + 1, output.retrievedValue); // Value doesn't equal input = new() { testOp = testOp, value = GetValue(key) + SetIncrement, comparisonValue = -1 }; - output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.Updated, output.result); Assert.AreEqual(GetValue(key) + SetIncrement, output.retrievedValue); - output = GetRecord(key, Status.OK, isImmutable); + output = GetRecord(key, new(StatusCode.OK), isOnDisk); Assert.AreEqual(GetValue(key) + SetIncrement, output.retrievedValue); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void DeleteIfValueEqualsTest([Values] bool isImmutable) + public void DeleteIfValueEqualsTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.DeleteIfValueEquals; var key = ModifyKey; + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); - VerifyKeyNotCreated(testOp, isImmutable); + VerifyKeyNotCreated(testOp, isOnDisk); // Value equals - delete it ExpirationInput input = new() { testOp = testOp, comparisonValue = GetValue(key) + 1 }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.Deleted, output.result); // Verify it's not there - GetRecord(key, Status.NOTFOUND, isImmutable); + GetRecord(key, new(StatusCode.NotFound), isOnDisk); // Value doesn't equal - no-op key += 1; // We deleted ModifyKey so get the next-higher key input = new() { testOp = testOp, comparisonValue = -1 }; - output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); - Assert.AreEqual(isImmutable ? ExpirationResult.None : ExpirationResult.NotDeleted, output.result); - Assert.AreEqual(isImmutable ? 0 : GetValue(key), output.retrievedValue); + output = ExecuteRMW(key, ref input, isOnDisk, GetMutableVsOnDiskStatus(isOnDisk)); + Assert.AreEqual(isOnDisk ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + Assert.AreEqual(isOnDisk ? ExpirationResult.None : ExpirationResult.NotDeleted, output.result); + Assert.AreEqual(isOnDisk ? 0 : GetValue(key), output.retrievedValue); // Verify it's there with unchanged value; note that it has not been InitialIncrement()ed - output = GetRecord(key, Status.OK, isImmutable); + output = GetRecord(key, new(StatusCode.OK), isOnDisk); Assert.AreEqual(GetValue(key), output.retrievedValue); } [Test] [Category("FasterKV")] [Category("Smoke"), Category("RMW")] - public void DeleteIfValueNotEqualsTest([Values] bool isImmutable) + public void DeleteIfValueNotEqualsTest([Values] bool isOnDisk) { InitialIncrement(); - MaybeMakeImmutable(isImmutable); + MaybeEvict(isOnDisk); const TestOp testOp = TestOp.DeleteIfValueNotEquals; var key = ModifyKey; + Status expectedFoundRmwStatus = isOnDisk ? new(StatusCode.CopyUpdatedRecord) : new(StatusCode.InPlaceUpdatedRecord); - VerifyKeyNotCreated(testOp, isImmutable); + VerifyKeyNotCreated(testOp, isOnDisk); // Value equals - no-op ExpirationInput input = new() { testOp = testOp, comparisonValue = GetValue(key) + 1 }; - ExpirationOutput output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); - Assert.AreEqual(isImmutable ? ExpirationResult.None : ExpirationResult.NotDeleted, output.result); - Assert.AreEqual(isImmutable ? 0 : GetValue(key) + 1, output.retrievedValue); + ExpirationOutput output = ExecuteRMW(key, ref input, isOnDisk, GetMutableVsOnDiskStatus(isOnDisk)); + Assert.AreEqual(isOnDisk ? Funcs.SkippedCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + Assert.AreEqual(isOnDisk ? ExpirationResult.None : ExpirationResult.NotDeleted, output.result); + Assert.AreEqual(isOnDisk ? 0 : GetValue(key) + 1, output.retrievedValue); // Verify it's there with unchanged value - output = GetRecord(key, Status.OK, isImmutable); + output = GetRecord(key, new(StatusCode.OK), isOnDisk); Assert.AreEqual(GetValue(key) + 1, output.retrievedValue); // Value doesn't equal - delete it input = new() { testOp = testOp, comparisonValue = -1 }; - output = ExecuteRMW(key, ref input, isImmutable); - Assert.AreEqual(isImmutable ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); + output = ExecuteRMW(key, ref input, isOnDisk, expectedFoundRmwStatus); + Assert.AreEqual(isOnDisk ? Funcs.DidCopyUpdate : Funcs.InPlaceUpdater, output.functionsCalled); Assert.AreEqual(ExpirationResult.Deleted, output.result); // Verify it's not there - GetRecord(key, Status.NOTFOUND, isImmutable); + GetRecord(key, new(StatusCode.NotFound), isOnDisk); } } } diff --git a/cs/test/GenericByteArrayTests.cs b/cs/test/GenericByteArrayTests.cs index 1b90644c9..d5da044b4 100644 --- a/cs/test/GenericByteArrayTests.cs +++ b/cs/test/GenericByteArrayTests.cs @@ -73,7 +73,7 @@ public void ByteArrayBasicTest() var key = GetByteArray(i); var value = GetByteArray(i); - if (session.Read(ref key, ref input, ref output, Empty.Default, 0) == Status.PENDING) + if (session.Read(ref key, ref input, ref output, Empty.Default, 0).Pending) { session.CompletePending(true); } diff --git a/cs/test/GenericDiskDeleteTests.cs b/cs/test/GenericDiskDeleteTests.cs index 0962e9b71..621a57722 100644 --- a/cs/test/GenericDiskDeleteTests.cs +++ b/cs/test/GenericDiskDeleteTests.cs @@ -3,6 +3,7 @@ using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test { @@ -65,7 +66,7 @@ public void DiskDeleteBasicTest1() var key1 = new MyKey { key = i }; var value = new MyValue { value = i }; - if (session.Read(ref key1, ref input, ref output, 0, 0) == Status.PENDING) + if (session.Read(ref key1, ref input, ref output, 0, 0).Pending) { session.CompletePending(true); } @@ -89,14 +90,12 @@ public void DiskDeleteBasicTest1() var status = session.Read(ref key1, ref input, ref output, 1, 0); - if (status == Status.PENDING) + if (status.Pending) { - session.CompletePending(true); - } - else - { - Assert.AreEqual(Status.NOTFOUND, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, _) = GetSinglePendingResult(outputs); } + Assert.IsFalse(status.Found); } @@ -132,13 +131,13 @@ public void DiskDeleteBasicTest2() var input = new MyInput { value = 1000 }; var output = new MyOutput(); var status = session.Read(ref key100, ref input, ref output, 1, 0); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found, status.ToString()); status = session.Upsert(ref key100, ref value100, 0, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found, status.ToString()); status = session.Read(ref key100, ref input, ref output, 0, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found, status.ToString()); Assert.AreEqual(value100.value, output.value.value); session.Delete(ref key100, 0, 0); @@ -146,10 +145,10 @@ public void DiskDeleteBasicTest2() // This RMW should create new initial value, since item is deleted status = session.RMW(ref key200, ref input, 1, 0); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found); status = session.Read(ref key200, ref input, ref output, 0, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found, status.ToString()); Assert.AreEqual(input.value, output.value.value); // Delete key 200 again @@ -163,16 +162,16 @@ public void DiskDeleteBasicTest2() session.Upsert(ref _key, ref _value, 0, 0); } status = session.Read(ref key100, ref input, ref output, 1, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); // This RMW should create new initial value, since item is deleted status = session.RMW(ref key200, ref input, 1, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); status = session.Read(ref key200, ref input, ref output, 0, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found, status.ToString()); Assert.AreEqual(input.value, output.value.value); } } diff --git a/cs/test/GenericLogCompactionTests.cs b/cs/test/GenericLogCompactionTests.cs index 004046570..1399fe0a7 100644 --- a/cs/test/GenericLogCompactionTests.cs +++ b/cs/test/GenericLogCompactionTests.cs @@ -3,6 +3,7 @@ using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test { @@ -73,7 +74,7 @@ public void TearDown() [Category("Smoke")] public void LogCompactBasicTest([Values] TestUtils.DeviceType deviceType, [Values] CompactionType compactionType) { - MyInput input = new MyInput(); + MyInput input = new(); const int totalRecords = 500; long compactUntil = 0; @@ -95,22 +96,22 @@ public void LogCompactBasicTest([Values] TestUtils.DeviceType deviceType, [Value // Read all keys - all should be present for (int i = 0; i < totalRecords; i++) { - MyOutput output = new MyOutput(); + MyOutput output = new(); var key1 = new MyKey { key = i }; var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, 0, 0); - if (status == Status.PENDING) + if (status.Pending) { session.CompletePendingWithOutputs(out var completedOutputs, wait: true); Assert.IsTrue(completedOutputs.Next()); - Assert.AreEqual(Status.OK, completedOutputs.Current.Status); + Assert.IsTrue(completedOutputs.Current.Status.Found); output = completedOutputs.Current.Output; Assert.IsFalse(completedOutputs.Next()); completedOutputs.Dispose(); } - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } } @@ -120,7 +121,7 @@ public void LogCompactBasicTest([Values] TestUtils.DeviceType deviceType, [Value [Category("Compaction")] public void LogCompactTestNewEntries([Values] CompactionType compactionType) { - MyInput input = new MyInput(); + MyInput input = new(); const int totalRecords = 2000; long compactUntil = 0; @@ -154,16 +155,16 @@ public void LogCompactTestNewEntries([Values] CompactionType compactionType) // Read 2000 keys - all should be present for (int i = 0; i < totalRecords; i++) { - MyOutput output = new MyOutput(); + MyOutput output = new(); var key1 = new MyKey { key = i }; var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, 0, 0); - if (status == Status.PENDING) + if (status.Pending) session.CompletePending(true); else { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } } @@ -175,7 +176,7 @@ public void LogCompactTestNewEntries([Values] CompactionType compactionType) [Category("Smoke")] public void LogCompactAfterDeleteTest([Values] CompactionType compactionType) { - MyInput input = new MyInput(); + MyInput input = new(); const int totalRecords = 2000; long compactUntil = 0; @@ -204,25 +205,25 @@ public void LogCompactAfterDeleteTest([Values] CompactionType compactionType) // Read keys - all should be present for (int i = 0; i < totalRecords; i++) { - MyOutput output = new MyOutput(); + MyOutput output = new(); var key1 = new MyKey { key = i }; var value = new MyValue { value = i }; int ctx = ((i < 500) && (i % 2 == 0)) ? 1 : 0; var status = session.Read(ref key1, ref input, ref output, ctx, 0); - if (status == Status.PENDING) + if (status.Pending) session.CompletePending(true); else { if (ctx == 0) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } else { - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found); } } } @@ -234,7 +235,7 @@ public void LogCompactAfterDeleteTest([Values] CompactionType compactionType) public void LogCompactBasicCustomFctnTest([Values] CompactionType compactionType) { - MyInput input = new MyInput(); + MyInput input = new(); const int totalRecords = 2000; var compactUntil = 0L; @@ -263,7 +264,7 @@ public void LogCompactBasicCustomFctnTest([Values] CompactionType compactionType var ctx = (i < (totalRecords / 2) && (i % 2 != 0)) ? 1 : 0; var status = session.Read(ref key1, ref input, ref output, ctx, 0); - if (status == Status.PENDING) + if (status.Pending) { session.CompletePending(true); } @@ -271,12 +272,12 @@ public void LogCompactBasicCustomFctnTest([Values] CompactionType compactionType { if (ctx == 0) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } else { - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found); } } } @@ -312,15 +313,13 @@ public void LogCompactCopyInPlaceCustomFctnTest([Values] CompactionType compacti var input = default(MyInput); var output = default(MyOutput); var status = session.Read(ref key, ref input, ref output, 0, 0); - if (status == Status.PENDING) + if (status.Pending) { - session.CompletePending(true); - } - else - { - Assert.AreEqual(Status.OK, status); - Assert.AreEqual(value.value, output.value.value); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); + Assert.AreEqual(value.value, output.value.value); } private class Test2CompactionFunctions : ICompactionFunctions diff --git a/cs/test/GenericStringTests.cs b/cs/test/GenericStringTests.cs index 340c369d1..7294b33c2 100644 --- a/cs/test/GenericStringTests.cs +++ b/cs/test/GenericStringTests.cs @@ -3,6 +3,7 @@ using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test { @@ -73,14 +74,14 @@ public void StringBasicTest([Values] TestUtils.DeviceType deviceType) var key = $"{i}"; var value = $"{i}"; - if (session.Read(ref key, ref input, ref output, Empty.Default, 0) == Status.PENDING) + var status = session.Read(ref key, ref input, ref output, Empty.Default, 0); + if (status.Pending) { - session.CompletePending(true); - } - else - { - Assert.AreEqual(value, output); + session.CompletePendingWithOutputs(out var outputs, wait:true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); + Assert.AreEqual(value, output); } } @@ -88,6 +89,7 @@ class MyFuncs : SimpleFunctions { public override void ReadCompletionCallback(ref string key, ref string input, ref string output, Empty ctx, Status status, RecordMetadata recordMetadata) { + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } } diff --git a/cs/test/InputOutputParameterTests.cs b/cs/test/InputOutputParameterTests.cs index 77b8f8b03..d5b29ff51 100644 --- a/cs/test/InputOutputParameterTests.cs +++ b/cs/test/InputOutputParameterTests.cs @@ -125,7 +125,7 @@ async Task doWrites() var r = await session.RMWAsync(ref key, ref input); if ((key & 0x1) == 0) { - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); status = r.Status; output = r.Output; @@ -141,7 +141,7 @@ async Task doWrites() var r = await session.UpsertAsync(ref key, ref input, ref key); if ((key & 0x1) == 0) { - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); status = r.Status; output = r.Output; @@ -159,10 +159,18 @@ async Task doWrites() ? session.RMW(ref key, ref input, ref output, out recordMetadata) : session.Upsert(ref key, ref input, ref key, ref output, out recordMetadata); } - Assert.AreEqual(loading && useRMW ? Status.NOTFOUND : Status.OK, status); - Assert.AreEqual(key * input, output); if (loading) + { + if (useRMW) + Assert.IsFalse(status.Found, status.ToString()); + else + Assert.IsTrue(status.CreatedRecord, status.ToString()); Assert.AreEqual(tailAddress, session.functions.lastWriteAddress); + } + else + Assert.IsTrue(status.InPlaceUpdatedRecord, status.ToString()); + + Assert.AreEqual(key * input, output); Assert.AreEqual(session.functions.lastWriteAddress, recordMetadata.Address); } } diff --git a/cs/test/LargeObjectTests.cs b/cs/test/LargeObjectTests.cs index a38ca2532..57f65cea6 100644 --- a/cs/test/LargeObjectTests.cs +++ b/cs/test/LargeObjectTests.cs @@ -84,7 +84,7 @@ public void LargeObjectTest(CheckpointType checkpointType) var key = new MyKey { key = keycnt }; var status = session2.Read(ref key, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) session2.CompletePending(true); else { diff --git a/cs/test/LockableUnsafeContextTests.cs b/cs/test/LockableUnsafeContextTests.cs index a35fc28a4..514181eeb 100644 --- a/cs/test/LockableUnsafeContextTests.cs +++ b/cs/test/LockableUnsafeContextTests.cs @@ -128,7 +128,7 @@ public void TearDown(bool forRecovery) void Populate() { for (int key = 0; key < numRecords; key++) - Assert.AreNotEqual(Status.PENDING, session.Upsert(key, key * valueMult)); + Assert.IsFalse(session.Upsert(key, key * valueMult).Pending); } static void AssertIsLocked(LockableUnsafeContext luContext, int key, bool xlock, bool slock) @@ -153,6 +153,13 @@ static void ClearCountsOnError(LockableUnsafeContext> luContext) + { + // If we already have an exception, clear these counts so "Run" will not report them spuriously. + luContext.sharedLockCount = 0; + luContext.exclusiveLockCount = 0; + } + void EnsureNoLocks() { using var iter = this.fht.Log.Scan(this.fht.Log.BeginAddress, this.fht.Log.TailAddress); @@ -160,7 +167,7 @@ void EnsureNoLocks() while (iter.GetNext(out var recordInfo, out var key, out var value)) { ++count; - Assert.False(recordInfo.IsLocked, $"Unexpected Locked record: {(recordInfo.NumLockedShared > 0 ? "S" : "")} {(recordInfo.IsLockedExclusive ? "X" : "")}"); + Assert.False(recordInfo.IsLocked, $"Unexpected Locked record for key {key}: {(recordInfo.NumLockedShared > 0 ? "S" : "")} {(recordInfo.IsLockedExclusive ? "X" : "")}"); } // We delete some records so just make sure the test worked. @@ -214,7 +221,7 @@ public void InMemorySimpleLockTxnTest([Values] ResultLockTarget resultLockTarget status = luContext.Read(24, out var value24); if (flushMode == FlushMode.OnDisk) { - if (status == Status.PENDING) + if (status.Pending) { luContext.CompletePendingWithOutputs(out var completedOutputs, wait: true); Assert.True(completedOutputs.Next()); @@ -227,13 +234,13 @@ public void InMemorySimpleLockTxnTest([Values] ResultLockTarget resultLockTarget } else { - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); } status = luContext.Read(51, out var value51); if (flushMode == FlushMode.OnDisk) { - if (status == Status.PENDING) + if (status.Pending) { luContext.CompletePendingWithOutputs(out var completedOutputs, wait: true); Assert.True(completedOutputs.Next()); @@ -246,7 +253,7 @@ public void InMemorySimpleLockTxnTest([Values] ResultLockTarget resultLockTarget } else { - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); } // Set the phase to Phase.INTERMEDIATE to test the non-Phase.REST blocks @@ -257,7 +264,7 @@ public void InMemorySimpleLockTxnTest([Values] ResultLockTarget resultLockTarget : luContext.Upsert(ref resultKey, ref dummyInOut, ref expectedResult, ref dummyInOut, out recordMetadata); if (flushMode == FlushMode.OnDisk) { - if (status == Status.PENDING) + if (status.Pending) { luContext.CompletePendingWithOutputs(out var completedOutputs, wait: true); Assert.True(completedOutputs.Next()); @@ -270,12 +277,12 @@ public void InMemorySimpleLockTxnTest([Values] ResultLockTarget resultLockTarget } else { - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); } // Reread the destination to verify status = luContext.Read(resultKey, out resultValue); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); Assert.AreEqual(expectedResult, resultValue); } foreach (var key in locks.Keys.OrderBy(key => -key)) @@ -294,7 +301,7 @@ public void InMemorySimpleLockTxnTest([Values] ResultLockTarget resultLockTarget // Verify reading the destination from the full session. status = session.Read(resultKey, out resultValue); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); Assert.AreEqual(expectedResult, resultValue); EnsureNoLocks(); } @@ -325,20 +332,20 @@ public void InMemoryLongLockTest([Values] ResultLockTarget resultLockTarget, [Va status = luContext.Read(24, out var value24); if (flushMode == FlushMode.OnDisk) { - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending, status.ToString()); luContext.CompletePendingWithOutputs(out var completedOutputs, wait: true); (status, value24) = GetSinglePendingResult(completedOutputs); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found, status.ToString()); Assert.AreEqual(24 * valueMult, value24); } else - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); // We just locked this above, but for FlushMode.OnDisk it will be in the LockTable and will still be PENDING. status = luContext.Read(51, out var value51); if (flushMode == FlushMode.OnDisk) { - if (status == Status.PENDING) + if (status.Pending) { luContext.CompletePendingWithOutputs(out var completedOutputs, wait: true); Assert.True(completedOutputs.Next()); @@ -351,7 +358,7 @@ public void InMemoryLongLockTest([Values] ResultLockTarget resultLockTarget, [Va } else { - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); } Assert.AreEqual(51 * valueMult, value51); @@ -360,10 +367,10 @@ public void InMemoryLongLockTest([Values] ResultLockTarget resultLockTarget, [Va status = useRMW ? luContext.RMW(resultKey, value24 + value51) : luContext.Upsert(resultKey, value24 + value51); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); status = luContext.Read(resultKey, out resultValue); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); Assert.AreEqual(expectedResult, resultValue); luContext.Unlock(51, LockType.Exclusive); @@ -380,7 +387,7 @@ public void InMemoryLongLockTest([Values] ResultLockTarget resultLockTarget, [Va // Verify from the full session. status = session.Read(resultKey, out resultValue); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); Assert.AreEqual(expectedResult, resultValue); EnsureNoLocks(); } @@ -417,11 +424,11 @@ public void InMemoryDeleteTest([Values] ResultLockTarget resultLockTarget, [Valu // Set the phase to Phase.INTERMEDIATE to test the non-Phase.REST blocks session.ctx.phase = phase; status = luContext.Delete(ref resultKey); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending, status.ToString()); // Reread the destination to verify status = luContext.Read(resultKey, out var _); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found, status.ToString()); foreach (var key in locks.Keys.OrderBy(key => key)) luContext.Unlock(key, locks[key]); @@ -439,7 +446,7 @@ public void InMemoryDeleteTest([Values] ResultLockTarget resultLockTarget, [Valu // Verify reading the destination from the full session. status = session.Read(resultKey, out var _); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found, status.ToString()); EnsureNoLocks(); } @@ -534,7 +541,7 @@ void AddLockTableEntry(LockableUnsafeContext> luContext, int expectedKey) + void VerifyAndUnlockSplicedInKey(LockableUnsafeContext> luContext, int expectedKey) { // Scan to the end of the readcache chain and verify we inserted the value. var (_, pa) = ChainTests.SkipReadCacheChain(fht, expectedKey); @@ -568,10 +575,10 @@ public void TransferFromLockTableToCTTTest() AddLockTableEntry(luContext, key, immutable: false); var status = session.Read(ref key, ref input, ref output, ref recordMetadata, ReadFlags.CopyToTail); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending, status.ToString()); session.CompletePending(wait: true); - VerifySplicedInKey(luContext, key); + VerifyAndUnlockSplicedInKey(luContext, key); } void PopulateAndEvict(bool immutable = false) @@ -603,21 +610,26 @@ public void TransferFromLockTableToUpsertTest([Values] ChainTests.RecordRegion r key = transferToExistingKey; AddLockTableEntry(luContext, key, recordRegion == ChainTests.RecordRegion.Immutable); var status = luContext.Upsert(key, key * valueMult); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.CreatedRecord, status.ToString()); } else { key = transferToNewKey; AddLockTableEntry(luContext, key, immutable: false); var status = luContext.Upsert(key, key * valueMult); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.CreatedRecord, status.ToString()); } } + catch (Exception) + { + ClearCountsOnError(luContext); + throw; + } finally { luContext.SuspendThread(); } - VerifySplicedInKey(luContext, key); + VerifyAndUnlockSplicedInKey(luContext, key); } [Test] @@ -639,7 +651,7 @@ public void TransferFromLockTableToRMWTest([Values] ChainTests.RecordRegion reco key = transferToExistingKey; AddLockTableEntry(luContext, key, recordRegion == ChainTests.RecordRegion.Immutable); var status = luContext.RMW(key, key * valueMult); - Assert.AreEqual(recordRegion == ChainTests.RecordRegion.OnDisk ? Status.PENDING : Status.OK, status); + Assert.IsTrue(recordRegion == ChainTests.RecordRegion.OnDisk ? status.Pending : status.Found); luContext.CompletePending(wait: true); } else @@ -647,15 +659,20 @@ public void TransferFromLockTableToRMWTest([Values] ChainTests.RecordRegion reco key = transferToNewKey; AddLockTableEntry(luContext, key, immutable: false); var status = luContext.RMW(key, key * valueMult); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found, status.ToString()); } } + catch (Exception) + { + ClearCountsOnError(luContext); + throw; + } finally { luContext.SuspendThread(); } - VerifySplicedInKey(luContext, key); + VerifyAndUnlockSplicedInKey(luContext, key); } [Test] @@ -677,24 +694,32 @@ public void TransferFromLockTableToDeleteTest([Values] ChainTests.RecordRegion r key = transferToExistingKey; AddLockTableEntry(luContext, key, recordRegion == ChainTests.RecordRegion.Immutable); var status = luContext.Delete(key); - Assert.AreEqual(Status.OK, status); + + // Delete does not search outside mutable region so the key will not be found + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); + + VerifyAndUnlockSplicedInKey(luContext, key); } else { key = transferToNewKey; AddLockTableEntry(luContext, key, immutable: false); var status = luContext.Delete(key); - Assert.AreEqual(Status.NOTFOUND, status); - luContext.Unlock(key, LockType.Exclusive); // TODO Delete should do this + Assert.IsFalse(status.Found, status.ToString()); + + // The mutable portion of this test does not transfer because the key is not found + luContext.Unlock(key, LockType.Exclusive); } } + catch (Exception) + { + ClearCountsOnError(luContext); + throw; + } finally { luContext.SuspendThread(); } - - if (recordRegion != ChainTests.RecordRegion.NotFound) - VerifySplicedInKey(luContext, key); } [Test] @@ -747,7 +772,7 @@ public void TransferFromReadOnlyToUpdateRecordTest([Values] UpdateOp updateOp) const int key = 42; luContext.Lock(key, LockType.Exclusive); - int getValue(int key) => key + valueMult; + static int getValue(int key) => key + valueMult; luContext.ResumeThread(); @@ -758,15 +783,23 @@ public void TransferFromReadOnlyToUpdateRecordTest([Values] UpdateOp updateOp) UpdateOp.Upsert => luContext.Upsert(key, getValue(key)), UpdateOp.RMW => luContext.RMW(key, getValue(key)), UpdateOp.Delete => luContext.Delete(key), - _ => Status.ERROR + _ => new(StatusCode.Error) }; - Assert.AreNotEqual(Status.ERROR, status, $"Unexpected UpdateOp {updateOp}"); - Assert.AreEqual(Status.OK, status); + Assert.IsFalse(status.Faulted, $"Unexpected UpdateOp {updateOp}, status {status}"); + if (updateOp == UpdateOp.RMW) + Assert.IsTrue(status.CopyUpdatedRecord, status.ToString()); + else + Assert.IsTrue(status.CreatedRecord, status.ToString()); var (xlock, slock) = luContext.IsLocked(key); Assert.IsTrue(xlock); Assert.AreEqual(0, slock); } + catch (Exception) + { + ClearCountsOnError(luContext); + throw; + } finally { luContext.SuspendThread(); @@ -777,7 +810,6 @@ public void TransferFromReadOnlyToUpdateRecordTest([Values] UpdateOp updateOp) [Test] [Category(LockableUnsafeContextTestCategory)] - [Category(SmokeTestCategory)] public void LockNewRecordCompeteWithUpdateTest([Values(LockOperationType.Lock, LockOperationType.Unlock)] LockOperationType lockOp, [Values] UpdateOp updateOp) { const int numNewRecords = 100; @@ -795,7 +827,7 @@ public void LockNewRecordCompeteWithUpdateTest([Values(LockOperationType.Lock, L if (updateOp == UpdateOp.Delete) { for (var key = numRecords; key < numRecords + numNewRecords; ++key) - Assert.AreNotEqual(Status.PENDING, session.Upsert(key, key * valueMult)); + Assert.IsFalse(session.Upsert(key, key * valueMult).Pending); fht.Log.FlushAndEvict(wait: true); } @@ -824,31 +856,39 @@ void unlockKey(int key) // Sleep at varying durations for each call to comparer.GetHashCode, which is called at the start of Lock/Unlock and Upsert/RMW/Delete. comparer.maxSleepMs = 20; - for (var key = numRecords; key < numRecords + numNewRecords; ++key) + try { - // Use Task instead of Thread because this propagates exceptions (such as Assert.* failures) back to this thread. - Task.WaitAll(Task.Run(() => locker(key)), Task.Run(() => updater(key))); - var (xlock, slockCount) = lockLuContext.IsLocked(key); - var expectedXlock = getLockType(key) == LockType.Exclusive && lockOp != LockOperationType.Unlock; - var expectedSlock = getLockType(key) == LockType.Shared && lockOp != LockOperationType.Unlock; - Assert.AreEqual(expectedXlock, xlock); - Assert.AreEqual(expectedSlock, slockCount > 0); - - if (lockOp == LockOperationType.Lock) - { - // There should be no entries in the locktable now; they should all be on the RecordInfo. - Assert.IsFalse(fht.LockTable.IsActive, $"count = {fht.LockTable.dict.Count}"); - } - else + for (var key = numRecords; key < numRecords + numNewRecords; ++key) { - // We are unlocking so should remove one record for each iteration. - Assert.AreEqual(numNewRecords + numRecords - key - 1, fht.LockTable.dict.Count); + // Use Task instead of Thread because this propagates exceptions (such as Assert.* failures) back to this thread. + Task.WaitAll(Task.Run(() => locker(key)), Task.Run(() => updater(key))); + var (xlock, slockCount) = lockLuContext.IsLocked(key); + var expectedXlock = getLockType(key) == LockType.Exclusive && lockOp != LockOperationType.Unlock; + var expectedSlock = getLockType(key) == LockType.Shared && lockOp != LockOperationType.Unlock; + Assert.AreEqual(expectedXlock, xlock); + Assert.AreEqual(expectedSlock, slockCount > 0); + + if (lockOp == LockOperationType.Lock) + { + // There should be no entries in the locktable now; they should all be on the RecordInfo. + Assert.IsFalse(fht.LockTable.IsActive, $"count = {fht.LockTable.dict.Count}"); + } + else + { + // We are unlocking so should remove one record for each iteration. + Assert.AreEqual(numNewRecords + numRecords - key - 1, fht.LockTable.dict.Count); + } } - } - // Unlock all the keys we are expecting to unlock, which ensures all the locks were applied to RecordInfos as expected. - foreach (var key in locks.ToArray()) - unlockKey(key); + // Unlock all the keys we are expecting to unlock, which ensures all the locks were applied to RecordInfos as expected. + foreach (var key in locks.ToArray()) + unlockKey(key); + } + catch (Exception) + { + ClearCountsOnError(lockLuContext); + throw; + } void locker(int key) { @@ -860,6 +900,11 @@ void locker(int key) else unlockKey(key); } + catch (Exception) + { + ClearCountsOnError(lockLuContext); + throw; + } finally { lockLuContext.SuspendThread(); @@ -873,19 +918,22 @@ void updater(int key) try { // Use the LuContext here even though we're not doing locking, because we don't want the ephemeral locks to be tried for this test - // (the test will hang as we try to acquire the lock). + // (the test would hang when trying to acquire the ephemeral lock). var status = updateOp switch { UpdateOp.Upsert => updateLuContext.Upsert(key, getValue(key)), UpdateOp.RMW => updateLuContext.RMW(key, getValue(key)), UpdateOp.Delete => updateLuContext.Delete(key), - _ => Status.ERROR + _ => new(StatusCode.Error) }; - Assert.AreNotEqual(Status.ERROR, status, $"Unexpected UpdateOp {updateOp}"); - if (updateOp == UpdateOp.RMW) - Assert.AreEqual(Status.NOTFOUND, status); - else - Assert.AreEqual(Status.OK, status); + Assert.IsFalse(status.Faulted, $"Unexpected UpdateOp {updateOp}, status {status}"); + Assert.IsFalse(status.Found, status.ToString()); + Assert.IsTrue(status.CreatedRecord, status.ToString()); + } + catch (Exception) + { + ClearCountsOnError(updateLuContext); + throw; } finally { @@ -966,7 +1014,7 @@ public void EvictFromMainLogToLockTableTest() int input = 0, output = 0, localKey = key; RecordMetadata recordMetadata = default; var status = session.Read(ref localKey, ref input, ref output, ref recordMetadata, ReadFlags.CopyToTail); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending, status.ToString()); session.CompletePending(wait: true); Assert.IsFalse(fht.LockTable.Get(key, out _)); @@ -1101,7 +1149,7 @@ async static Task PrimaryWriter(FasterKV primaryStore, SyncMode sync } var status = s1.Upsert(ref key, ref key); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.CreatedRecord, status.ToString()); luc1.Lock(key, LockType.Shared); } @@ -1141,7 +1189,7 @@ async static Task SecondaryReader(FasterKV secondaryStore, SyncMode while (true) { var status = s1.Read(ref key, ref output); - if (status == Status.NOTFOUND) + if (!status.Found) { // Key {key} not found at secondary; performing recovery to catch up Thread.Sleep(500); diff --git a/cs/test/LowMemAsyncTests.cs b/cs/test/LowMemAsyncTests.cs index 86c56208c..83c8be2fe 100644 --- a/cs/test/LowMemAsyncTests.cs +++ b/cs/test/LowMemAsyncTests.cs @@ -54,7 +54,7 @@ private static async Task Populate(ClientSession, int, int> { public override void RMWCompletionCallback(ref ReadOnlyMemory key, ref Memory input, ref (IMemoryOwner, int) output, int ctx, Status status, RecordMetadata recordMetadata) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); + Assert.IsTrue(status.CopyUpdatedRecord); } public override void ReadCompletionCallback(ref ReadOnlyMemory key, ref Memory input, ref (IMemoryOwner, int) output, int ctx, Status status, RecordMetadata recordMetadata) @@ -140,17 +141,18 @@ public override void ReadCompletionCallback(ref ReadOnlyMemory key, ref Mem { if (ctx == 0) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.IsTrue(output.Item1.Memory.Span.Slice(0, output.Item2).SequenceEqual(key.Span)); } else { - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found); } } finally { - if (status == Status.OK) output.Item1.Dispose(); + if (status.Found) + output.Item1.Dispose(); } } } diff --git a/cs/test/MiscFASTERTests.cs b/cs/test/MiscFASTERTests.cs index fde8912f7..1c392f487 100644 --- a/cs/test/MiscFASTERTests.cs +++ b/cs/test/MiscFASTERTests.cs @@ -3,6 +3,7 @@ using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test { @@ -47,7 +48,7 @@ public void MixedTest1() int key = 8999998; var input1 = new MyInput { value = 23 }; - MyOutput output = new MyOutput(); + MyOutput output = new(); session.RMW(ref key, ref input1, Empty.Default, 0); @@ -75,32 +76,28 @@ public void MixedTest2() } var key2 = 23; - var input = new MyInput(); - MyOutput g1 = new MyOutput(); + MyInput input = new(); + MyOutput g1 = new(); var status = session.Read(ref key2, ref input, ref g1, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - session.CompletePending(true); - } - else - { - Assert.AreEqual(Status.OK, status); + session.CompletePendingWithOutputs(out var outputs, wait:true); + (status, _) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(23, g1.value.value); key2 = 99999; status = session.Read(ref key2, ref input, ref g1, Empty.Default, 0); - if (status == Status.PENDING) - { - session.CompletePending(true); - } - else + if (status.Pending) { - Assert.AreEqual(Status.NOTFOUND, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, _) = GetSinglePendingResult(outputs); } + Assert.IsFalse(status.Found); } [Test] @@ -126,17 +123,20 @@ public void ShouldCreateNewRecordIfConcurrentWriterReturnsFalse() key = new KeyStruct() { kfield1 = 1, kfield2 = 2 }; value = new ValueStruct() { vfield1 = 1000, vfield2 = 2000 }; - session.Upsert(ref key, ref input, ref value, ref output, out RecordMetadata recordMetadata1); + var status = session.Upsert(ref key, ref input, ref value, ref output, out RecordMetadata recordMetadata1); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); + // ConcurrentWriter returns false, so we create a new record (and leave the old one sealed). value = new ValueStruct() { vfield1 = 1001, vfield2 = 2002 }; - session.Upsert(ref key, ref input, ref value, ref output, out RecordMetadata recordMetadata2); + status = session.Upsert(ref key, ref input, ref value, ref output, out RecordMetadata recordMetadata2); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); Assert.Greater(recordMetadata2.Address, recordMetadata1.Address); var recordCount = 0; using (var iterator = fht.Log.Scan(fht.Log.BeginAddress, fht.Log.TailAddress)) { - // We now seal before copying and unseal/set to Invalid after copying, so we only get one record. + // We seal before copying and leave it sealed after copying, so we only get one record. while (iterator.GetNext(out var info)) { recordCount++; diff --git a/cs/test/NativeReadCacheTests.cs b/cs/test/NativeReadCacheTests.cs index 5d590ff62..0a6ad0837 100644 --- a/cs/test/NativeReadCacheTests.cs +++ b/cs/test/NativeReadCacheTests.cs @@ -60,7 +60,7 @@ public void NativeDiskWriteReadCache() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } @@ -72,7 +72,7 @@ public void NativeDiskWriteReadCache() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); } @@ -88,7 +88,7 @@ public void NativeDiskWriteReadCache() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } @@ -100,7 +100,7 @@ public void NativeDiskWriteReadCache() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); } @@ -120,7 +120,7 @@ public void NativeDiskWriteReadCache() var key1 = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; input = new InputStruct { ifield1 = 1, ifield2 = 1 }; var status = session.RMW(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { session.CompletePending(true); } @@ -139,7 +139,7 @@ public void NativeDiskWriteReadCache() var value = new ValueStruct { vfield1 = i + 1, vfield2 = i + 2 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); } @@ -172,7 +172,7 @@ public void NativeDiskWriteReadCache2() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } @@ -184,7 +184,7 @@ public void NativeDiskWriteReadCache2() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); } @@ -200,7 +200,7 @@ public void NativeDiskWriteReadCache2() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } @@ -212,7 +212,7 @@ public void NativeDiskWriteReadCache2() var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); } diff --git a/cs/test/NeedCopyUpdateTests.cs b/cs/test/NeedCopyUpdateTests.cs index 7c4e368ca..2489414ef 100644 --- a/cs/test/NeedCopyUpdateTests.cs +++ b/cs/test/NeedCopyUpdateTests.cs @@ -3,6 +3,7 @@ using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test { @@ -43,41 +44,51 @@ public void TearDown() [Category("Smoke")] public void TryAddTest() { - using var session = fht.For(new TryAddTestFunctions()).NewSession(); + TryAddTestFunctions functions = new(); + using var session = fht.For(functions).NewSession(); Status status; var key = 1; var value1 = new RMWValue { value = 1 }; var value2 = new RMWValue { value = 2 }; + functions.noNeedInitialUpdater = true; + status = session.RMW(ref key, ref value1); // needInitialUpdater false + NOTFOUND + Assert.IsFalse(status.Found, status.ToString()); + Assert.IsFalse(value1.flag); // InitialUpdater is not called + functions.noNeedInitialUpdater = false; + status = session.RMW(ref key, ref value1); // InitialUpdater + NOTFOUND - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found, status.ToString()); Assert.IsTrue(value1.flag); // InitialUpdater is called status = session.RMW(ref key, ref value2); // InPlaceUpdater + OK - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.InPlaceUpdatedRecord, status.ToString()); fht.Log.Flush(true); - status = session.RMW(ref key, ref value2); // NeedCopyUpdate + OK - Assert.AreEqual(Status.OK, status); + status = session.RMW(ref key, ref value2); // NeedCopyUpdate returns false, so RMW returns simply Found + Assert.IsTrue(status.Found, status.ToString()); fht.Log.FlushAndEvict(true); - status = session.RMW(ref key, ref value2, Status.OK, 0); // PENDING + NeedCopyUpdate + OK - Assert.AreEqual(Status.PENDING, status); - session.CompletePending(true); + status = session.RMW(ref key, ref value2, new(StatusCode.OK), 0); // PENDING + NeedCopyUpdate + OK + Assert.IsTrue(status.Pending, status.ToString()); + session.CompletePendingWithOutputs(out var outputs, true); - // Test stored value. Should be value1 var output = new RMWValue(); - status = session.Read(ref key, ref value1, ref output, Status.OK, 0); - Assert.AreEqual(Status.PENDING, status); + (status, output) = GetSinglePendingResult(outputs); + Assert.IsTrue(status.Found, status.ToString()); // NeedCopyUpdate returns false, so RMW returns simply Found + + // Test stored value. Should be value1 + status = session.Read(ref key, ref value1, ref output, new(StatusCode.OK), 0); + Assert.IsTrue(status.Pending, status.ToString()); session.CompletePending(true); status = session.Delete(ref key); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); session.CompletePending(true); fht.Log.FlushAndEvict(true); - status = session.RMW(ref key, ref value2, Status.NOTFOUND, 0); // PENDING + InitialUpdater + NOTFOUND - Assert.AreEqual(Status.PENDING, status); + status = session.RMW(ref key, ref value2, new(StatusCode.NotFound | StatusCode.CreatedRecord), 0); // PENDING + InitialUpdater + NOTFOUND + Assert.IsTrue(status.Pending, status.ToString()); session.CompletePending(true); } } @@ -106,6 +117,13 @@ public override void Deserialize(out RMWValue value) internal class TryAddTestFunctions : TryAddFunctions { + internal bool noNeedInitialUpdater; + + public override bool NeedInitialUpdate(ref int key, ref RMWValue input, ref RMWValue output) + { + return noNeedInitialUpdater ? false : base.NeedInitialUpdate(ref key, ref input, ref output); + } + public override void InitialUpdater(ref int key, ref RMWValue input, ref RMWValue value, ref RMWValue output, ref RecordInfo recordInfo, long address) { input.flag = true; @@ -121,7 +139,7 @@ public override void RMWCompletionCallback(ref int key, ref RMWValue input, ref { Assert.AreEqual(ctx, status); - if (status == Status.NOTFOUND) + if (!status.Found) Assert.IsTrue(input.flag); // InitialUpdater is called. } diff --git a/cs/test/ObjectFASTERTests.cs b/cs/test/ObjectFASTERTests.cs index e415ed757..a92163e09 100644 --- a/cs/test/ObjectFASTERTests.cs +++ b/cs/test/ObjectFASTERTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test { @@ -46,11 +47,11 @@ public void ObjectInMemWriteRead() { using var session = fht.NewSession(new MyFunctions()); - var key1 = new MyKey { key = 9999999 }; - var value = new MyValue { value = 23 }; + MyKey key1 = new() { key = 9999999 }; + MyValue value = new() { value = 23 }; MyInput input = null; - MyOutput output = new MyOutput(); + MyOutput output = new(); session.Upsert(ref key1, ref value, Empty.Default, 0); session.Read(ref key1, ref input, ref output, Empty.Default, 0); @@ -63,14 +64,14 @@ public void ObjectInMemWriteRead2() { using var session = fht.NewSession(new MyFunctions()); - var key1 = new MyKey { key = 8999998 }; - var input1 = new MyInput { value = 23 }; - MyOutput output = new MyOutput(); + MyKey key1 = new() { key = 8999998 }; + MyInput input1 = new() { value = 23 }; + MyOutput output = new(); session.RMW(ref key1, ref input1, Empty.Default, 0); - var key2 = new MyKey { key = 8999999 }; - var input2 = new MyInput { value = 24 }; + MyKey key2 = new() { key = 8999999 }; + MyInput input2 = new() { value = 24 }; session.RMW(ref key2, ref input2, Empty.Default, 0); session.Read(ref key1, ref input1, ref output, Empty.Default, 0); @@ -98,32 +99,30 @@ public void ObjectDiskWriteRead() // fht.ShiftReadOnlyAddress(fht.LogTailAddress); } - var key2 = new MyKey { key = 23 }; - var input = new MyInput(); - MyOutput g1 = new MyOutput(); + MyKey key2 = new() { key = 23 }; + MyInput input = new(); + MyOutput g1 = new(); var status = session.Read(ref key2, ref input, ref g1, Empty.Default, 0); - if (status == Status.PENDING) - { - session.CompletePending(true); - } - else + if (status.Pending) { - Assert.AreEqual(Status.OK, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, g1) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(23, g1.value.value); key2 = new MyKey { key = 99999 }; status = session.Read(ref key2, ref input, ref g1, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { session.CompletePending(true); } else { - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found); } // Update first 100 using RMW from storage @@ -132,7 +131,7 @@ public void ObjectDiskWriteRead() var key1 = new MyKey { key = i }; input = new MyInput { value = 1 }; status = session.RMW(ref key1, ref input, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) session.CompletePending(true); } @@ -142,7 +141,7 @@ public void ObjectDiskWriteRead() var key1 = new MyKey { key = i }; var value = new MyValue { value = i }; - if (session.Read(ref key1, ref input, ref output, Empty.Default, 0) == Status.PENDING) + if (session.Read(ref key1, ref input, ref output, Empty.Default, 0).Pending) { session.CompletePending(true); } @@ -175,7 +174,7 @@ public async Task ReadAsyncObjectDiskWriteRead() var value = new MyValue { value = i }; var r = await session.UpsertAsync(ref key, ref value); - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); // test async version of Upsert completion } @@ -183,21 +182,21 @@ public async Task ReadAsyncObjectDiskWriteRead() var input = new MyInput(); var readResult = await session.ReadAsync(ref key1, ref input, Empty.Default); var result = readResult.Complete(); - Assert.AreEqual(Status.OK, result.status); + Assert.IsTrue(result.status.Found); Assert.AreEqual(1989, result.output.value.value); var key2 = new MyKey { key = 23 }; readResult = await session.ReadAsync(ref key2, ref input, Empty.Default); result = readResult.Complete(); - Assert.AreEqual(Status.OK, result.status); + Assert.IsTrue(result.status.Found); Assert.AreEqual(23, result.output.value.value); var key3 = new MyKey { key = 9999 }; readResult = await session.ReadAsync(ref key3, ref input, Empty.Default); result = readResult.Complete(); - Assert.AreEqual(Status.NOTFOUND, result.status); + Assert.IsFalse(result.status.Found); // Update last 100 using RMW in memory for (int i = 1900; i < 2000; i++) @@ -205,7 +204,7 @@ public async Task ReadAsyncObjectDiskWriteRead() var key = new MyKey { key = i }; input = new MyInput { value = 1 }; var r = await session.RMWAsync(ref key, ref input, Empty.Default); - while (r.Status == Status.PENDING) + while (r.Status.Pending) { r = await r.CompleteAsync(); // test async version of RMW completion } @@ -227,7 +226,7 @@ public async Task ReadAsyncObjectDiskWriteRead() readResult = await session.ReadAsync(ref key, ref input, Empty.Default); result = readResult.Complete(); - Assert.AreEqual(Status.OK, result.status); + Assert.IsTrue(result.status.Found); if (i < 100 || i >= 1900) Assert.AreEqual(value.value + 1, result.output.value.value); else diff --git a/cs/test/ObjectReadCacheTests.cs b/cs/test/ObjectReadCacheTests.cs index 3935b3274..5b09f28bf 100644 --- a/cs/test/ObjectReadCacheTests.cs +++ b/cs/test/ObjectReadCacheTests.cs @@ -67,7 +67,7 @@ public void ObjectDiskWriteReadCache() var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } @@ -79,7 +79,7 @@ public void ObjectDiskWriteReadCache() var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } @@ -94,7 +94,7 @@ public void ObjectDiskWriteReadCache() var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } @@ -106,7 +106,7 @@ public void ObjectDiskWriteReadCache() var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } @@ -125,7 +125,7 @@ public void ObjectDiskWriteReadCache() var key1 = new MyKey { key = i }; input = new MyInput { value = 1 }; var status = session.RMW(ref key1, ref input, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) session.CompletePending(true); } @@ -137,7 +137,7 @@ public void ObjectDiskWriteReadCache() var value = new MyValue { value = i + 1 }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status, $"key = {key1.key}"); + Assert.IsTrue(status.Found, $"key = {key1.key}"); Assert.AreEqual(value.value, output.value.value); } } @@ -169,7 +169,7 @@ public void ObjectDiskWriteReadCache2() var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } @@ -181,7 +181,7 @@ public void ObjectDiskWriteReadCache2() var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } @@ -196,19 +196,19 @@ public void ObjectDiskWriteReadCache2() var value = new MyValue { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending); session.CompletePending(true); } // Read 100 keys - all should be served from cache for (int i = 1900; i < 2000; i++) { - MyOutput output = new MyOutput(); - var key1 = new MyKey { key = i }; - var value = new MyValue { value = i }; + MyOutput output = new(); + MyKey key1 = new() { key = i }; + MyValue value = new() { value = i }; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(value.value, output.value.value); } } diff --git a/cs/test/ObjectRecoveryTest2.cs b/cs/test/ObjectRecoveryTest2.cs index 1256545a4..37dacaaa2 100644 --- a/cs/test/ObjectRecoveryTest2.cs +++ b/cs/test/ObjectRecoveryTest2.cs @@ -119,36 +119,36 @@ private void Read(ClientSession 0) { fht.TryInitiateHybridLogCheckpoint(out Guid token, checkpointType); - fht.CompleteCheckpointAsync().GetAwaiter().GetResult(); + fht.CompleteCheckpointAsync().AsTask().GetAwaiter().GetResult(); tokens.Add((i, token)); } } @@ -125,17 +125,17 @@ private void Read(ClientSession public override void CopyUpdater(ref int key, ref int input, ref int oldValue, ref int newValue, ref int output, ref RecordInfo recordInfo, long address) { newValue = oldValue; } /// - public override bool PostCopyUpdater(ref int key, ref int input, ref int oldValue, ref int newValue, ref int output, ref RecordInfo recordInfo, long address) { this.pcuAddress = address; return true; } + public override void PostCopyUpdater(ref int key, ref int input, ref int oldValue, ref int newValue, ref int output, ref RecordInfo recordInfo, long address) { this.pcuAddress = address; } public override void PostSingleDeleter(ref int key, ref RecordInfo recordInfo, long address) { this.psdAddress = address; } public override bool ConcurrentDeleter(ref int key, ref int value, ref RecordInfo recordInfo, long address) => false; diff --git a/cs/test/ReadAddressTests.cs b/cs/test/ReadAddressTests.cs index e629e1c06..a50ebcbbd 100644 --- a/cs/test/ReadAddressTests.cs +++ b/cs/test/ReadAddressTests.cs @@ -122,7 +122,7 @@ public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValu public override void ReadCompletionCallback(ref Key key, ref Value input, ref Output output, Empty ctx, Status status, RecordMetadata recordMetadata) { - if (status == Status.OK) + if (status.Found) { if (this.useReadCache && !this.readFlags.HasFlag(ReadFlags.SkipReadCache)) Assert.AreEqual(Constants.kInvalidAddress, recordMetadata.Address, $"key {key}"); @@ -133,7 +133,7 @@ public override void ReadCompletionCallback(ref Key key, ref Value input, ref Ou public override void RMWCompletionCallback(ref Key key, ref Value input, ref Output output, Empty ctx, Status status, RecordMetadata recordMetadata) { - if (status == Status.OK) + if (status.Found) Assert.AreEqual(output.address, recordMetadata.Address); } } @@ -211,7 +211,7 @@ internal async Task Populate(bool useRMW, bool useAsync) : session.RMW(ref key, ref value, serialNo: lap) : session.Upsert(ref key, ref value, serialNo: lap); - if (status == Status.PENDING) + if (status.Pending) await session.CompletePendingAsync(); InsertAddresses[ii] = functions.lastWriteAddress; @@ -231,7 +231,7 @@ internal bool ProcessChainRecord(Status status, RecordMetadata recordMetadata, i Assert.GreaterOrEqual(lap, 0); long expectedValue = SetReadOutput(defaultKeyToScan, LapOffset(lap) + defaultKeyToScan); - Assert.AreEqual(status == Status.NOTFOUND, recordInfo.Tombstone, $"status({status}) == NOTFOUND != Tombstone ({recordInfo.Tombstone}) on lap {lap}"); + Assert.AreEqual(status.Found, !recordInfo.Tombstone, $"status({status}) == NOTFOUND != Tombstone ({recordInfo.Tombstone}) on lap {lap}"); Assert.AreEqual(lap == deleteLap, recordInfo.Tombstone, $"lap({lap}) == deleteLap({deleteLap}) != Tombstone ({recordInfo.Tombstone})"); if (!recordInfo.Tombstone) Assert.AreEqual(expectedValue, actualOutput.value, $"lap({lap})"); @@ -242,7 +242,7 @@ internal bool ProcessChainRecord(Status status, RecordMetadata recordMetadata, i internal static void ProcessNoKeyRecord(Status status, ref Output actualOutput, int keyOrdinal) { - if (status != Status.NOTFOUND) + if (status.Found) { var keyToScan = keyOrdinal % keyMod; var lap = keyOrdinal / keyMod; @@ -285,7 +285,7 @@ public void VersionedReadSyncTests(UseReadCache urc, CopyReadsToTail copyReadsTo { var status = session.Read(ref key, ref input, ref output, ref recordMetadata, session.functions.readFlags, serialNo: maxLap + 1); - if (status == Status.PENDING) + if (status.Pending) { // This will wait for each retrieved record; not recommended for performance-critical code or when retrieving multiple records unless necessary. session.CompletePendingWithOutputs(out var completedOutputs, wait: true); @@ -352,7 +352,7 @@ public void ReadAtAddressSyncTests(UseReadCache urc, CopyReadsToTail copyReadsTo var readAtAddress = recordMetadata.RecordInfo.PreviousAddress; var status = session.Read(ref key, ref input, ref output, ref recordMetadata, session.functions.readFlags, serialNo: maxLap + 1); - if (status == Status.PENDING) + if (status.Pending) { // This will wait for each retrieved record; not recommended for performance-critical code or when retrieving multiple records unless necessary. session.CompletePendingWithOutputs(out var completedOutputs, wait: true); @@ -368,7 +368,7 @@ public void ReadAtAddressSyncTests(UseReadCache urc, CopyReadsToTail copyReadsTo var saveRecordMetadata = recordMetadata; status = session.ReadAtAddress(readAtAddress, ref input, ref output, session.functions.readFlags, serialNo: maxLap + 1); - if (status == Status.PENDING) + if (status.Pending) { // This will wait for each retrieved record; not recommended for performance-critical code or when retrieving multiple records unless necessary. session.CompletePendingWithOutputs(out var completedOutputs, wait: true); @@ -538,7 +538,7 @@ public void ReadNoKeySyncTests(UseReadCache urc, CopyReadsToTail copyReadsToTail var keyOrdinal = rng.Next(numKeys); var status = session.ReadAtAddress(testStore.InsertAddresses[keyOrdinal], ref input, ref output, session.functions.readFlags, serialNo: maxLap + 1); - if (status == Status.PENDING) + if (status.Pending) { // This will wait for each retrieved record; not recommended for performance-critical code or when retrieving multiple records unless necessary. session.CompletePendingWithOutputs(out var completedOutputs, wait: true); @@ -651,27 +651,27 @@ async ValueTask ReadMin(long key, Status expectedStatus) { RecordMetadata recordMetadata = new(new RecordInfo { PreviousAddress = minAddress }); status = session.Read(ref key, ref input, ref output, ref recordMetadata, ReadFlags.MinAddress); - if (status == Status.PENDING) + if (status.Pending) { Assert.IsTrue(session.CompletePendingWithOutputs(out var completedOutputs, wait: true)); (status, output) = TestUtils.GetSinglePendingResult(completedOutputs); } } Assert.AreEqual(expectedStatus, status); - if (status != Status.NOTFOUND) + if (status.Found) Assert.AreEqual(output, makeValue(key)); } async ValueTask RunTests() { // First read at the pivot, to verify that and make sure the rest of the test works - await ReadMin(pivotKey, Status.OK); + await ReadMin(pivotKey, new(StatusCode.OK)); // Read a Key that is below the min address - await ReadMin(pivotKey - 1, Status.NOTFOUND); + await ReadMin(pivotKey - 1, new(StatusCode.NotFound)); // Read a Key that is above the min address - await ReadMin(pivotKey + 1, Status.OK); + await ReadMin(pivotKey + 1, new(StatusCode.OK)); } await RunTests(); diff --git a/cs/test/ReadCacheChainTests.cs b/cs/test/ReadCacheChainTests.cs index 3072e85ac..deb5270c3 100644 --- a/cs/test/ReadCacheChainTests.cs +++ b/cs/test/ReadCacheChainTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using static FASTER.test.TestUtils; namespace FASTER.test.ReadCacheTests { @@ -45,9 +46,9 @@ internal class ChainComparer : IFasterEqualityComparer [SetUp] public void Setup() { - TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); + DeleteDirectory(MethodTestDir, wait: true); var readCacheSettings = new ReadCacheSettings { MemorySizeBits = 15, PageSizeBits = 9 }; - log = Devices.CreateLogDevice(TestUtils.MethodTestDir + "/NativeReadCacheTests.log", deleteOnClose: true); + log = Devices.CreateLogDevice(MethodTestDir + "/NativeReadCacheTests.log", deleteOnClose: true); fht = new FasterKV (1L << 20, new LogSettings { LogDevice = log, MemorySizeBits = 15, PageSizeBits = 10, ReadCacheSettings = readCacheSettings }, comparer: new ChainComparer(mod)); @@ -60,19 +61,22 @@ public void TearDown() fht = null; log?.Dispose(); log = null; - TestUtils.DeleteDirectory(TestUtils.MethodTestDir); + DeleteDirectory(MethodTestDir); } - void PopulateAndEvict(bool immutable = false) + public enum RecordRegion { Immutable, OnDisk, Mutable }; + + void PopulateAndEvict(RecordRegion recordRegion = RecordRegion.OnDisk) { using var session = fht.NewSession(new SimpleFunctions()); - if (!immutable) + if (recordRegion != RecordRegion.Immutable) { for (int key = 0; key < numKeys; key++) session.Upsert(key, key + valueAdd); session.CompletePending(true); - fht.Log.FlushAndEvict(true); + if (recordRegion == RecordRegion.OnDisk) + fht.Log.FlushAndEvict(true); return; } @@ -88,17 +92,25 @@ void PopulateAndEvict(bool immutable = false) fht.Log.ShiftReadOnlyAddress(fht.Log.TailAddress, wait: true); } - void CreateChain(bool immutable = false) + void CreateChain(RecordRegion recordRegion = RecordRegion.OnDisk) { using var session = fht.NewSession(new SimpleFunctions()); + int output = -1; + bool expectPending(int key) => recordRegion == RecordRegion.OnDisk || (recordRegion == RecordRegion.Immutable && key < immutableSplitKey); // Pass1: PENDING reads and populate the cache for (var ii = 0; ii < chainLen; ++ii) { var key = lowChainKey + ii * mod; var status = session.Read(key, out _); - Assert.AreEqual((immutable && key >= immutableSplitKey) ? Status.OK : Status.PENDING, status); - session.CompletePending(wait: true); + if (expectPending(key)) + { + Assert.IsTrue(status.Pending, status.ToString()); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); + Assert.IsTrue(status.CopiedRecordToReadCache, status.ToString()); + } + Assert.IsTrue(status.Found, status.ToString()); if (ii == 0) readCacheHighEvictionAddress = fht.ReadCache.TailAddress; } @@ -107,7 +119,7 @@ void CreateChain(bool immutable = false) for (var ii = 0; ii < chainLen; ++ii) { var status = session.Read(lowChainKey + ii * mod, out _); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsTrue(!status.Pending && status.Found, status.ToString()); } // Pass 3: Put in bunch of extra keys into the cache so when we FlushAndEvict we get all the ones of interest. @@ -116,7 +128,14 @@ void CreateChain(bool immutable = false) if ((key % mod) != 0) { var status = session.Read(key, out _); - Assert.AreEqual((immutable && key >= immutableSplitKey) ? Status.OK : Status.PENDING, status); + if (expectPending(key)) + { + Assert.IsTrue(status.Pending); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); + Assert.IsTrue(status.CopiedRecordToReadCache, status.ToString()); + } + Assert.IsTrue(status.Found, status.ToString()); session.CompletePending(wait: true); } } @@ -214,9 +233,9 @@ void VerifySplicedInKey(int expectedKey) } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void ChainVerificationTest() { PopulateAndEvict(); @@ -226,9 +245,9 @@ public void ChainVerificationTest() } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void DeleteCacheRecordTest() { PopulateAndEvict(); @@ -238,10 +257,10 @@ public void DeleteCacheRecordTest() void doTest(int key) { var status = session.Delete(key); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); status = session.Read(key, out var value); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found, status.ToString()); } doTest(lowChainKey); @@ -254,9 +273,9 @@ void doTest(int key) } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void DeleteAllCacheRecordsTest() { PopulateAndEvict(); @@ -266,10 +285,10 @@ public void DeleteAllCacheRecordsTest() void doTest(int key) { var status = session.Delete(key); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); status = session.Read(key, out var value); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found, status.ToString()); } // Delete all keys in the readcache chain. @@ -287,18 +306,18 @@ void doTest(int key) } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void UpsertCacheRecordTest() { DoUpdateTest(useRMW: false); } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void RMWCacheRecordTest() { DoUpdateTest(useRMW: true); @@ -313,23 +332,23 @@ void DoUpdateTest(bool useRMW) void doTest(int key) { var status = session.Read(key, out var value); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found, status.ToString()); if (useRMW) { // RMW will get the old value from disk, unlike Upsert status = session.RMW(key, value + valueAdd); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending, status.ToString()); session.CompletePending(wait: true); } else { status = session.Upsert(key, value + valueAdd); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.CreatedRecord, status.ToString()); } status = session.Read(key, out value); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found, status.ToString()); Assert.AreEqual(key + valueAdd * 2, value); } @@ -343,9 +362,9 @@ void doTest(int key) } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void SpliceInFromCTTTest() { PopulateAndEvict(); @@ -356,22 +375,20 @@ public void SpliceInFromCTTTest() RecordMetadata recordMetadata = default; var status = session.Read(ref key, ref input, ref output, ref recordMetadata, ReadFlags.CopyToTail); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending, status.ToString()); session.CompletePending(wait: true); VerifySplicedInKey(key); } - public enum RecordRegion { Immutable, OnDisk, NotFound }; - [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void SpliceInFromUpsertTest([Values] RecordRegion recordRegion) { - PopulateAndEvict(recordRegion == RecordRegion.Immutable); - CreateChain(recordRegion == RecordRegion.Immutable); + PopulateAndEvict(recordRegion); + CreateChain(recordRegion); using var session = fht.NewSession(new SimpleFunctions()); int key = -1; @@ -380,57 +397,72 @@ public void SpliceInFromUpsertTest([Values] RecordRegion recordRegion) { key = spliceInExistingKey; var status = session.Upsert(key, key + valueAdd); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); } else { key = spliceInNewKey; var status = session.Upsert(key, key + valueAdd); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); } VerifySplicedInKey(key); } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void SpliceInFromRMWTest([Values] RecordRegion recordRegion) { - PopulateAndEvict(recordRegion == RecordRegion.Immutable); - CreateChain(recordRegion == RecordRegion.Immutable); + PopulateAndEvict(recordRegion); + CreateChain(recordRegion); using var session = fht.NewSession(new SimpleFunctions()); - int key = -1; + int key = -1, output = -1; if (recordRegion == RecordRegion.Immutable || recordRegion == RecordRegion.OnDisk) { + // Existing key key = spliceInExistingKey; var status = session.RMW(key, key + valueAdd); - Assert.AreEqual(recordRegion == RecordRegion.OnDisk ? Status.PENDING : Status.OK, status); - session.CompletePending(wait: true); + if (recordRegion == RecordRegion.OnDisk) + { + Assert.IsTrue(status.Pending, status.ToString()); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); + } + Assert.IsTrue(status.Found && status.CopyUpdatedRecord, status.ToString()); + + { // New key + key = spliceInNewKey; + status = session.RMW(key, key + valueAdd); + + // This NOTFOUND key will return PENDING because we have to trace back through the collisions. + Assert.IsTrue(status.Pending, status.ToString()); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); + } } else { key = spliceInNewKey; var status = session.RMW(key, key + valueAdd); - // This NOTFOUND key will return PENDING because we have to trace back through the collisions. - Assert.AreEqual(Status.PENDING, status); - session.CompletePending(wait: true); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); } VerifySplicedInKey(key); } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void SpliceInFromDeleteTest([Values] RecordRegion recordRegion) { - PopulateAndEvict(recordRegion == RecordRegion.Immutable); - CreateChain(recordRegion == RecordRegion.Immutable); + PopulateAndEvict(recordRegion); + CreateChain(recordRegion); using var session = fht.NewSession(new SimpleFunctions()); int key = -1; @@ -439,22 +471,22 @@ public void SpliceInFromDeleteTest([Values] RecordRegion recordRegion) { key = spliceInExistingKey; var status = session.Delete(key); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); } else { key = spliceInNewKey; var status = session.Delete(key); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); } VerifySplicedInKey(key); } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void EvictFromReadCacheToLockTableTest() { PopulateAndEvict(); @@ -496,9 +528,9 @@ public void EvictFromReadCacheToLockTableTest() } [Test] - [Category(TestUtils.FasterKVTestCategory)] - [Category(TestUtils.ReadCacheTestCategory)] - [Category(TestUtils.SmokeTestCategory)] + [Category(FasterKVTestCategory)] + [Category(ReadCacheTestCategory)] + [Category(SmokeTestCategory)] public void TransferFromLockTableToReadCacheTest() { PopulateAndEvict(); @@ -542,7 +574,7 @@ public void TransferFromLockTableToReadCacheTest() foreach (var key in locks.Keys) { var status = session.Read(key, out _); - Assert.AreEqual(Status.PENDING, status); + Assert.IsTrue(status.Pending, status.ToString()); session.CompletePending(wait: true); var lockType = locks[key]; diff --git a/cs/test/RecoverContinueTests.cs b/cs/test/RecoverContinueTests.cs index 5d3431b78..878121ad5 100644 --- a/cs/test/RecoverContinueTests.cs +++ b/cs/test/RecoverContinueTests.cs @@ -133,7 +133,7 @@ private void CheckAllValues( inputArg.adId.adId = key; var status = fht.Read(ref inputArg.adId, ref inputArg, ref outputArg, Empty.Default, fht.SerialNo); - if (status == Status.PENDING) + if (status.Pending) fht.CompletePending(true); else { @@ -165,7 +165,7 @@ public class AdSimpleFunctions : FunctionsBase { public override void ReadCompletionCallback(ref long key, ref long input, ref long output, Empty ctx, Status status, RecordMetadata recordMetadata) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -62,7 +62,7 @@ public override void ReadCompletionCallback(ref long key, ref long input, ref lo internal static void Verify(Status status, long key, long output) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); if (key < 950) Assert.AreEqual(key, output); else @@ -106,9 +106,9 @@ public async ValueTask RecoveryCheck1([Values] CheckpointType checkpointType, [V { long output = default; var status = s1.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -143,9 +143,9 @@ public async ValueTask RecoveryCheck1([Values] CheckpointType checkpointType, [V { long output = default; var status = s2.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -195,9 +195,9 @@ public async ValueTask RecoveryCheck2([Values] CheckpointType checkpointType, [V { long output = default; var status = s1.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -226,9 +226,9 @@ public async ValueTask RecoveryCheck2([Values] CheckpointType checkpointType, [V { long output = default; var status = s2.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -278,9 +278,9 @@ public void RecoveryCheck2Repeated([Values] CheckpointType checkpointType) { long output = default; var status = s2.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -330,9 +330,9 @@ public async ValueTask RecoveryCheck3([Values] CheckpointType checkpointType, [V { long output = default; var status = s1.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -361,9 +361,9 @@ public async ValueTask RecoveryCheck3([Values] CheckpointType checkpointType, [V { long output = default; var status = s2.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -414,9 +414,9 @@ public async ValueTask RecoveryCheck4([Values] CheckpointType checkpointType, [V { long output = default; var status = s1.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -448,9 +448,9 @@ public async ValueTask RecoveryCheck4([Values] CheckpointType checkpointType, [V { long output = default; var status = s2.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -493,9 +493,9 @@ public async ValueTask RecoveryCheck5([Values] CheckpointType checkpointType, [V { long output = default; var status = s1.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -508,9 +508,9 @@ public async ValueTask RecoveryCheck5([Values] CheckpointType checkpointType, [V { long output = default; var status = s1.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -544,9 +544,9 @@ public async ValueTask RecoveryCheck5([Values] CheckpointType checkpointType, [V { long output = default; var status = s2.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { - Assert.AreEqual(Status.OK, status, $"status = {status}"); + Assert.IsTrue(status.Found, $"status = {status}"); Assert.AreEqual(key, output, $"output = {output}"); } } @@ -646,7 +646,7 @@ private async ValueTask IncrSnapshotRecoveryCheck(ICheckpointManager checkpointM { long output = default; var status = s2.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { MyFunctions2.Verify(status, key, output); } @@ -668,7 +668,7 @@ private async ValueTask IncrSnapshotRecoveryCheck(ICheckpointManager checkpointM { long output = default; var status = s3.Read(ref key, ref output); - if (status != Status.PENDING) + if (!status.Pending) { MyFunctions2.Verify(status, key, output); } diff --git a/cs/test/RecoveryTests.cs b/cs/test/RecoveryTests.cs index 3a5a6c6b9..fdbf6cb75 100644 --- a/cs/test/RecoveryTests.cs +++ b/cs/test/RecoveryTests.cs @@ -188,7 +188,7 @@ private async ValueTask RecoverAndTestAsync(int tokenIndex, bool isAsync) for (var i = 0; i < numUniqueKeys; i++) { var status = session.Read(ref inputArray[i].adId, ref input, ref output, Empty.Default, i); - Assert.AreEqual(Status.OK, status, $"At tokenIndex {tokenIndex}, keyIndex {i}, AdId {inputArray[i].adId.adId}"); + Assert.IsTrue(status.Found, $"At tokenIndex {tokenIndex}, keyIndex {i}, AdId {inputArray[i].adId.adId}"); inputArray[i].numClicks = output.value; } @@ -467,7 +467,7 @@ private static void Read(FasterKV fht) for (var i = 0; i < DeviceTypeRecoveryTests.numUniqueKeys; i++) { var status = session.Read(i % DeviceTypeRecoveryTests.numUniqueKeys, default, out long output); - Assert.AreEqual(Status.OK, status, $"keyIndex {i}"); + Assert.IsTrue(status.Found, $"keyIndex {i}"); Assert.AreEqual(ExpectedValue(i), output); } } @@ -493,7 +493,7 @@ private static void Read(FasterKV fht) int[] output = null; var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(len, output[0], "Length"); Assert.AreEqual(ExpectedValue(i), output[1], "field1"); for (int j = 2; j < len; j++) @@ -515,7 +515,7 @@ private static void Read(FasterKV fht) { var key = new MyValue { value = i }; var status = session.Read(key, default, out MyOutput output); - Assert.AreEqual(Status.OK, status, $"keyIndex {i}"); + Assert.IsTrue(status.Found, $"keyIndex {i}"); Assert.AreEqual(ExpectedValue(i), output.value.value); } } diff --git a/cs/test/ReproReadCacheTest.cs b/cs/test/ReproReadCacheTest.cs index 4515e23ba..1aa07582a 100644 --- a/cs/test/ReproReadCacheTest.cs +++ b/cs/test/ReproReadCacheTest.cs @@ -32,7 +32,7 @@ public override bool SingleReader(ref SpanByte key, ref long input, ref long val public override void ReadCompletionCallback(ref SpanByte key, ref long input, ref long output, Context context, Status status, RecordMetadata recordMetadata) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(input, output); context.Status = status; } @@ -75,13 +75,13 @@ void Read(int i) long output = 0; var status = session.Read(ref sb, ref input, ref output, context); - if (status == Status.OK) + if (status.Found) { Assert.AreEqual(input, output); return; } - Assert.AreEqual(Status.PENDING, status, $"was not OK or PENDING: {keyString}"); + Assert.IsTrue(status.Pending, $"was not OK or PENDING: {keyString}"); session.CompletePending(wait: true); } @@ -97,7 +97,8 @@ void Read(int i) fixed (byte* _ = key) { var sb = SpanByte.FromFixedSpan(key); - Assert.AreEqual(Status.OK, session.Upsert(sb, i * 2)); + var status = session.Upsert(sb, i * 2); + Assert.IsTrue(!status.Found && status.CreatedRecord, status.ToString()); } } diff --git a/cs/test/SessionFASTERTests.cs b/cs/test/SessionFASTERTests.cs index 74c55d7c2..6310a5ddb 100644 --- a/cs/test/SessionFASTERTests.cs +++ b/cs/test/SessionFASTERTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test.async { @@ -16,8 +17,8 @@ internal class SessionFASTERTests [SetUp] public void Setup() { - TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - log = Devices.CreateLogDevice(TestUtils.MethodTestDir + "/hlog1.log", deleteOnClose: true); + DeleteDirectory(MethodTestDir, wait: true); + log = Devices.CreateLogDevice(MethodTestDir + "/hlog1.log", deleteOnClose: true); fht = new FasterKV (128, new LogSettings { LogDevice = log, MemorySizeBits = 29 }); } @@ -29,7 +30,7 @@ public void TearDown() fht = null; log?.Dispose(); log = null; - TestUtils.DeleteDirectory(TestUtils.MethodTestDir); + DeleteDirectory(MethodTestDir); } [Test] @@ -47,15 +48,13 @@ public void SessionTest1() session.Upsert(ref key1, ref value, Empty.Default, 0); var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - session.CompletePending(true); - } - else - { - Assert.AreEqual(Status.OK, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); } @@ -79,29 +78,25 @@ public void SessionTest2() var status = session1.Read(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - session1.CompletePending(true); - } - else - { - Assert.AreEqual(Status.OK, status); + session1.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value1.vfield1, output.value.vfield1); Assert.AreEqual(value1.vfield2, output.value.vfield2); status = session2.Read(ref key2, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - session2.CompletePending(true); - } - else - { - Assert.AreEqual(Status.OK, status); + session2.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value2.vfield1, output.value.vfield1); Assert.AreEqual(value2.vfield2, output.value.vfield2); } @@ -122,15 +117,13 @@ public void SessionTest3() session.Upsert(ref key1, ref value, Empty.Default, 0); var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) - { - session.CompletePending(true); - } - else + if (status.Pending) { - Assert.AreEqual(Status.OK, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); }).Wait(); @@ -153,15 +146,13 @@ public void SessionTest4() session1.Upsert(ref key1, ref value1, Empty.Default, 0); var status = session1.Read(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) - { - session1.CompletePending(true); - } - else + if (status.Pending) { - Assert.AreEqual(Status.OK, status); + session1.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value1.vfield1, output.value.vfield1); Assert.AreEqual(value1.vfield2, output.value.vfield2); }); @@ -178,15 +169,13 @@ public void SessionTest4() var status = session2.Read(ref key2, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) - { - session2.CompletePending(true); - } - else + if (status.Pending) { - Assert.AreEqual(Status.OK, status); + session2.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value2.vfield1, output.value.vfield1); Assert.AreEqual(value2.vfield2, output.value.vfield2); }); @@ -210,15 +199,13 @@ public void SessionTest5() session.Upsert(ref key1, ref value1, Empty.Default, 0); var status = session.Read(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) - { - session.CompletePending(true); - } - else + if (status.Pending) { - Assert.AreEqual(Status.OK, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value1.vfield1, output.value.vfield1); Assert.AreEqual(value1.vfield2, output.value.vfield2); @@ -233,26 +220,22 @@ public void SessionTest5() status = session.Read(ref key2, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - session.CompletePending(true); - } - else - { - Assert.AreEqual(Status.OK, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); status = session.Read(ref key2, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) - { - session.CompletePending(true); - } - else + if (status.Pending) { - Assert.AreEqual(Status.OK, status); + session.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } + Assert.IsTrue(status.Found); Assert.AreEqual(value2.vfield1, output.value.vfield1); Assert.AreEqual(value2.vfield2, output.value.vfield2); diff --git a/cs/test/SharedDirectoryTests.cs b/cs/test/SharedDirectoryTests.cs index e36925eab..90dbeb0fb 100644 --- a/cs/test/SharedDirectoryTests.cs +++ b/cs/test/SharedDirectoryTests.cs @@ -209,14 +209,13 @@ private void Test(FasterTestInstance fasterInstance, Guid checkpointToken) for (var i = 0; i < numUniqueKeys; i++) { var status = session.Read(ref inputArray[i].adId, ref input, ref output, Empty.Default, i); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); inputArray[i].numClicks = output.value; } // Complete all pending requests session.CompletePending(true); - // Compute expected array long[] expected = new long[numUniqueKeys]; foreach (var guid in checkpointInfo.continueTokens.Keys) diff --git a/cs/test/SimpleAsyncTests.cs b/cs/test/SimpleAsyncTests.cs index 625a22e2b..26d163bf3 100644 --- a/cs/test/SimpleAsyncTests.cs +++ b/cs/test/SimpleAsyncTests.cs @@ -57,14 +57,14 @@ public async Task ReadAsyncMinParamTest() for (long key = 0; key < numOps; key++) { var r = await s1.UpsertAsync(ref key, ref key); - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); // test async version of Upsert completion } for (long key = 0; key < numOps; key++) { var (status, output) = (await s1.ReadAsync(ref key)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } } @@ -86,7 +86,7 @@ public async Task ReadAsyncMinParamTestNoDefaultTest() for (long key = 0; key < numOps; key++) { var (status, output) = (await s1.ReadAsync(ref key, Empty.Default, 99, cancellationToken)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } } @@ -107,7 +107,7 @@ public async Task ReadAsyncNoRefKeyTest() for (long key = 0; key < numOps; key++) { var (status, output) = (await s1.ReadAsync(key,Empty.Default, 99)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } } @@ -129,7 +129,7 @@ public async Task ReadAsyncRefKeyRefInputTest() for (key = 0; key < numOps; key++) { (status, output) = (await s1.ReadAsync(ref key, ref output)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } @@ -142,7 +142,7 @@ public async Task ReadAsyncRefKeyRefInputTest() (await t2).Complete(); // should trigger RMW re-do (status, output) = (await s1.ReadAsync(ref key, ref output)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key + input + input, output); } @@ -159,14 +159,14 @@ public async Task ReadAsyncNoRefKeyNoRefInputTest() for (key = 0; key < numOps; key++) { (status, output) = (await s1.RMWAsync(ref key, ref key, Empty.Default)).Complete(); - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending); Assert.AreEqual(key, output); } for (key = 0; key < numOps; key++) { (status, output) = (await s1.ReadAsync(key, output)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } @@ -179,7 +179,7 @@ public async Task ReadAsyncNoRefKeyNoRefInputTest() (await t2).Complete(); // should trigger RMW re-do (status, output) = (await s1.ReadAsync(key, output,Empty.Default, 129)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key + input + input, output); } @@ -193,7 +193,7 @@ public async Task UpsertReadDeleteReadAsyncMinParamByRefTest() for (long key = 0; key < numOps; key++) { var r = await s1.UpsertAsync(ref key, ref key); - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); // test async version of Upsert completion } @@ -202,18 +202,18 @@ public async Task UpsertReadDeleteReadAsyncMinParamByRefTest() for (long key = 0; key < numOps; key++) { var (status, output) = (await s1.ReadAsync(ref key)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } { // Scope for variables long deleteKey = 99; var r = await s1.DeleteAsync(ref deleteKey); - while (r.Status == Status.PENDING) + while (r.Status.Pending) r = await r.CompleteAsync(); // test async version of Delete completion var (status, _) = (await s1.ReadAsync(ref deleteKey)).Complete(); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found); } } @@ -227,7 +227,7 @@ public async Task UpsertReadDeleteReadAsyncMinParamByValueTest() for (long key = 0; key < numOps; key++) { var status = (await s1.UpsertAsync(key, key)).Complete(); // test sync version of Upsert completion - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending); } Assert.Greater(numOps, 100); @@ -235,17 +235,17 @@ public async Task UpsertReadDeleteReadAsyncMinParamByValueTest() for (long key = 0; key < numOps; key++) { var (status, output) = (await s1.ReadAsync(key)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } { // Scope for variables long deleteKey = 99; var status = (await s1.DeleteAsync(deleteKey)).Complete(); // test sync version of Delete completion - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending); (status, _) = (await s1.ReadAsync(deleteKey)).Complete(); - Assert.AreEqual(Status.NOTFOUND, status); + Assert.IsFalse(status.Found); } } @@ -270,14 +270,14 @@ public async Task AsyncStartAddressParamTest() // subtract after the insert to get record start address. (status, output) = (await s1.RMWAsync(ref key, ref key)).Complete(); addresses[key] = fht1.Log.TailAddress - recordSize; - Assert.AreNotEqual(Status.PENDING, status); + Assert.IsFalse(status.Pending); Assert.AreEqual(key, output); } for (key = 0; key < numOps; key++) { (status, output) = (await s1.ReadAsync(ref key, ref output, addresses[key], ReadFlags.None)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } @@ -294,7 +294,7 @@ public async Task AsyncStartAddressParamTest() addresses[key] = fht1.Log.TailAddress - recordSize; (status, output) = (await s1.ReadAsync(ref key, ref output, addresses[key], ReadFlags.None, Empty.Default, 129)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key + input + input, output); } @@ -310,14 +310,14 @@ public async Task ReadAsyncRMWAsyncNoRefTest() for (key = 0; key < numOps; key++) { var asyncResult = await (await s1.RMWAsync(key, key)).CompleteAsync(); - Assert.AreNotEqual(Status.PENDING, asyncResult.Status); + Assert.IsFalse(asyncResult.Status.Pending); Assert.AreEqual(key, asyncResult.Output); } for (key = 0; key < numOps; key++) { (status, output) = (await s1.ReadAsync(ref key, ref output)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } @@ -330,7 +330,7 @@ public async Task ReadAsyncRMWAsyncNoRefTest() (await t2).Complete(); // should trigger RMW re-do (status, output) = (await s1.ReadAsync(ref key, ref output)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key + input + input, output); } @@ -354,7 +354,7 @@ public async Task ReadyToCompletePendingAsyncTest() for (key = 0; key < numOps; key++) { (status, output) = (await s1.ReadAsync(ref key, ref output)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key, output); } @@ -367,7 +367,7 @@ public async Task ReadyToCompletePendingAsyncTest() (await t2).Complete(); // should trigger RMW re-do (status, output) = (await s1.ReadAsync(ref key, ref output)).Complete(); - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); Assert.AreEqual(key + input + input, output); } @@ -384,7 +384,7 @@ async ValueTask completeRmw(FasterKV.RmwAsyncResult.UpsertAsyncResult : FunctionsBase first, Action second, Action verification, int randSleepRangeMs = -1) + internal async static ValueTask DoTwoThreadRandomKeyTest(int count, Action first, Action second, Action verification) { - Thread[] threads = new Thread[2]; + Task[] tasks = new Task[2]; var rng = new Random(101); for (var iter = 0; iter < count; ++iter) { var arg = rng.Next(count); - threads[0] = new Thread(() => first(arg)); - threads[1] = new Thread(() => second(arg)); + tasks[0] = Task.Factory.StartNew(() => first(arg)); + tasks[1] = Task.Factory.StartNew(() => second(arg)); - var doSleep = randSleepRangeMs >= 0; - for (int t = 0; t < threads.Length; t++) - { - if (doSleep) - { - if (randSleepRangeMs > 0) - Thread.Sleep(rng.Next(10)); - else - Thread.Yield(); - } - threads[t].Start(); - } - for (int t = 0; t < threads.Length; t++) - threads[t].Join(); + await Task.WhenAll(tasks); verification(arg); } diff --git a/cs/test/UnsafeContextTests.cs b/cs/test/UnsafeContextTests.cs index 96ddaef4a..abc927ed7 100644 --- a/cs/test/UnsafeContextTests.cs +++ b/cs/test/UnsafeContextTests.cs @@ -56,7 +56,7 @@ public void TearDown() private void AssertCompleted(Status expected, Status actual) { - if (actual == Status.PENDING) + if (actual.Pending) (actual, _) = CompletePendingResult(); Assert.AreEqual(expected, actual); } @@ -86,7 +86,7 @@ public void NativeInMemWriteRead([Values] TestUtils.DeviceType deviceType) uContext.Upsert(ref key1, ref value, Empty.Default, 0); var status = uContext.Read(ref key1, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.OK, status); + AssertCompleted(new (StatusCode.OK), status); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); } @@ -114,12 +114,12 @@ public void NativeInMemWriteReadDelete([Values] TestUtils.DeviceType deviceType) uContext.Upsert(ref key1, ref value, Empty.Default, 0); var status = uContext.Read(ref key1, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); uContext.Delete(ref key1, Empty.Default, 0); status = uContext.Read(ref key1, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.NOTFOUND, status); + AssertCompleted(new(StatusCode.NotFound), status); var key2 = new KeyStruct { kfield1 = 14, kfield2 = 15 }; var value2 = new ValueStruct { vfield1 = 24, vfield2 = 25 }; @@ -127,7 +127,7 @@ public void NativeInMemWriteReadDelete([Values] TestUtils.DeviceType deviceType) uContext.Upsert(ref key2, ref value2, Empty.Default, 0); status = uContext.Read(ref key2, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); Assert.AreEqual(value2.vfield1, output.value.vfield1); Assert.AreEqual(value2.vfield2, output.value.vfield2); } @@ -177,7 +177,7 @@ public void NativeInMemWriteReadDelete2() var value = new ValueStruct { vfield1 = i, vfield2 = 24 }; var status = uContext.Read(ref key1, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.NOTFOUND, status); + AssertCompleted(new(StatusCode.NotFound), status); uContext.Upsert(ref key1, ref value, Empty.Default, 0); } @@ -186,7 +186,7 @@ public void NativeInMemWriteReadDelete2() { var key1 = new KeyStruct { kfield1 = i, kfield2 = 14 }; var status = uContext.Read(ref key1, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); } } finally @@ -213,7 +213,7 @@ public unsafe void NativeInMemWriteRead2() { InputStruct input = default; - Random r = new Random(10); + Random r = new(10); for (int c = 0; c < count; c++) { var i = r.Next(10000); @@ -231,7 +231,7 @@ public unsafe void NativeInMemWriteRead2() var key1 = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; - if (uContext.Read(ref key1, ref input, ref output, Empty.Default, 0) == Status.PENDING) + if (uContext.Read(ref key1, ref input, ref output, Empty.Default, 0).Pending) { uContext.CompletePending(true); } @@ -249,7 +249,7 @@ public unsafe void NativeInMemWriteRead2() var i = r.Next(10000); OutputStruct output = default; var key1 = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; - Assert.AreEqual(Status.NOTFOUND, uContext.Read(ref key1, ref input, ref output, Empty.Default, 0)); + Assert.IsFalse(uContext.Read(ref key1, ref input, ref output, Empty.Default, 0).Found); } } finally @@ -268,7 +268,7 @@ public unsafe void TestShiftHeadAddress([Values] TestUtils.DeviceType deviceType const int RandRange = 10000; const int NumRecs = 200; - Random r = new Random(RandSeed); + Random r = new(RandSeed); var sw = Stopwatch.StartNew(); Setup(128, new LogSettings { MemorySizeBits = 22, SegmentSizeBits = 22, PageSizeBits = 10 }, deviceType); @@ -294,7 +294,7 @@ public unsafe void TestShiftHeadAddress([Values] TestUtils.DeviceType deviceType var key1 = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; var value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; - if (uContext.Read(ref key1, ref input, ref output, Empty.Default, 0) != Status.PENDING) + if (!uContext.Read(ref key1, ref input, ref output, Empty.Default, 0).Pending) { Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); @@ -314,7 +314,7 @@ public unsafe void TestShiftHeadAddress([Values] TestUtils.DeviceType deviceType OutputStruct output = default; var key1 = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; Status foundStatus = uContext.Read(ref key1, ref input, ref output, Empty.Default, 0); - Assert.AreEqual(Status.PENDING, foundStatus); + Assert.IsTrue(foundStatus.Pending); } uContext.CompletePendingWithOutputs(out var outputs, wait: true); @@ -369,7 +369,7 @@ public unsafe void NativeInMemRMWRefKeys([Values] TestUtils.DeviceType deviceTyp var i = nums[j]; var key1 = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; input = new InputStruct { ifield1 = i, ifield2 = i + 1 }; - if (uContext.RMW(ref key1, ref input, ref output, Empty.Default, 0) == Status.PENDING) + if (uContext.RMW(ref key1, ref input, ref output, Empty.Default, 0).Pending) { uContext.CompletePending(true); } @@ -388,18 +388,18 @@ public unsafe void NativeInMemRMWRefKeys([Values] TestUtils.DeviceType deviceTyp var i = nums[j]; key = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; - ValueStruct value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; + ValueStruct value = new() { vfield1 = i, vfield2 = i + 1 }; status = uContext.Read(ref key, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); Assert.AreEqual(2 * value.vfield1, output.value.vfield1); Assert.AreEqual(2 * value.vfield2, output.value.vfield2); } key = new KeyStruct { kfield1 = nums.Length, kfield2 = nums.Length + 1 }; status = uContext.Read(ref key, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.NOTFOUND, status); + AssertCompleted(new(StatusCode.NotFound), status); } finally { @@ -453,18 +453,18 @@ public unsafe void NativeInMemRMWNoRefKeys([Values] TestUtils.DeviceType deviceT var i = nums[j]; key = new KeyStruct { kfield1 = i, kfield2 = i + 1 }; - ValueStruct value = new ValueStruct { vfield1 = i, vfield2 = i + 1 }; + ValueStruct value = new() { vfield1 = i, vfield2 = i + 1 }; status = uContext.Read(ref key, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); Assert.AreEqual(2 * value.vfield1, output.value.vfield1); Assert.AreEqual(2 * value.vfield2, output.value.vfield2); } key = new KeyStruct { kfield1 = nums.Length, kfield2 = nums.Length + 1 }; status = uContext.Read(ref key, ref input, ref output, Empty.Default, 0); - AssertCompleted(Status.NOTFOUND, status); + AssertCompleted(new(StatusCode.NotFound), status); } finally { @@ -490,7 +490,7 @@ public void ReadNoRefKeyInputOutput([Values] TestUtils.DeviceType deviceType) uContext.Upsert(ref key1, ref value, Empty.Default, 0); var status = uContext.Read(key1, input, out OutputStruct output, Empty.Default, 111); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); // Verify the read data Assert.AreEqual(value.vfield1, output.value.vfield1); @@ -519,7 +519,7 @@ public void ReadNoRefKey([Values] TestUtils.DeviceType deviceType) uContext.Upsert(ref key1, ref value, Empty.Default, 0); var status = uContext.Read(key1, out OutputStruct output, Empty.Default, 1); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); // Verify the read data Assert.AreEqual(value.vfield1, output.value.vfield1); @@ -552,7 +552,7 @@ public void ReadWithoutInput([Values] TestUtils.DeviceType deviceType) uContext.Upsert(ref key1, ref value, Empty.Default, 0); var status = uContext.Read(ref key1, ref output, Empty.Default, 99); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); // Verify the read data Assert.AreEqual(value.vfield1, output.value.vfield1); @@ -588,7 +588,7 @@ public void ReadWithoutSerialID() uContext.Upsert(ref key1, ref value, Empty.Default, 0); var status = uContext.Read(ref key1, ref input, ref output, Empty.Default); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); @@ -618,7 +618,7 @@ public void ReadBareMinParams([Values] TestUtils.DeviceType deviceType) uContext.Upsert(ref key1, ref value, Empty.Default, 0); var (status, output) = uContext.Read(key1); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); @@ -654,7 +654,7 @@ public void ReadAtAddressReadFlagsNone() uContext.Upsert(ref key1, ref value, Empty.Default, 0); var status = uContext.ReadAtAddress(readAtAddress, ref input, ref output, ReadFlags.None, Empty.Default, 0); - AssertCompleted(Status.OK, status); + AssertCompleted(new(StatusCode.OK), status); Assert.AreEqual(value.vfield1, output.value.vfield1); Assert.AreEqual(value.vfield2, output.value.vfield2); diff --git a/cs/test/VLTestTypes.cs b/cs/test/VLTestTypes.cs index 62d3e2c7e..6c68caa13 100644 --- a/cs/test/VLTestTypes.cs +++ b/cs/test/VLTestTypes.cs @@ -95,12 +95,13 @@ public class VLFunctions : FunctionsBase { public override void RMWCompletionCallback(ref Key key, ref Input input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); + Assert.IsTrue(status.CopyUpdatedRecord); } public override void ReadCompletionCallback(ref Key key, ref Input input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); for (int i = 0; i < output.Length; i++) { Assert.AreEqual(output.Length, output[i]); @@ -140,12 +141,13 @@ public class VLFunctions2 : FunctionsBase { public override void RMWCompletionCallback(ref VLValue key, ref Input input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); + Assert.IsTrue(status.CopyUpdatedRecord); } public override void ReadCompletionCallback(ref VLValue key, ref Input input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) { - Assert.AreEqual(Status.OK, status); + Assert.IsTrue(status.Found); for (int i = 0; i < output.Length; i++) { Assert.AreEqual(output.Length, output[i]); diff --git a/cs/test/VariableLengthStructFASTERTests.cs b/cs/test/VariableLengthStructFASTERTests.cs index e51f3ec70..838825cf2 100644 --- a/cs/test/VariableLengthStructFASTERTests.cs +++ b/cs/test/VariableLengthStructFASTERTests.cs @@ -4,6 +4,7 @@ using System; using FASTER.core; using NUnit.Framework; +using static FASTER.test.TestUtils; namespace FASTER.test { @@ -58,18 +59,17 @@ public unsafe void VariableLengthTest1() int[] output = null; var status = s.Read(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - s.CompletePending(true); + s.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } - else + + Assert.IsTrue(status.Found); + Assert.AreEqual(len, output.Length); + for (int j = 0; j < len; j++) { - Assert.AreEqual(Status.OK, status); - Assert.AreEqual(len, output.Length); - for (int j = 0; j < len; j++) - { - Assert.AreEqual(len, output[j]); - } + Assert.AreEqual(len, output[j]); } } s.Dispose(); @@ -129,18 +129,17 @@ public unsafe void VariableLengthTest2() int[] output = null; var status = s.Read(ref key1, ref input, ref output, Empty.Default, 0); - if (status == Status.PENDING) + if (status.Pending) { - s.CompletePending(true); + s.CompletePendingWithOutputs(out var outputs, wait: true); + (status, output) = GetSinglePendingResult(outputs); } - else + + Assert.IsTrue(status.Found); + Assert.AreEqual(len, output.Length); + for (int j = 0; j < len; j++) { - Assert.AreEqual(Status.OK, status); - Assert.AreEqual(len, output.Length); - for (int j = 0; j < len; j++) - { - Assert.AreEqual(len, output[j]); - } + Assert.AreEqual(len, output[j]); } } diff --git a/docs/_docs/20-fasterkv-basics.md b/docs/_docs/20-fasterkv-basics.md index 8692aa089..9a6961351 100644 --- a/docs/_docs/20-fasterkv-basics.md +++ b/docs/_docs/20-fasterkv-basics.md @@ -148,7 +148,7 @@ var status = (await s1.UpsertAsync(ref key, ref value)).Complete(); // Fully async (completions may themselves need to go async) var r = await session.UpsertAsync(ref key, ref value); -while (r.Status == Status.PENDING) +while (r.Status.IsPending) r = await r.CompleteAsync(); ``` @@ -165,7 +165,7 @@ var status = (await session.RMWAsync(ref key, ref input)).Complete(); // Fully async (completion may rarely go async and require multiple iterations) var r = await session.RMWAsync(ref key, ref input); -while (r.Status == Status.PENDING) +while (r.Status.IsPending) r = await r.CompleteAsync(); Console.WriteLine(r.Output); ``` @@ -182,7 +182,7 @@ var status = (await s1.DeleteAsync(ref key)).Complete(); // Fully async var r = await session.DeleteAsync(ref key); -while (r.Status == Status.PENDING) +while (r.Status.IsPending) r = await r.CompleteAsync(); ```