/// <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); }
/// <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); }
/// <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 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)); }
/// <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); }
/// <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); } }
/// <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); }
/// <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); }
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); }
/// <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); }
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... } }