Skip to content

Commit 62736c9

Browse files
committed
fix(runtime): preserve callback ownership
1 parent c5e348a commit 62736c9

6 files changed

Lines changed: 158 additions & 114 deletions

File tree

src/Orleans.Core/Messaging/CorrelationId.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Orleans.Runtime
1616

1717
public static CorrelationId GetNext() => new(System.Threading.Interlocked.Increment(ref lastUsed));
1818

19-
public override int GetHashCode() => HashCode.Combine(id);
19+
public override int GetHashCode() => id.GetHashCode();
2020

2121
public override bool Equals(object? obj) => obj is CorrelationId correlationId && Equals(correlationId);
2222

src/Orleans.Core/Messaging/MessageFactory.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ public Message CreateMessage(object? body, InvokeMethodOptions options)
4949
private CorrelationId GetNextCorrelationId()
5050
{
5151
var id = _seed ^ Interlocked.Increment(ref _nextId);
52-
var stripeIndex = StripedCallbackDictionary<object>.GetCurrentThreadStripeIndex();
53-
return StripedCallbackDictionary<object>.CreateCorrelationId(unchecked((long)id), stripeIndex);
52+
return new CorrelationId(unchecked((long)id));
5453
}
5554

5655
public Message CreateResponseMessage(Message request)

src/Orleans.Core/Messaging/StripedCallbackDictionary.cs

Lines changed: 34 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,20 @@ namespace Orleans.Runtime;
1010

1111
/// <summary>
1212
/// A striped dictionary that distributes entries across multiple internal dictionaries
13-
/// to reduce lock contention. The stripe is determined by bits embedded in the CorrelationId.
13+
/// to reduce lock contention by hashing correlation ids across stripes.
1414
/// </summary>
1515
/// <typeparam name="TValue">The type of values stored in the dictionary.</typeparam>
16-
internal sealed class StripedCallbackDictionary<TValue> : IEnumerable<KeyValuePair<CorrelationId, TValue>>
16+
internal sealed class StripedCallbackDictionary<TValue> : IEnumerable<TValue>
1717
where TValue : notnull
1818
{
19-
/// <summary>
20-
/// The number of bits used to identify the stripe (stored in the upper bits of the CorrelationId).
21-
/// </summary>
22-
public const int StripeBits = 7;
23-
24-
/// <summary>
25-
/// The number of stripes (must be a power of 2).
26-
/// </summary>
27-
public const int StripeCount = 1 << StripeBits; // 128 stripes
28-
29-
/// <summary>
30-
/// Mask to extract the stripe index from the upper bits.
31-
/// </summary>
32-
private const long StripeMask = (long)(StripeCount - 1) << (64 - StripeBits);
19+
private const int StripeBits = 7;
20+
// Fibonacci hashing spreads sequential and strided ids using one multiply and shift.
21+
private const ulong HashFactor = 11_400_714_819_323_198_485;
3322

3423
/// <summary>
35-
/// The shift amount to move the stripe bits to the lowest position.
24+
/// The number of stripes.
3625
/// </summary>
37-
private const int StripeShift = 64 - StripeBits;
26+
public const int StripeCount = 1 << StripeBits;
3827

3928
private readonly Stripe[] _stripes;
4029

@@ -48,40 +37,14 @@ public StripedCallbackDictionary()
4837
}
4938

5039
/// <summary>
51-
/// Encodes a stripe index into the upper bits of a base value to create a CorrelationId.
52-
/// </summary>
53-
/// <param name="baseValue">The base value (e.g., from an incrementing counter XORed with a seed).</param>
54-
/// <param name="stripeIndex">The stripe index (typically derived from thread id).</param>
55-
/// <returns>A CorrelationId with the stripe encoded in the upper bits.</returns>
56-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
57-
public static CorrelationId CreateCorrelationId(long baseValue, int stripeIndex)
58-
{
59-
// Clear the upper StripeBits of the base value and set the stripe index there
60-
long maskedBase = baseValue & ~StripeMask;
61-
long stripeValue = (long)(stripeIndex & (StripeCount - 1)) << StripeShift;
62-
return new CorrelationId(maskedBase | stripeValue);
63-
}
64-
65-
/// <summary>
66-
/// Extracts the stripe index from a CorrelationId.
40+
/// Computes the stripe index for a correlation id.
6741
/// </summary>
6842
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6943
public static int GetStripeIndex(CorrelationId correlationId)
70-
{
71-
return (int)((correlationId.ToInt64() & StripeMask) >>> StripeShift);
72-
}
44+
=> (int)(unchecked((ulong)correlationId.ToInt64() * HashFactor) >> (64 - StripeBits));
7345

7446
/// <summary>
75-
/// Gets the stripe index for the current thread. Use this when creating new CorrelationIds.
76-
/// </summary>
77-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
78-
public static int GetCurrentThreadStripeIndex()
79-
{
80-
return Environment.CurrentManagedThreadId & (StripeCount - 1);
81-
}
82-
83-
/// <summary>
84-
/// Gets the stripe for the given correlation id.
47+
/// Gets the stripe for the given callback id.
8548
/// </summary>
8649
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8750
private Stripe GetStripe(CorrelationId correlationId)
@@ -93,38 +56,38 @@ private Stripe GetStripe(CorrelationId correlationId)
9356
/// Attempts to add the specified key and value to the dictionary.
9457
/// </summary>
9558
[MethodImpl(MethodImplOptions.AggressiveInlining)]
96-
public bool TryAdd(CorrelationId key, TValue value)
59+
public bool TryAdd(GrainId owner, CorrelationId id, TValue value)
9760
{
98-
var stripe = GetStripe(key);
61+
var stripe = GetStripe(id);
9962
lock (stripe.Lock)
10063
{
101-
return stripe.Dictionary.TryAdd(key, value);
64+
return stripe.Dictionary.TryAdd(new(owner, id), value);
10265
}
10366
}
10467

10568
/// <summary>
10669
/// Attempts to get the value associated with the specified key.
10770
/// </summary>
10871
[MethodImpl(MethodImplOptions.AggressiveInlining)]
109-
public bool TryGetValue(CorrelationId key, [NotNullWhen(true)] out TValue? value)
72+
public bool TryGetValue(GrainId owner, CorrelationId id, [NotNullWhen(true)] out TValue? value)
11073
{
111-
var stripe = GetStripe(key);
74+
var stripe = GetStripe(id);
11275
lock (stripe.Lock)
11376
{
114-
return stripe.Dictionary.TryGetValue(key, out value);
77+
return stripe.Dictionary.TryGetValue(new(owner, id), out value);
11578
}
11679
}
11780

11881
/// <summary>
11982
/// Attempts to remove the value with the specified key.
12083
/// </summary>
12184
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122-
public bool TryRemove(CorrelationId key, [NotNullWhen(true)] out TValue? value)
85+
public bool TryRemove(GrainId owner, CorrelationId id, [NotNullWhen(true)] out TValue? value)
12386
{
124-
var stripe = GetStripe(key);
87+
var stripe = GetStripe(id);
12588
lock (stripe.Lock)
12689
{
127-
return stripe.Dictionary.Remove(key, out value);
90+
return stripe.Dictionary.Remove(new(owner, id), out value);
12891
}
12992
}
13093

@@ -150,16 +113,16 @@ public int Count
150113
/// <summary>
151114
/// Counts items matching a predicate across all stripes.
152115
/// </summary>
153-
public int CountWhere(Func<KeyValuePair<CorrelationId, TValue>, bool> predicate)
116+
public int CountWhere(Func<TValue, bool> predicate)
154117
{
155118
int count = 0;
156119
foreach (var stripe in _stripes)
157120
{
158121
lock (stripe.Lock)
159122
{
160-
foreach (var kvp in stripe.Dictionary)
123+
foreach (var value in stripe.Dictionary.Values)
161124
{
162-
if (predicate(kvp))
125+
if (predicate(value))
163126
{
164127
count++;
165128
}
@@ -175,21 +138,23 @@ public int CountWhere(Func<KeyValuePair<CorrelationId, TValue>, bool> predicate)
175138
/// </summary>
176139
public Enumerator GetEnumerator() => new(this);
177140

178-
IEnumerator<KeyValuePair<CorrelationId, TValue>> IEnumerable<KeyValuePair<CorrelationId, TValue>>.GetEnumerator() => GetEnumerator();
141+
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => GetEnumerator();
179142

180143
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
181144

182145
private sealed class Stripe
183146
{
184147
public readonly object Lock = new();
185-
public readonly Dictionary<CorrelationId, TValue> Dictionary = new();
148+
public readonly Dictionary<CallbackKey, TValue> Dictionary = new();
186149
}
187150

188-
public sealed class Enumerator : IEnumerator<KeyValuePair<CorrelationId, TValue>>
151+
private readonly record struct CallbackKey(GrainId Owner, CorrelationId Id);
152+
153+
public sealed class Enumerator : IEnumerator<TValue>
189154
{
190155
private readonly StripedCallbackDictionary<TValue> _dictionary;
191156
private int _stripeIndex;
192-
private KeyValuePair<CorrelationId, TValue>[]? _currentSnapshot;
157+
private TValue[]? _currentSnapshot;
193158
private int _snapshotCount;
194159
private int _snapshotIndex;
195160

@@ -202,7 +167,7 @@ internal Enumerator(StripedCallbackDictionary<TValue> dictionary)
202167
_snapshotIndex = -1;
203168
}
204169

205-
public KeyValuePair<CorrelationId, TValue> Current => _currentSnapshot![_snapshotIndex];
170+
public TValue Current => _currentSnapshot![_snapshotIndex];
206171

207172
object IEnumerator.Current => Current;
208173

@@ -236,11 +201,11 @@ public bool MoveNext()
236201
{
237202
if (stripe.Dictionary.Count > 0)
238203
{
239-
_currentSnapshot = ArrayPool<KeyValuePair<CorrelationId, TValue>>.Shared.Rent(stripe.Dictionary.Count);
204+
_currentSnapshot = ArrayPool<TValue>.Shared.Rent(stripe.Dictionary.Count);
240205
_snapshotCount = 0;
241-
foreach (var pair in stripe.Dictionary)
206+
foreach (var value in stripe.Dictionary.Values)
242207
{
243-
_currentSnapshot[_snapshotCount++] = pair;
208+
_currentSnapshot[_snapshotCount++] = value;
244209
}
245210
_snapshotIndex = -1;
246211
}
@@ -265,9 +230,9 @@ private void ReturnSnapshot()
265230
{
266231
if (_currentSnapshot is { } snapshot)
267232
{
268-
ArrayPool<KeyValuePair<CorrelationId, TValue>>.Shared.Return(
233+
ArrayPool<TValue>.Shared.Return(
269234
snapshot,
270-
clearArray: RuntimeHelpers.IsReferenceOrContainsReferences<KeyValuePair<CorrelationId, TValue>>());
235+
clearArray: RuntimeHelpers.IsReferenceOrContainsReferences<TValue>());
271236
_currentSnapshot = null;
272237
_snapshotCount = 0;
273238
}

src/Orleans.Core/Runtime/OutsideRuntimeClient.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public OutsideRuntimeClient(
9292
TimeSpan.FromSeconds(1)));
9393
this.callbackTimer = new PeriodicTimer(period, timeProvider);
9494
this.sharedCallbackData = new SharedCallbackData(
95-
msg => this.UnregisterCallback(msg.Id),
95+
msg => this.UnregisterCallback(msg.SendingGrain, msg.Id),
9696
this.loggerFactory.CreateLogger<CallbackData>(),
9797
this.clientMessagingOptions.ResponseTimeout,
9898
this.clientMessagingOptions.CancelRequestOnTimeout,
@@ -296,7 +296,7 @@ public void SendRequest(GrainReference target, IInvokable request, IResponseComp
296296
return;
297297
}
298298

299-
callbacks.TryAdd(message.Id, callbackData);
299+
callbacks.TryAdd(message.SendingGrain, message.Id, callbackData);
300300
callbackData.SubscribeForCancellation(cancellationToken);
301301

302302
if (Volatile.Read(ref _isStopping) != 0)
@@ -327,7 +327,7 @@ public void ReceiveResponse(Message response)
327327
if (response.Result is Message.ResponseTypes.Status)
328328
{
329329
var status = (StatusResponse)response.BodyObject!;
330-
callbacks.TryGetValue(response.Id, out var callback);
330+
callbacks.TryGetValue(response.TargetGrain, response.Id, out var callback);
331331
var request = callback?.Message;
332332
if (request is not null)
333333
{
@@ -360,7 +360,7 @@ public void ReceiveResponse(Message response)
360360
}
361361

362362
CallbackData? callbackData;
363-
var found = callbacks.TryRemove(response.Id, out callbackData);
363+
var found = callbacks.TryRemove(response.TargetGrain, response.Id, out callbackData);
364364
if (found)
365365
{
366366
// We need to import the RequestContext here as well.
@@ -374,9 +374,9 @@ public void ReceiveResponse(Message response)
374374
}
375375
}
376376

377-
private void UnregisterCallback(CorrelationId id)
377+
private void UnregisterCallback(GrainId owner, CorrelationId id)
378378
{
379-
callbacks.TryRemove(id, out _);
379+
callbacks.TryRemove(owner, id, out _);
380380
}
381381

382382
private void ConstructorReset()
@@ -448,16 +448,16 @@ public void BreakOutstandingMessagesToSilo(SiloAddress deadSilo)
448448
{
449449
foreach (var callback in callbacks)
450450
{
451-
if (deadSilo.Equals(callback.Value.Message.TargetSilo))
451+
if (deadSilo.Equals(callback.Message.TargetSilo))
452452
{
453-
callback.Value.OnTargetSiloFail();
453+
callback.OnTargetSiloFail();
454454
}
455455
}
456456
}
457457

458458
private void BreakOutstandingMessages()
459459
{
460-
foreach (var (_, callback) in callbacks)
460+
foreach (var callback in callbacks)
461461
{
462462
try
463463
{
@@ -471,7 +471,7 @@ private void BreakOutstandingMessages()
471471
}
472472

473473
public int GetRunningRequestsCount(GrainInterfaceType grainInterfaceType)
474-
=> this.callbacks.CountWhere(c => c.Value.Message.InterfaceType == grainInterfaceType);
474+
=> this.callbacks.CountWhere(c => c.Message.InterfaceType == grainInterfaceType);
475475

476476
/// <inheritdoc />
477477
public void NotifyClusterConnectionLost()
@@ -515,7 +515,7 @@ private async Task MonitorCallbackExpiry()
515515
try
516516
{
517517
var currentStopwatchTicks = ValueStopwatch.GetTimestamp();
518-
foreach (var (_, callback) in callbacks)
518+
foreach (var callback in callbacks)
519519
{
520520
if (callback.IsCompleted)
521521
{

0 commit comments

Comments
 (0)