/// <summary>Add a value to a counter in one atomic operation</summary> /// <param name="transaction"></param> /// <param name="counterKey">Key of the counter, relative to the list's subspace</param> /// <param name="value">Value that will be added</param> /// <remarks>This operation will not cause the current transaction to conflict. It may create conflicts for transactions that would read the value of the counter.</remarks> public void Add([NotNull] IFdbTransaction transaction, [NotNull] TKey counterKey, long value) { if (transaction == null) { throw new ArgumentNullException(nameof(transaction)); } if (counterKey == null) { throw new ArgumentNullException(nameof(counterKey)); } //REVIEW: we could no-op if value == 0 but this may change conflict behaviour for other transactions... transaction.AtomicAdd64(this.Location.Keys[counterKey], value); }
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); } }
/// <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 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... } }