/// <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>Increments the count of an (index, value) pair in the multimap.</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 increment</param> /// <remarks>If the (index, value) pair does not exist, its value is considered to be 0</remarks> /// <exception cref="System.ArgumentNullException">If <paramref name="trans"/> is null.</exception> public Task AddAsync([NotNull] IFdbTransaction trans, TKey key, TValue value) { //note: this method does not need to be async, but subtract is, so it's better if both methods have the same shape. if (trans == null) throw new ArgumentNullException(nameof(trans)); trans.AtomicAdd(this.Subspace.Keys[key, value], PlusOne); return Task.CompletedTask; }
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")); }
/// <summary>Increments the count of an (index, value) pair in the multimap.</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 increment</param> /// <remarks>If the (index, value) pair does not exist, its value is considered to be 0</remarks> /// <exception cref="System.ArgumentNullException">If <paramref name="trans"/> is null.</exception> public Task AddAsync([NotNull] IFdbTransaction trans, TKey key, TValue value) { //note: this method does not need to be async, but subtract is, so it's better if both methods have the same shape. if (trans == null) { throw new ArgumentNullException("trans"); } trans.AtomicAdd(this.Location.Keys.Encode(key, value), PlusOne); return(FoundationDB.Async.TaskHelpers.CompletedTask); }
private Task Scenario3(IFdbTransaction tr) { var location = FdbSubspace.Create(Slice.FromAscii("TEST")); tr.Set(location.Key + (byte)'a', Slice.FromAscii("A")); tr.AtomicAdd(location.Key + (byte)'k', Slice.FromFixed32(1)); tr.Set(location.Key + (byte)'z', Slice.FromAscii("C")); tr.ClearRange(location.Key + (byte)'a', location.Key + (byte)'k'); tr.ClearRange(location.Key + (byte)'k', location.Key + (byte)'z'); return(Task.FromResult <object>(null)); }
private Task Scenario3(IFdbTransaction tr) { var location = FdbSubspace.Create(Slice.FromAscii("TEST")); tr.Set(location.Key + (byte)'a', Slice.FromAscii("A")); tr.AtomicAdd(location.Key + (byte)'k', Slice.FromFixed32(1)); tr.Set(location.Key + (byte)'z', Slice.FromAscii("C")); tr.ClearRange(location.Key + (byte)'a', location.Key + (byte)'k'); tr.ClearRange(location.Key + (byte)'k', location.Key + (byte)'z'); return Task.FromResult<object>(null); }
/// <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("transaction"); } if (counterKey == null) { throw new ArgumentNullException("counterKey"); } //REVIEW: we could no-op if value == 0 but this may change conflict behaviour for other transactions... Slice param = value == 1 ? PlusOne : value == -1 ? MinusOne : Slice.FromFixed64(value); transaction.AtomicAdd(this.Location.Keys.Encode(counterKey), param); }
public async Task InsertAsync([NotNull] 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.AtomicAdd(this.Subspace.Keys.Encode(level, prevKey), EncodeCount(1)); } 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.Keys.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.Keys.Encode(level, prevKey), EncodeCount(newPrevCount)); trans.Set(this.Subspace.Keys.Encode(level, key), EncodeCount(count)); } } }
public async Task EraseAsync([NotNull] 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.Keys.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.AtomicAdd(this.Subspace.Keys.Encode(level, prevKey), EncodeCount(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("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.Key, this.Counters.Keys.Encode(start) + FdbKey.MinValue); start += window; count = 0; trans.ClearRange(this.Recent.Key, this.Recent.Keys.Encode(start)); } // Increment the allocation count for the current window trans.AtomicAdd(this.Counters.Keys.Encode(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.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... } }
/// <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... } }