-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ADO.NET
IHashPicker
customization API + Orleans v3-compatible `IHas…
…hPicker` implementation (#9217) * Allow to customize AdoNetGrainStorage.HashPicker via AdoNetGrainStorageOptions * Orleans v3-compatible IHasher implementation added * custom Orleans v3-compatible IHashPicker implementation added: string-only grain id serialization breaking changes handling required * UseOrleans3CompatibleHasher() method fix * Orleans3CompatibleHasher byte[] allocations eliminated, JenkinsHash unused methods removed * JenkinsHash optimization, Orleans3CompatibleStringKeyHasher refactoring * Orleans3CompatibleStringKeyHasher refactoring * Orleans3CompatibleStringKeyHasher refactoring * default IHashPicker change reverted * IHashPicker configuration comments fix * AdoNetGrainStorage.HashPicker assignment fallback in ctor
- Loading branch information
1 parent
3535eb6
commit 77cb079
Showing
7 changed files
with
298 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/JenkinsHash.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using System; | ||
|
||
namespace Orleans.Storage | ||
{ | ||
// Based on the version in http://home.comcast.net/~bretm/hash/7.html, which is based on that | ||
// in http://burtleburtle.net/bob/hash/evahash.html. | ||
// Note that we only use the version that takes three ulongs, which was written by the Orleans team. | ||
// implementation restored from Orleans v3.7.2: https://github.com/dotnet/orleans/blob/b24e446abfd883f0e4ed614f5267eaa3331548dc/src/Orleans.Core.Abstractions/IDs/JenkinsHash.cs, | ||
// trimmed and slightly optimized | ||
internal static class JenkinsHash | ||
{ | ||
private static void Mix(ref uint aa, ref uint bb, ref uint cc) | ||
{ | ||
uint a = aa; | ||
uint b = bb; | ||
uint c = cc; | ||
|
||
a -= b; a -= c; a ^= (c >> 13); | ||
b -= c; b -= a; b ^= (a << 8); | ||
c -= a; c -= b; c ^= (b >> 13); | ||
a -= b; a -= c; a ^= (c >> 12); | ||
b -= c; b -= a; b ^= (a << 16); | ||
c -= a; c -= b; c ^= (b >> 5); | ||
a -= b; a -= c; a ^= (c >> 3); | ||
b -= c; b -= a; b ^= (a << 10); | ||
c -= a; c -= b; c ^= (b >> 15); | ||
|
||
aa = a; | ||
bb = b; | ||
cc = c; | ||
} | ||
|
||
// This is the reference implementation of the Jenkins hash. | ||
public static uint ComputeHash(ReadOnlySpan<byte> data) | ||
{ | ||
int len = data.Length; | ||
uint a = 0x9e3779b9; | ||
uint b = a; | ||
uint c = 0; | ||
int i = 0; | ||
|
||
while (i <= len - 12) | ||
{ | ||
a += (uint)data[i++] | | ||
((uint)data[i++] << 8) | | ||
((uint)data[i++] << 16) | | ||
((uint)data[i++] << 24); | ||
b += (uint)data[i++] | | ||
((uint)data[i++] << 8) | | ||
((uint)data[i++] << 16) | | ||
((uint)data[i++] << 24); | ||
c += (uint)data[i++] | | ||
((uint)data[i++] << 8) | | ||
((uint)data[i++] << 16) | | ||
((uint)data[i++] << 24); | ||
Mix(ref a, ref b, ref c); | ||
} | ||
c += (uint)len; | ||
if (i < len) | ||
a += data[i++]; | ||
if (i < len) | ||
a += (uint)data[i++] << 8; | ||
if (i < len) | ||
a += (uint)data[i++] << 16; | ||
if (i < len) | ||
a += (uint)data[i++] << 24; | ||
if (i < len) | ||
b += (uint)data[i++]; | ||
if (i < len) | ||
b += (uint)data[i++] << 8; | ||
if (i < len) | ||
b += (uint)data[i++] << 16; | ||
if (i < len) | ||
b += (uint)data[i++] << 24; | ||
if (i < len) | ||
c += (uint)data[i++] << 8; | ||
if (i < len) | ||
c += (uint)data[i++] << 16; | ||
if (i < len) | ||
c += (uint)data[i++] << 24; | ||
Mix(ref a, ref b, ref c); | ||
return c; | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/Orleans3CompatibleHasher.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System; | ||
|
||
namespace Orleans.Storage | ||
{ | ||
/// <summary> | ||
/// Orleans v3-compatible hasher implementation for non-string-only grain key ids. | ||
/// </summary> | ||
internal class Orleans3CompatibleHasher : IHasher | ||
{ | ||
/// <summary> | ||
/// <see cref="IHasher.Description"/> | ||
/// </summary> | ||
public string Description { get; } = $"Orleans v3 hash function ({nameof(JenkinsHash)})."; | ||
|
||
/// <summary> | ||
/// <see cref="IHasher.Hash(byte[])"/>. | ||
/// </summary> | ||
public int Hash(byte[] data) => Hash(data.AsSpan()); | ||
|
||
/// <summary> | ||
/// <see cref="IHasher.Hash(byte[])"/>. | ||
/// </summary> | ||
public int Hash(ReadOnlySpan<byte> data) | ||
{ | ||
// implementation restored from Orleans v3.7.2: https://github.com/dotnet/orleans/blob/b24e446abfd883f0e4ed614f5267eaa3331548dc/src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/OrleansDefaultHasher.cs | ||
return unchecked((int)JenkinsHash.ComputeHash(data)); | ||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/Orleans3CompatibleStorageHashPicker.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using System.Collections.Generic; | ||
using Orleans.Runtime; | ||
|
||
namespace Orleans.Storage | ||
{ | ||
/// <summary> | ||
/// Orleans v3-compatible hash picker implementation for Orleans v3 -> v7+ migration scenarios. | ||
/// </summary> | ||
public class Orleans3CompatibleStorageHashPicker : IStorageHasherPicker | ||
{ | ||
private readonly Orleans3CompatibleHasher _nonStringHasher; | ||
|
||
/// <summary> | ||
/// <see cref="IStorageHasherPicker.HashProviders"/>. | ||
/// </summary> | ||
public ICollection<IHasher> HashProviders { get; } | ||
|
||
/// <summary> | ||
/// A constructor. | ||
/// </summary> | ||
public Orleans3CompatibleStorageHashPicker() | ||
{ | ||
_nonStringHasher = new(); | ||
HashProviders = [_nonStringHasher]; | ||
} | ||
|
||
/// <summary> | ||
/// <see cref="IStorageHasherPicker.PickHasher{T}"/>. | ||
/// </summary> | ||
public IHasher PickHasher<T>( | ||
string serviceId, | ||
string storageProviderInstanceName, | ||
string grainType, | ||
GrainId grainId, | ||
IGrainState<T> grainState, | ||
string tag = null) | ||
{ | ||
// string-only grain keys had special behaviour in Orleans v3 | ||
if (grainId.TryGetIntegerKey(out _, out _) || grainId.TryGetGuidKey(out _, out _)) | ||
return _nonStringHasher; | ||
|
||
// unable to cache hasher instances: content-aware behaviour, see hasher implementation for details | ||
return new Orleans3CompatibleStringKeyHasher(_nonStringHasher, grainType); | ||
} | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/Orleans3CompatibleStringKeyHasher.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
using System; | ||
using System.Buffers; | ||
using System.Text; | ||
|
||
namespace Orleans.Storage | ||
{ | ||
/// <summary> | ||
/// Orleans v3-compatible hasher implementation for string-only grain key ids. | ||
/// </summary> | ||
internal class Orleans3CompatibleStringKeyHasher : IHasher | ||
{ | ||
private readonly Orleans3CompatibleHasher _innerHasher; | ||
private readonly string _grainType; | ||
|
||
public Orleans3CompatibleStringKeyHasher(Orleans3CompatibleHasher innerHasher, string grainType) | ||
{ | ||
_innerHasher = innerHasher; | ||
_grainType = grainType; | ||
} | ||
|
||
/// <summary> | ||
/// <see cref="IHasher.Description"/> | ||
/// </summary> | ||
public string Description { get; } = $"Orleans v3 hash function ({nameof(JenkinsHash)})."; | ||
|
||
/// <summary> | ||
/// <see cref="IHasher.Hash(byte[])"/>. | ||
/// </summary> | ||
public int Hash(byte[] data) | ||
{ | ||
// Orleans v3 treats string-only keys as integer keys with extension (AdoGrainKey.IsLongKey == true), | ||
// so data must be extended for string-only grain keys. | ||
// But AdoNetGrainStorage implementation also uses such code: | ||
// ... | ||
// var grainIdHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes()); | ||
// var grainTypeHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType)); | ||
// ... | ||
// PickHasher parameters are the same for both calls so we need to analyze data content to distinguish these cases. | ||
// It doesn't word if string key is equal to grain type name, but we consider this edge case to be negligibly rare. | ||
|
||
if (IsGrainTypeName(data)) | ||
return _innerHasher.Hash(data); | ||
|
||
var extendedLength = data.Length + 8; | ||
|
||
const int maxOnStack = 256; | ||
byte[] rentedBuffer = null; | ||
|
||
// assuming code below never throws, so calling ArrayPool.Return without try/finally block for JIT optimization | ||
|
||
var buffer = extendedLength > maxOnStack | ||
? (rentedBuffer = ArrayPool<byte>.Shared.Rent(extendedLength)).AsSpan() | ||
: stackalloc byte[maxOnStack]; | ||
|
||
buffer = buffer[..extendedLength]; | ||
|
||
data.AsSpan().CopyTo(buffer); | ||
// buffer may contain arbitrary data, setting zeros in 'extension' segment | ||
buffer[data.Length..].Clear(); | ||
|
||
var hash = _innerHasher.Hash(buffer); | ||
|
||
if (rentedBuffer is not null) | ||
ArrayPool<byte>.Shared.Return(rentedBuffer); | ||
|
||
return hash; | ||
} | ||
|
||
private bool IsGrainTypeName(byte[] data) | ||
{ | ||
// at least 1 byte per char | ||
if (data.Length < _grainType.Length) | ||
return false; | ||
|
||
var grainTypeByteCount = Encoding.UTF8.GetByteCount(_grainType); | ||
if (grainTypeByteCount != data.Length) | ||
return false; | ||
|
||
const int maxOnStack = 256; | ||
byte[] rentedBuffer = null; | ||
|
||
// assuming code below never throws, so calling ArrayPool.Return without try/finally block for JIT optimization | ||
|
||
var buffer = grainTypeByteCount > maxOnStack | ||
? (rentedBuffer = ArrayPool<byte>.Shared.Rent(grainTypeByteCount)).AsSpan() | ||
: stackalloc byte[maxOnStack]; | ||
|
||
buffer = buffer[..grainTypeByteCount]; | ||
|
||
var bytesWritten = Encoding.UTF8.GetBytes(_grainType, buffer); | ||
var isGrainType = buffer[..bytesWritten].SequenceEqual(data); | ||
if (rentedBuffer is not null) | ||
ArrayPool<byte>.Shared.Return(rentedBuffer); | ||
|
||
return isGrainType; | ||
} | ||
} | ||
} |