private async Task RunMultiClientTest(IFdbDatabase db, FdbDirectorySubspaceLocation location, string desc, int K, int NUM, CancellationToken ct)
        {
            Log($"Starting {desc} test with {K} threads and {NUM} iterations");

            await CleanLocation(db, location);

            var queue = new FdbQueue <string>(location);

            // use a CTS to ensure that everything will stop in case of problems...
            using (var go = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
            {
                var tok = go.Token;

                var pushLock = new AsyncCancelableMutex(tok);
                var popLock  = new AsyncCancelableMutex(tok);

                int pushCount = 0;
                int popCount  = 0;
                int stalls    = 0;

                var start = DateTime.UtcNow;

                var pushTreads = Enumerable.Range(0, K)
                                 .Select(async id =>
                {
                    int i = 0;
                    try
                    {
                        // wait for the signal
                        await pushLock.Task.ConfigureAwait(false);

                        var res = new List <string>(NUM);

                        for (; i < NUM; i++)
                        {
                            var item = $"{id}.{i}";
                            await queue.WriteAsync(db, (tr, state) => state.Push(tr, item), tok).ConfigureAwait(false);
                            Interlocked.Increment(ref pushCount);
                            res.Add(item);
                        }

                        Log($"PushThread[{id}] pushed {NUM:N0} items in {(DateTime.UtcNow - start).TotalSeconds:N1} sec");

                        return(res);
                    }
                    catch (Exception e)
                    {
                        Log($"PushThread[{id}] failed after {i} push and {(DateTime.UtcNow - start).TotalSeconds:N1} sec: {e}");
                        Assert.Fail($"PushThread[{id}] failed: {e.Message}");
                        throw;
                    }
                }).ToArray();

                var popThreads = Enumerable.Range(0, K)
                                 .Select(async id =>
                {
                    int i = 0;
                    try
                    {
                        // make everyone wait a bit, to ensure that they all start roughly at the same time
                        await popLock.Task.ConfigureAwait(false);

                        var res = new List <string>(NUM);

                        while (i < NUM)
                        {
                            var item = await queue.ReadWriteAsync(db, (tr, state) => state.PopAsync(tr), tok).ConfigureAwait(false);
                            if (item.HasValue)
                            {
                                Interlocked.Increment(ref popCount);
                                res.Add(item.Value);
                                ++i;
                            }
                            else
                            {
                                Interlocked.Increment(ref stalls);
                                await Task.Delay(10, this.Cancellation).ConfigureAwait(false);
                            }
                        }
                        Log($"PopThread[{id}] popped {NUM:N0} items in {(DateTime.UtcNow - start).TotalSeconds:N1} sec");

                        return(res);
                    }
                    catch (Exception e)
                    {
                        Log($"PopThread[{id}] failed: {e}");
                        Assert.Fail($"PopThread[{id}] failed after {i} pops and {(DateTime.UtcNow - start).TotalSeconds:N1} sec: {e.Message}");
                        throw;
                    }
                }).ToArray();

                var sw = Stopwatch.StartNew();
                pushLock.Set(async: true);

                await Task.Delay(50, this.Cancellation);

                popLock.Set(async: true);

                await Task.WhenAll(pushTreads);

                Log("Push threads are finished!");
                await Task.WhenAll(popThreads);

                sw.Stop();
                Log($"> Finished {desc} test in {sw.Elapsed.TotalSeconds} seconds");
                Log($"> Pushed {pushCount}, Popped {popCount} and Stalled {stalls}");

                var pushedItems = pushTreads.SelectMany(t => t.Result).ToList();
                var poppedItems = popThreads.SelectMany(t => t.Result).ToList();

                Assert.That(pushCount, Is.EqualTo(K * NUM));
                Assert.That(popCount, Is.EqualTo(K * NUM));

                // all pushed items should have been popped (with no duplicates)
                Assert.That(poppedItems, Is.EquivalentTo(pushedItems));

                // the queue should be empty
                bool empty = await queue.ReadAsync(db, (tr, state) => state.EmptyAsync(tr), ct);

                Assert.That(empty, Is.True);
            }
        }
        public async Task Test_Queue_Fast()
        {
            using (var db = await OpenTestPartitionAsync())
            {
                var location = db.Root["queue"];
                await CleanLocation(db, location);

#if ENABLE_LOGGING
                var logged = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true)));
#else
                var logged = db;
#endif

                var queue = new FdbQueue <int>(location);

                Log("Empty? " + queue.ReadAsync(logged, (tr, state) => state.EmptyAsync(tr), this.Cancellation));

                Log("Push 10, 8, 6 in separate transactions");
                await queue.WriteAsync(logged, (tr, state) => state.Push(tr, 10), this.Cancellation);

                await queue.WriteAsync(logged, (tr, state) => state.Push(tr, 8), this.Cancellation);

                await queue.WriteAsync(logged, (tr, state) => state.Push(tr, 6), this.Cancellation);

#if DEBUG
                await DumpSubspace(db, location);
#endif

                // Empty?
                bool empty = await queue.ReadAsync(logged, (tr, state) => state.EmptyAsync(tr), this.Cancellation);

                Log("Empty? " + empty);
                Assert.That(empty, Is.False);

                var item = await queue.ReadWriteAsync(logged, (tr, state) => state.PopAsync(tr), this.Cancellation);

                Log($"Pop item: {item}");
                Assert.That(item.HasValue, Is.True);
                Assert.That(item.Value, Is.EqualTo(10));
                item = await queue.ReadWriteAsync(logged, (tr, state) => state.PeekAsync(tr), this.Cancellation);

                Log($"Next item: {item}");
                Assert.That(item.HasValue, Is.True);
                Assert.That(item.Value, Is.EqualTo(8));
#if DEBUG
                await DumpSubspace(db, location);
#endif

                item = await queue.ReadWriteAsync(logged, (tr, state) => state.PopAsync(tr), this.Cancellation);

                Log($"Pop item: {item}");
                Assert.That(item.HasValue, Is.True);
                Assert.That(item.Value, Is.EqualTo(8));
#if DEBUG
                await DumpSubspace(db, location);
#endif

                item = await queue.ReadWriteAsync(logged, (tr, state) => state.PopAsync(tr), this.Cancellation);

                Log($"Pop item: {item}");
                Assert.That(item.HasValue, Is.True);
                Assert.That(item.Value, Is.EqualTo(6));
#if DEBUG
                await DumpSubspace(db, location);
#endif

                empty = await queue.ReadAsync(logged, (tr, state) => state.EmptyAsync(tr), this.Cancellation);

                Log("Empty? " + empty);
                Assert.That(empty, Is.True);

                Log("Push 5");
                await queue.WriteAsync(logged, (tr, state) => state.Push(tr, 5), this.Cancellation);

#if DEBUG
                await DumpSubspace(db, location);
#endif

                Log("Clear Queue");
                await queue.WriteAsync(logged, (tr, state) => state.Clear(tr), this.Cancellation);

#if DEBUG
                await DumpSubspace(db, location);
#endif

                empty = await queue.ReadAsync(logged, (tr, state) => state.EmptyAsync(tr), this.Cancellation);

                Log("Empty? " + empty);
                Assert.That(empty, Is.True);
            }
        }
        public async Task Test_Queue_Batch()
        {
            using (var db = await OpenTestPartitionAsync())
            {
                var location = db.Root["queue"];
                await CleanLocation(db, location);

#if ENABLE_LOGGING
                var logged = db.Logged((tr) => Log(tr.Log.GetTimingsReport(true)));
#else
                var logged = db;
#endif

                var queue = new FdbQueue <int>(location);

                Log("Pushing 10 items in a batch...");
                await queue.WriteAsync(logged, (tr, state) =>
                {
                    for (int i = 0; i < 10; i++)
                    {
                        state.Push(tr, i);
                    }
                }, this.Cancellation);

#if DEBUG
                await DumpSubspace(db, queue.Location);
#endif

                Log("Popping 7 items in same transaction...");
                await queue.WriteAsync(logged, async (tr, state) =>
                {
                    for (int i = 0; i < 7; i++)
                    {
                        var r = await state.PopAsync(tr);
                        Log($"- ({r.Value}, {r.HasValue})");
                        Assert.That(r.HasValue, Is.True);
                        Assert.That(r.Value, Is.EqualTo(i));
                    }
                }, this.Cancellation);

#if DEBUG
                await DumpSubspace(db, queue.Location);
#endif

                bool empty = await queue.ReadAsync(logged, (tr, state) => state.EmptyAsync(tr), this.Cancellation);

                Assert.That(empty, Is.False);

                Log("Popping 3 + 1 items in another transaction...");
                await queue.WriteAsync(logged, async (tr, state) =>
                {
                    // should be able to pop 3 items..

                    var r = await state.PopAsync(tr);
                    Log($"- ({r.Value}, {r.HasValue})");
                    Assert.That(r.HasValue, Is.True);
                    Assert.That(r.Value, Is.EqualTo(7));

                    r = await state.PopAsync(tr);
                    Log($"- ({r.Value}, {r.HasValue})");
                    Assert.That(r.HasValue, Is.True);
                    Assert.That(r.Value, Is.EqualTo(8));

                    r = await state.PopAsync(tr);
                    Log($"- ({r.Value}, {r.HasValue})");
                    Assert.That(r.HasValue, Is.True);
                    Assert.That(r.Value, Is.EqualTo(9));

                    // queue should now be empty!
                    r = await state.PopAsync(tr);
                    Log($"- ({r.Value}, {r.HasValue})");
                    Assert.That(r.HasValue, Is.False);
                    Assert.That(r.Value, Is.EqualTo(0));
                }, this.Cancellation);

                empty = await queue.ReadAsync(logged, (tr, state) => state.EmptyAsync(tr), this.Cancellation);

                Assert.That(empty, Is.True);
            }
        }