예제 #1
0
        /// <summary>
        /// Signup a student to a class
        /// </summary>
        public async Task Signup(IFdbTransaction tr, string s, string c)
        {
            var rec = AttendsKey(s, c);

            if ((await tr.GetAsync(rec)).IsPresent)
            {             // already signed up
                return;
            }
            int seatsLeft = Int32.Parse((await tr.GetAsync(ClassKey(c))).ToStringAscii());

            if (seatsLeft <= 0)
            {
                throw new InvalidOperationException("No remaining seats");
            }

            var classes = await tr.GetRange(AttendsKeys(s)).ToListAsync();

            if (classes.Count >= 5)
            {
                throw new InvalidOperationException("Too many classes");
            }

            tr.Set(ClassKey(c), Slice.FromStringAscii((seatsLeft - 1).ToString()));
            tr.Set(rec, Slice.Empty);
        }
예제 #2
0
        /// <summary>
        /// Drop a student from a class
        /// </summary>
        public async Task Drop(IFdbTransaction tr, string s, string c)
        {
            var rec = AttendsKey(s, c);

            if ((await tr.GetAsync(rec)).IsNullOrEmpty)
            {             // not taking this class
                return;
            }

            var students = Int32.Parse((await tr.GetAsync(ClassKey(c))).ToStringAscii());

            tr.Set(ClassKey(c), Slice.FromStringAscii((students + 1).ToString()));
            tr.Clear(rec);
        }
예제 #3
0
        /// <summary>Decrements the count of an (index, value) pair in the multimap, and optionally removes it if the count reaches zero.</summary>
        /// <param name="trans">Transaction used for the operation</param>
        /// <param name="key">Key of the entry</param>
        /// <param name="value">Value for the <paramref name="key"/> to decrement</param>
        /// <remarks>If the updated count reaches zero or less, and AllowNegativeValues is not set, the key will be cleared from the map.</remarks>
        public async Task SubtractAsync([NotNull] IFdbTransaction trans, TKey key, TValue value)
        {
            if (trans == null)
            {
                throw new ArgumentNullException("trans");
            }

            Slice k = this.Location.Keys.Encode(key, value);

            if (this.AllowNegativeValues)
            {
                trans.AtomicAdd(k, MinusOne);
                // note: it's faster, but we will end up with counts less than or equal to 0
                // If 'k' does not already exist, its count will be set to -1
            }
            else
            {
                Slice v = await trans.GetAsync(k).ConfigureAwait(false);

                if (this.AllowNegativeValues || v.ToInt64() > 1)                 //note: Slice.Nil.ToInt64() will return 0
                {
                    trans.AtomicAdd(k, MinusOne);
                    //note: since we already read 'k', the AtomicAdd will be optimized into the equivalent of Set(k, v - 1) by the client, unless RYW has been disabled on the transaction
                    //TODO: if AtomicMax ever gets implemented, we could use it to truncate the values to 0
                }
                else
                {
                    trans.Clear(k);
                }
            }
        }
        /// <summary>Finds a new free uid that can be used to store a new string in the table</summary>
        /// <param name="trans">Transaction used to look for and create a new uid</param>
        /// <returns>Newly created UID that is guaranteed to be globally unique</returns>
        private async Task <Slice> FindUidAsync(IFdbTransaction trans)
        {
            const int MAX_TRIES = 256;

            int tries = 0;

            while (tries < MAX_TRIES)
            {
                // note: we diverge from the python sample layer by not expanding the size at each retry, in order to ensure that value size keeps as small as possible
                Slice slice;
                lock (m_prng)
                {                 // note: not all PRNG implementations are thread-safe !
                    slice = Slice.Random(m_prng, 4 + (tries >> 1));
                }

                if (m_uidStringCache.ContainsKey(new Uid(slice)))
                {
                    continue;
                }

                var candidate = await trans.GetAsync(UidKey(slice)).ConfigureAwait(false);

                if (candidate.IsNull)
                {
                    return(slice);
                }

                ++tries;
            }

            //TODO: another way ?
            throw new InvalidOperationException("Failed to find a free uid for interned string after " + MAX_TRIES + " attempts");
        }
        private async Task <Chunk> GetChunkAtAsync([NotNull] IFdbTransaction trans, long offset)
        {
            Contract.Requires(trans != null && offset >= 0);

            var chunkKey = await trans.GetKeyAsync(FdbKeySelector.LastLessOrEqual(DataKey(offset))).ConfigureAwait(false);

            if (chunkKey.IsNull)
            {             // nothing before (sparse)
                return(default(Chunk));
            }

            if (chunkKey < DataKey(0))
            {             // off beginning
                return(default(Chunk));
            }

            long chunkOffset = DataKeyOffset(chunkKey);

            Slice chunkData = await trans.GetAsync(chunkKey).ConfigureAwait(false);

            if (chunkOffset + chunkData.Count <= offset)
            {             // in sparse region after chunk
                return(default(Chunk));
            }

            return(new Chunk(chunkKey, chunkData, chunkOffset));
        }
		private async Task Scenario6(IFdbTransaction tr)
		{
			var location = FdbSubspace.Create(Slice.FromAscii("TEST"));

			tr.AtomicAdd(location.Pack("ATOMIC"), Slice.FromFixed32(0x55555555));

			var x = await tr.GetAsync(location.Pack("ATOMIC"));
			Console.WriteLine(x.ToInt32().ToString("x"));
		}
        private async Task Scenario6(IFdbTransaction tr)
        {
            var location = FdbSubspace.Create(Slice.FromAscii("TEST"));

            tr.AtomicAdd(location.Pack("ATOMIC"), Slice.FromFixed32(0x55555555));

            var x = await tr.GetAsync(location.Pack("ATOMIC"));

            Console.WriteLine(x.ToInt32().ToString("x"));
        }
        private async Task PushAtAsync([NotNull] IFdbTransaction tr, T value, long index)
        {
            // Items are pushed on the queue at an (index, randomID) pair. Items pushed at the
            // same time will have the same index, and so their ordering will be random.
            // This makes pushes fast and usually conflict free (unless the queue becomes empty
            // during the push)

            Slice key = this.QueueItem.Keys.Encode(index, this.RandId());
            await tr.GetAsync(key).ConfigureAwait(false);

            tr.Set(key, this.Encoder.EncodeValue(value));
        }
예제 #9
0
        /// <summary>Update the indexed values of an entity</summary>
        /// <param name="trans">Transaction to use</param>
        /// <param name="id">Id of the entity that has changed</param>
        /// <param name="newValue">Previous value of this entity in the index</param>
        /// <param name="previousValue">New value of this entity in the index</param>
        /// <returns>True if a change was performed in the index; otherwise false (if <paramref name="previousValue"/> and <paramref name="newValue"/>)</returns>
        /// <remarks>If <paramref name="newValue"/> and <paramref name="previousValue"/> are identical, then nothing will be done. Otherwise, the old index value will be deleted and the new value will be added</remarks>
        public async Task <bool> UpdateAsync([NotNull] IFdbTransaction trans, long id, TValue newValue, TValue previousValue)
        {
            if (trans == null)
            {
                throw new ArgumentNullException(nameof(trans));
            }

            if (!this.ValueComparer.Equals(newValue, previousValue))
            {
                // remove previous value
                if (this.IndexNullValues || previousValue != null)
                {
                    var key  = this.Location.Keys.Encode(previousValue);
                    var data = await trans.GetAsync(key).ConfigureAwait(false);

                    if (data.HasValue)
                    {
                        var builder = new CompressedBitmapBuilder(data);
                        builder.Clear((int)id);                         //BUGBUG: 64 bit id!
                        trans.Set(key, builder.ToSlice());
                    }
                }

                // add new value
                if (this.IndexNullValues || newValue != null)
                {
                    var key  = this.Location.Keys.Encode(newValue);
                    var data = await trans.GetAsync(key).ConfigureAwait(false);

                    var builder = data.HasValue ? new CompressedBitmapBuilder(data) : CompressedBitmapBuilder.Empty;
                    builder.Set((int)id);                     //BUGBUG: 64 bit id!
                    trans.Set(key, builder.ToSlice());
                }

                // cannot be both null, so we did at least something)
                return(true);
            }
            return(false);
        }
        private async Task <Slice> AddConflictedPopAsync([NotNull] IFdbTransaction tr, bool forced)
        {
            long index = await GetNextIndexAsync(tr.Snapshot, this.ConflictedPop).ConfigureAwait(false);

            if (index == 0 && !forced)
            {
                return(Slice.Nil);
            }

            Slice waitKey = this.ConflictedPop.Keys.Encode(index, this.RandId());
            await tr.GetAsync(waitKey).ConfigureAwait(false);

            tr.Set(waitKey, Slice.Empty);
            return(waitKey);
        }
예제 #11
0
        /// <summary>Remove an entity from the index</summary>
        /// <param name="trans">Transaction to use</param>
        /// <param name="id">Id of the entity that has been deleted</param>
        /// <param name="value">Previous value of the entity in the index</param>
        public async Task <bool> RemoveAsync([NotNull] IFdbTransaction trans, long id, TValue value)
        {
            if (trans == null)
            {
                throw new ArgumentNullException(nameof(trans));
            }

            var key  = this.Location.Keys.Encode(value);
            var data = await trans.GetAsync(key).ConfigureAwait(false);

            if (data.HasValue)
            {
                var builder = new CompressedBitmapBuilder(data);
                builder.Clear((int)id);                 //BUGBUG: 64 bit id!
                trans.Set(key, builder.ToSlice());
                return(true);
            }
            return(false);
        }
            public async Task InsertAsync(IFdbTransaction trans, Slice key)
            {
                if (trans == null)
                {
                    throw new ArgumentNullException(nameof(trans));
                }

                if (await ContainsAsync(trans, key).ConfigureAwait(false))
                {
                    return;
                }

                int keyHash = key.GetHashCode();                 //TODO: proper hash function?

                //Console.WriteLine("Inserting " + key + " with hash " + keyHash.ToString("x"));
                for (int level = 0; level < MAX_LEVELS; level++)
                {
                    var prevKey = await GetPreviousNodeAsync(trans, level, key);

                    if ((keyHash & ((1 << (level * LEVEL_FAN_POW)) - 1)) != 0)
                    {
                        //Console.WriteLine("> [" + level + "] Incrementing previous key: " + FdbKey.Dump(prevKey));
                        trans.AtomicIncrement64(this.Subspace.Encode(level, prevKey));
                    }
                    else
                    {
                        //Console.WriteLine("> [" + level + "] inserting and updating previous key: " + FdbKey.Dump(prevKey));
                        // Insert into this level by looking at the count of the previous
                        // key in the level and recounting the next lower level to correct
                        // the counts
                        var prevCount    = DecodeCount(await trans.GetAsync(this.Subspace.Encode(level, prevKey)).ConfigureAwait(false));
                        var newPrevCount = await SlowCountAsync(trans, level - 1, prevKey, key);

                        var count = checked ((prevCount - newPrevCount) + 1);

                        // print "insert", key, "level", level, "count", count,
                        // "splits", prevKey, "oldC", prevCount, "newC", newPrevCount
                        trans.Set(this.Subspace.Encode(level, prevKey), EncodeCount(newPrevCount));
                        trans.Set(this.Subspace.Encode(level, key), EncodeCount(count));
                    }
                }
            }
        /// <summary>Adds a value to a counter, but return its previous value.</summary>
        /// <param name="transaction">Transaction to use for the operation</param>
        /// <param name="counterKey">Key of the counter, relative to the list's subspace</param>
        /// <returns>Previous value of the counter. Returns 0 if the counter did not exist previously.</returns>
        /// <remarks>This method WILL conflict with other transactions!</remarks>
        public async Task <long> ReadThenAddAsync([NotNull] IFdbTransaction transaction, [NotNull] TKey counterKey, long value)
        {
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction");
            }
            if (counterKey == null)
            {
                throw new ArgumentNullException("counterKey");
            }

            var key = this.Location.Keys.Encode(counterKey);
            var res = await transaction.GetAsync(key).ConfigureAwait(false);

            long previous = res.IsNullOrEmpty ? 0 : res.ToInt64();

            transaction.Set(key, Slice.FromFixed64(value + previous));

            return(previous);
        }
            public async Task EraseAsync(IFdbTransaction trans, Slice key)
            {
                if (trans == null)
                {
                    throw new ArgumentNullException(nameof(trans));
                }

                if (!(await ContainsAsync(trans, key).ConfigureAwait(false)))
                {
                    return;
                }

                for (int level = 0; level < MAX_LEVELS; level++)
                {
                    // This could be optimized with hash
                    var k = this.Subspace.Encode(level, key);
                    var c = await trans.GetAsync(k).ConfigureAwait(false);

                    if (c.HasValue)
                    {
                        trans.Clear(k);
                    }
                    if (level == 0)
                    {
                        continue;
                    }

                    var prevKey = await GetPreviousNodeAsync(trans, level, key);

                    Contract.Assert(prevKey != key);
                    long countChange = -1;
                    if (c.HasValue)
                    {
                        countChange += DecodeCount(c);
                    }

                    trans.AtomicAdd64(this.Subspace.Encode(level, prevKey), countChange);
                }
            }
예제 #15
0
        /// <summary>Adds a value to a counter, and return its new value.</summary>
        /// <param name="transaction">Transaction to use for the operation</param>
        /// <param name="counterKey">Key of the counter, relative to the list's subspace</param>
        /// <returns>New value of the counter. Returns <paramref name="value"/> if the counter did not exist previously.</returns>
        /// <remarks>This method WILL conflict with other transactions!</remarks>
        public async Task <long> AddThenReadAsync([NotNull] IFdbTransaction transaction, [NotNull] TKey counterKey, long value)
        {
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction");
            }
            if (counterKey == null)
            {
                throw new ArgumentNullException("counterKey");
            }

            var key = this.Location.EncodeKey(counterKey);
            var res = await transaction.GetAsync(key).ConfigureAwait(false);

            if (!res.IsNullOrEmpty)
            {
                value += res.ToInt64();
            }
            transaction.Set(key, Slice.FromFixed64(value));

            return(value);
        }
예제 #16
0
        /// <summary>Insert a newly created entity to the index</summary>
        /// <param name="trans">Transaction to use</param>
        /// <param name="id">Id of the new entity (that was never indexed before)</param>
        /// <param name="value">Value of this entity in the index</param>
        /// <returns>True if a value was inserted into the index; or false if <paramref name="value"/> is null and <see cref="IndexNullValues"/> is false, or if this <paramref name="id"/> was already indexed at this <paramref name="value"/>.</returns>
        public async Task <bool> AddAsync([NotNull] IFdbTransaction trans, long id, TValue value)
        {
            if (trans == null)
            {
                throw new ArgumentNullException(nameof(trans));
            }

            if (this.IndexNullValues || value != null)
            {
                var key  = this.Location.Keys.Encode(value);
                var data = await trans.GetAsync(key).ConfigureAwait(false);

                var builder = data.HasValue ? new CompressedBitmapBuilder(data) : CompressedBitmapBuilder.Empty;

                //TODO: wasteful to crate a builder to only set on bit ?
                builder.Set((int)id);                 //BUGBUG: id should be 64-bit!

                //TODO: if bit was already set, skip the set ?
                trans.Set(key, builder.ToSlice());
                return(true);
            }
            return(false);
        }
예제 #17
0
            private async Task <Slice> InternSlowAsync(IFdbTransaction trans, string value)
            {
                var stringKey = StringKey(value);

                var uid = await trans.GetAsync(stringKey).ConfigureAwait(false);

                if (uid.IsNull)
                {
#if DEBUG_STRING_INTERNING
                    Debug.WriteLine("_ not found in db, will create...");
#endif

                    uid = await FindUidAsync(trans).ConfigureAwait(false);

                    if (uid.IsNull)
                    {
                        throw new InvalidOperationException("Failed to allocate a new uid while attempting to intern a string");
                    }
#if DEBUG_STRING_INTERNING
                    Debug.WriteLine("> using new uid " + uid.ToBase64());
#endif

                    trans.Set(UidKey(uid), Slice.FromString(value));
                    trans.Set(stringKey, uid);

                    //BUGBUG: if the transaction fails to commit, we will inserted a bad value in the cache!
                    this.Layer.AddToCache(value, uid);
                }
                else
                {
#if DEBUG_STRING_INTERNING
                    Debug.WriteLine("> found in db with uid " + uid.ToBase64());
#endif
                }

                return(uid);
            }
		private async Task CheckWriteVersionAsync(IFdbTransaction trans)
		{
			var value = await trans.GetAsync(this.RootNode.Pack(VersionKey)).ConfigureAwait(false);
			if (value.IsNullOrEmpty)
			{
				InitializeDirectory(trans);
			}
			else
			{
				CheckVersion(value, true);
			}
		}
        /// <summary>Returns a 64-bit integer that
        /// 1) has never and will never be returned by another call to this
        ///    method on the same subspace
        /// 2) is nearly as short as possible given the above
        /// </summary>
        public async Task <long> AllocateAsync([NotNull] IFdbTransaction trans)
        {
            if (trans == null)
            {
                throw new ArgumentNullException(nameof(trans));
            }

            // find the current window size, by reading the last entry in the 'counters' subspace
            long start = 0, count = 0;
            var  kv = await trans
                      .Snapshot
                      .GetRange(this.Counters.Keys.ToRange())
                      .LastOrDefaultAsync();

            if (kv.Key.IsPresent)
            {
                start = this.Counters.Keys.Decode <long>(kv.Key);
                count = kv.Value.ToInt64();
            }

            // check if the window is full
            int window = GetWindowSize(start);

            if ((count + 1) * 2 >= window)
            {             // advance the window
                if (FdbDirectoryLayer.AnnotateTransactions)
                {
                    trans.Annotate("Advance allocator window size to {0} starting at {1}", window, start + window);
                }
                trans.ClearRange(this.Counters.GetPrefix(), this.Counters.Keys.Encode(start) + FdbKey.MinValue);
                start += window;
                count  = 0;
                trans.ClearRange(this.Recent.GetPrefix(), this.Recent.Keys.Encode(start));
            }

            // Increment the allocation count for the current window
            trans.AtomicAdd64(this.Counters.Keys.Encode(start), 1);

            // As of the snapshot being read from, the window is less than half
            // full, so this should be expected to take 2 tries.  Under high
            // contention (and when the window advances), there is an additional
            // subsequent risk of conflict for this transaction.
            while (true)
            {
                // Find a random free slot in the current window...
                long candidate;
                lock (m_rnd)
                {
                    candidate = start + m_rnd.Next(window);
                }

                // test if the key is used
                var key   = this.Recent.Keys.Encode(candidate);
                var value = await trans.GetAsync(key).ConfigureAwait(false);

                if (value.IsNull)
                {                 // free slot
                    // mark as used
                    trans.Set(key, Slice.Empty);
                    if (FdbDirectoryLayer.AnnotateTransactions)
                    {
                        trans.Annotate("Allocated prefix {0} from window [{1}..{2}] ({3} used)", candidate, start, start + window - 1, count + 1);
                    }
                    return(candidate);
                }

                // no luck this time, try again...
            }
        }
 public virtual Task <Slice> GetAsync(Slice key)
 {
     ThrowIfDisposed();
     return(m_transaction.GetAsync(key));
 }
		// Compare the behavior of the MemoryDB against a FoundationDB database

		private async Task Scenario1(IFdbTransaction tr)
		{
			tr.Set(Slice.FromAscii("hello"), Slice.FromAscii("world!"));
			tr.Clear(Slice.FromAscii("removed"));
			var result = await tr.GetAsync(Slice.FromAscii("narf"));
		}
		/// <summary>
		/// Drop a student from a class
		/// </summary>
		public async Task Drop(IFdbTransaction tr, string s, string c)
		{
			var rec = AttendsKey(s, c);
			if ((await tr.GetAsync(rec)).IsNullOrEmpty)
			{ // not taking this class
				return;
			}

			var students = Int32.Parse((await tr.GetAsync(ClassKey(c))).ToAscii());
			tr.Set(ClassKey(c), Slice.FromAscii((students + 1).ToString()));
			tr.Clear(rec);
		}
예제 #23
0
 /// <inheritdoc />
 public virtual Task <Slice> GetAsync(ReadOnlySpan <byte> key)
 {
     ThrowIfDisposed();
     return(m_transaction.GetAsync(key));
 }
		/// <summary>
		/// Signup a student to a class
		/// </summary>
		public async Task Signup(IFdbTransaction tr, string s, string c)
		{
			var rec = AttendsKey(s, c);

			if ((await tr.GetAsync(rec)).IsPresent)
			{ // already signed up
				return;
			}
			int seatsLeft = Int32.Parse((await tr.GetAsync(ClassKey(c))).ToAscii());
			if (seatsLeft <= 0)
			{
				throw new InvalidOperationException("No remaining seats");
			}

			var classes = await tr.GetRange(AttendsKeys(s)).ToListAsync();
			if (classes.Count >= 5) throw new InvalidOperationException("Too many classes");

			tr.Set(ClassKey(c), Slice.FromAscii((seatsLeft - 1).ToString()));
			tr.Set(rec, Slice.Empty);
		}
		private async Task<Chunk> GetChunkAtAsync(IFdbTransaction trans, long offset)
		{
			Contract.Requires(trans != null && offset >= 0);

			var chunkKey = await trans.GetKeyAsync(FdbKeySelector.LastLessOrEqual(DataKey(offset))).ConfigureAwait(false);
			if (chunkKey.IsNull)
			{ // nothing before (sparse)
				return default(Chunk);
			}

			if (chunkKey < DataKey(0))
			{ // off beginning
				return default(Chunk);
			}

			long chunkOffset = DataKeyOffset(chunkKey);

			Slice chunkData = await trans.GetAsync(chunkKey).ConfigureAwait(false);

			if (chunkOffset + chunkData.Count <= offset)
			{ // in sparse region after chunk
				return default(Chunk);
			}

			return new Chunk(chunkKey, chunkData, chunkOffset);
		}
        // Compare the behavior of the MemoryDB against a FoundationDB database

        private async Task Scenario1(IFdbTransaction tr)
        {
            tr.Set(Slice.FromAscii("hello"), Slice.FromAscii("world!"));
            tr.Clear(Slice.FromAscii("removed"));
            var result = await tr.GetAsync(Slice.FromAscii("narf"));
        }
        public static async Task <long> AllocateAsync(IFdbTransaction trans, ITypedKeySubspace <int, long> subspace, Random rng)
        {
            Contract.NotNull(trans);

            // find the current window size, by reading the last entry in the 'counters' subspace
            long start = 0, count = 0;
            var  kv = await trans
                      .Snapshot
                      .GetRange(subspace.EncodePartialRange(COUNTERS))
                      .LastOrDefaultAsync();

            if (kv.Key.Count != 0)
            {
                start = subspace.DecodeLast(kv.Key);
                count = kv.Value.ToInt64();
            }

            // check if the window is full
            int window = GetWindowSize(start);

            if ((count + 1) * 2 >= window)
            {             // advance the window
                if (FdbDirectoryLayer.AnnotateTransactions)
                {
                    trans.Annotate("Advance allocator window size to {0} starting at {1}", window, start + window);
                }
                trans.ClearRange(subspace[COUNTERS, 0], subspace[COUNTERS, start + 1]);
                start += window;
                count  = 0;
                trans.ClearRange(subspace[RECENT, 0], subspace[RECENT, start]);
            }

            // Increment the allocation count for the current window
            trans.AtomicAdd64(subspace[COUNTERS, start], 1);

            // As of the snapshot being read from, the window is less than half
            // full, so this should be expected to take 2 tries.  Under high
            // contention (and when the window advances), there is an additional
            // subsequent risk of conflict for this transaction.
            while (true)
            {
                // Find a random free slot in the current window...
                long candidate;
                lock (rng)
                {
                    candidate = start + rng.Next(window);
                }

                // test if the key is used
                var key   = subspace[RECENT, candidate];
                var value = await trans.GetAsync(key).ConfigureAwait(false);

                if (value.IsNull)
                {                 // free slot
                    // mark as used
                    trans.Set(key, Slice.Empty);
                    if (FdbDirectoryLayer.AnnotateTransactions)
                    {
                        trans.Annotate("Allocated prefix {0} from window [{1}..{2}] ({3} used)", candidate, start, start + window - 1, count + 1);
                    }
                    return(candidate);
                }

                // no luck this time, try again...
            }
        }
		/// <summary>Returns a 64-bit integer that
		/// 1) has never and will never be returned by another call to this
		///    method on the same subspace
		/// 2) is nearly as short as possible given the above
		/// </summary>
		public async Task<long> AllocateAsync(IFdbTransaction trans)
		{
			// find the current window size, by reading the last entry in the 'counters' subspace
			long start = 0, count = 0;
			var kv = await trans
				.Snapshot
				.GetRange(this.Counters.ToRange())
				.LastOrDefaultAsync();

			if (kv.Key.IsPresent)
			{
				start = this.Counters.UnpackSingle<long>(kv.Key);
				count = kv.Value.ToInt64();
			}

			// check if the window is full
			int window = GetWindowSize(start);
			if ((count + 1) * 2 >= window)
			{ // advance the window
				trans.ClearRange(this.Counters.Key, this.Counters.Pack(start) + FdbKey.MinValue);
				start += window;
				trans.ClearRange(this.Recent.Key, this.Recent.Pack(start));
			}

			// Increment the allocation count for the current window
			trans.AtomicAdd(this.Counters.Pack(start), Slice.FromFixed64(1));

			// As of the snapshot being read from, the window is less than half
            // full, so this should be expected to take 2 tries.  Under high
            // contention (and when the window advances), there is an additional
            // subsequent risk of conflict for this transaction.
			while (true)
			{
				// Find a random free slot in the current window...
				long candidate;
				lock (m_rnd)
				{
					candidate = start + m_rnd.Next(window);
				}

				// test if the key is used
				var key = this.Recent.Pack(candidate);
				var value = await trans.GetAsync(key).ConfigureAwait(false);

				if (value.IsNull)
				{ // free slot

					// mark as used
					trans.Set(key, Slice.Empty);
					return candidate;
				}

				// no luck this time, try again...
			}
		}