Skip to content

Commit 6e0ae3c

Browse files
ReubenBondCopilot
andcommitted
fix(runtime): retain nullable callback invariants
Port striped callback storage onto the current nullable-safe runtime clients and clear pooled snapshots only when their entries contain references. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent df88baf commit 6e0ae3c

3 files changed

Lines changed: 123 additions & 90 deletions

File tree

src/Orleans.Core/Messaging/StripedCallbackDictionary.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Buffers;
44
using System.Collections;
55
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Runtime.CompilerServices;
78

89
namespace Orleans.Runtime;
@@ -13,6 +14,7 @@ namespace Orleans.Runtime;
1314
/// </summary>
1415
/// <typeparam name="TValue">The type of values stored in the dictionary.</typeparam>
1516
internal sealed class StripedCallbackDictionary<TValue> : IEnumerable<KeyValuePair<CorrelationId, TValue>>
17+
where TValue : notnull
1618
{
1719
/// <summary>
1820
/// The number of bits used to identify the stripe (stored in the upper bits of the CorrelationId).
@@ -104,7 +106,7 @@ public bool TryAdd(CorrelationId key, TValue value)
104106
/// Attempts to get the value associated with the specified key.
105107
/// </summary>
106108
[MethodImpl(MethodImplOptions.AggressiveInlining)]
107-
public bool TryGetValue(CorrelationId key, out TValue? value)
109+
public bool TryGetValue(CorrelationId key, [NotNullWhen(true)] out TValue? value)
108110
{
109111
var stripe = GetStripe(key);
110112
lock (stripe.Lock)
@@ -117,7 +119,7 @@ public bool TryGetValue(CorrelationId key, out TValue? value)
117119
/// Attempts to remove the value with the specified key.
118120
/// </summary>
119121
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120-
public bool TryRemove(CorrelationId key, out TValue? value)
122+
public bool TryRemove(CorrelationId key, [NotNullWhen(true)] out TValue? value)
121123
{
122124
var stripe = GetStripe(key);
123125
lock (stripe.Lock)
@@ -263,7 +265,9 @@ private void ReturnSnapshot()
263265
{
264266
if (_currentSnapshot is { } snapshot)
265267
{
266-
ArrayPool<KeyValuePair<CorrelationId, TValue>>.Shared.Return(snapshot, clearArray: true);
268+
ArrayPool<KeyValuePair<CorrelationId, TValue>>.Shared.Return(
269+
snapshot,
270+
clearArray: RuntimeHelpers.IsReferenceOrContainsReferences<KeyValuePair<CorrelationId, TValue>>());
267271
_currentSnapshot = null;
268272
_snapshotCount = 0;
269273
}

src/Orleans.Core/Runtime/OutsideRuntimeClient.cs

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections.Concurrent;
32
using System.Collections.Generic;
43
using System.Linq;
54
using System.Threading;
@@ -16,7 +15,6 @@
1615
using Orleans.Serialization.Invocation;
1716
using static Orleans.Internal.StandardExtensions;
1817

19-
#nullable disable
2018
namespace Orleans
2119
{
2220
internal partial class OutsideRuntimeClient : IRuntimeClient, IDisposable, IClusterConnectionStatusListener
@@ -27,43 +25,43 @@ internal partial class OutsideRuntimeClient : IRuntimeClient, IDisposable, IClus
2725
private readonly ClientMessagingOptions clientMessagingOptions;
2826

2927
private readonly StripedCallbackDictionary<CallbackData> callbacks;
30-
private InvokableObjectManager localObjects;
28+
private InvokableObjectManager? localObjects;
3129
private int _isStopping;
3230
private bool disposing;
3331
private bool disposed;
3432

3533
private readonly MessagingTrace messagingTrace;
3634
private readonly InterfaceToImplementationMappingCache _interfaceToImplementationMapping;
3735
private readonly ApplicationRequestInstruments _applicationRequestInstruments;
38-
private IGrainCallCancellationManager _cancellationManager;
39-
private IClusterConnectionStatusObserver[] _statusObservers;
36+
private IGrainCallCancellationManager? _cancellationManager;
37+
private IClusterConnectionStatusObserver[]? _statusObservers;
4038

41-
public IInternalGrainFactory InternalGrainFactory { get; private set; }
39+
public IInternalGrainFactory InternalGrainFactory { get; private set; } = null!;
4240

43-
private ClientClusterManifestProvider _manifestProvider;
44-
private MessageFactory messageFactory;
41+
private ClientClusterManifestProvider? _manifestProvider;
42+
private MessageFactory? messageFactory;
4543
private readonly LocalClientDetails _localClientDetails;
4644
private readonly ILoggerFactory loggerFactory;
4745

4846
private readonly SharedCallbackData sharedCallbackData;
4947
private readonly PeriodicTimer callbackTimer;
50-
private Task callbackTimerTask;
48+
private Task? callbackTimerTask;
5149

5250
public GrainAddress CurrentActivationAddress
5351
{
5452
get;
5553
private set;
56-
}
57-
public ClientGatewayObserver gatewayObserver { get; private set; }
54+
} = null!;
55+
public ClientGatewayObserver? gatewayObserver { get; private set; }
5856

5957
public string CurrentActivationIdentity
6058
{
6159
get { return CurrentActivationAddress.ToString(); }
6260
}
6361

64-
public IGrainReferenceRuntime GrainReferenceRuntime { get; private set; }
62+
public IGrainReferenceRuntime GrainReferenceRuntime { get; private set; } = null!;
6563

66-
internal ClientMessageCenter MessageCenter { get; private set; }
64+
internal ClientMessageCenter? MessageCenter { get; private set; }
6765

6866
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
6967
Justification = "MessageCenter is IDisposable but cannot call Dispose yet as it lives past the end of this method call.")]
@@ -158,6 +156,9 @@ public async Task StopAsync(CancellationToken cancellationToken)
158156
{
159157
Volatile.Write(ref _isStopping, 1);
160158
this.callbackTimer.Dispose();
159+
160+
// Fault callbacks before any cancellation-sensitive waits. Completing them can resume code
161+
// which issues follow-up calls, so request admission must already be closed.
161162
BreakOutstandingMessages();
162163

163164
if (this.callbackTimerTask is { } task)
@@ -192,20 +193,20 @@ await ExecuteWithRetries(
192193
MessageCenter = ActivatorUtilities.CreateInstance<ClientMessageCenter>(this.ServiceProvider);
193194
MessageCenter.RegisterLocalMessageHandler(this.HandleMessage);
194195
await ExecuteWithRetries(
195-
async () => await MessageCenter.StartAsync(cancellationToken),
196+
async () => await MessageCenter!.StartAsync(cancellationToken),
196197
retryFilter,
197198
cancellationToken);
198199
CurrentActivationAddress = GrainAddress.NewActivationAddress(MessageCenter.MyAddress, _localClientDetails.ClientId.GrainId);
199200

200201
this.gatewayObserver = new ClientGatewayObserver(gatewayManager);
201-
this.InternalGrainFactory.CreateObjectReference<IClientGatewayObserver>(this.gatewayObserver);
202+
this.InternalGrainFactory.CreateObjectReference<IClientGatewayObserver>(this.gatewayObserver!);
202203

203204
await ExecuteWithRetries(
204-
_manifestProvider.StartAsync,
205+
_manifestProvider!.StartAsync,
205206
retryFilter,
206207
cancellationToken);
207208

208-
static async Task ExecuteWithRetries(Func<Task> task, IClientConnectionRetryFilter retryFilter, CancellationToken cancellationToken)
209+
static async Task ExecuteWithRetries(Func<Task> task, IClientConnectionRetryFilter? retryFilter, CancellationToken cancellationToken)
209210
{
210211
do
211212
{
@@ -239,7 +240,7 @@ private void HandleMessage(Message message)
239240
case Message.Directions.OneWay:
240241
case Message.Directions.Request:
241242
{
242-
this.localObjects.Dispatch(message);
243+
this.localObjects!.Dispatch(message);
243244
break;
244245
}
245246
default:
@@ -251,19 +252,19 @@ private void HandleMessage(Message message)
251252
public void SendResponse(Message request, Response response)
252253
{
253254
ThrowIfDisposed();
254-
var message = this.messageFactory.CreateResponseMessage(request);
255+
var message = this.messageFactory!.CreateResponseMessage(request);
255256
OrleansOutsideRuntimeClientEvent.Instance.SendResponse(message);
256257
message.BodyObject = response;
257258

258-
MessageCenter.SendMessage(message);
259+
MessageCenter!.SendMessage(message);
259260
}
260261

261-
public void SendRequest(GrainReference target, IInvokable request, IResponseCompletionSource context, InvokeMethodOptions options)
262+
public void SendRequest(GrainReference target, IInvokable request, IResponseCompletionSource? context, InvokeMethodOptions options)
262263
{
263264
ThrowIfDisposed();
264265
var cancellationToken = request.GetCancellationToken();
265266
cancellationToken.ThrowIfCancellationRequested();
266-
var message = this.messageFactory.CreateMessage(request, options);
267+
var message = this.messageFactory!.CreateMessage(request, options);
267268
OrleansOutsideRuntimeClientEvent.Instance.SendRequest(message);
268269

269270
message.InterfaceType = target.InterfaceType;
@@ -288,7 +289,7 @@ public void SendRequest(GrainReference target, IInvokable request, IResponseComp
288289

289290
if (!oneWay)
290291
{
291-
var callbackData = new CallbackData(this.sharedCallbackData, context, message, _applicationRequestInstruments);
292+
var callbackData = new CallbackData(this.sharedCallbackData, context!, message, _applicationRequestInstruments);
292293
if (Volatile.Read(ref _isStopping) != 0)
293294
{
294295
callbackData.OnHostShutdown();
@@ -314,7 +315,7 @@ public void SendRequest(GrainReference target, IInvokable request, IResponseComp
314315
}
315316

316317
LogSendingMessage(logger, message);
317-
MessageCenter.SendMessage(message);
318+
MessageCenter!.SendMessage(message);
318319
}
319320

320321
public void ReceiveResponse(Message response)
@@ -325,12 +326,12 @@ public void ReceiveResponse(Message response)
325326

326327
if (response.Result is Message.ResponseTypes.Status)
327328
{
328-
var status = (StatusResponse)response.BodyObject;
329+
var status = (StatusResponse)response.BodyObject!;
329330
callbacks.TryGetValue(response.Id, out var callback);
330331
var request = callback?.Message;
331332
if (request is not null)
332333
{
333-
callback.OnStatusUpdate(status);
334+
callback!.OnStatusUpdate(status);
334335
if (status.Diagnostics != null && status.Diagnostics.Count > 0)
335336
{
336337
LogReceivedStatusUpdateForPendingRequest(logger, request, new(status.Diagnostics));
@@ -358,14 +359,14 @@ public void ReceiveResponse(Message response)
358359
return;
359360
}
360361

361-
CallbackData callbackData;
362+
CallbackData? callbackData;
362363
var found = callbacks.TryRemove(response.Id, out callbackData);
363364
if (found)
364365
{
365366
// We need to import the RequestContext here as well.
366367
// Unfortunately, it is not enough, since CallContext.LogicalGetData will not flow "up" from task completion source into the resolved task.
367368
// RequestContextExtensions.Import(response.RequestContextData);
368-
callbackData.DoCallback(response);
369+
callbackData!.DoCallback(response);
369370
}
370371
else
371372
{
@@ -402,7 +403,7 @@ public IAddressable CreateObjectReference(IAddressable obj)
402403
: ObserverGrainId.Create(_localClientDetails.ClientId);
403404
var reference = this.InternalGrainFactory.GetGrain(observerId.GrainId);
404405

405-
if (!localObjects.TryRegister(obj, observerId))
406+
if (!localObjects!.TryRegister(obj, observerId))
406407
{
407408
throw new ArgumentException($"Failed to add new observer {reference} to localObjects collection.", "reference");
408409
}
@@ -422,7 +423,7 @@ public void DeleteObjectReference(IAddressable obj)
422423
throw new ArgumentException($"Reference {reference.GrainId} is not an observer reference");
423424
}
424425

425-
if (!localObjects.TryDeregister(observerId))
426+
if (!localObjects!.TryDeregister(observerId))
426427
{
427428
throw new ArgumentException("Reference is not associated with a local object.", "reference");
428429
}
@@ -475,7 +476,7 @@ public int GetRunningRequestsCount(GrainInterfaceType grainInterfaceType)
475476
/// <inheritdoc />
476477
public void NotifyClusterConnectionLost()
477478
{
478-
foreach (var observer in _statusObservers)
479+
foreach (var observer in _statusObservers!)
479480
{
480481
try
481482
{
@@ -491,7 +492,7 @@ public void NotifyClusterConnectionLost()
491492
/// <inheritdoc />
492493
public void NotifyGatewayCountChanged(int currentNumberOfGateways, int previousNumberOfGateways)
493494
{
494-
foreach (var observer in _statusObservers)
495+
foreach (var observer in _statusObservers!)
495496
{
496497
try
497498
{

0 commit comments

Comments
 (0)