Skip to content

Commit df88baf

Browse files
ReubenBondCopilot
andcommitted
fix(runtime): preserve callback shutdown boundaries
Close callback registration before shutdown sweeps and eagerly capture silo services so late responses cannot resolve disposed providers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 8a1f6a0 commit df88baf

2 files changed

Lines changed: 92 additions & 22 deletions

File tree

src/Orleans.Core/Runtime/OutsideRuntimeClient.cs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal partial class OutsideRuntimeClient : IRuntimeClient, IDisposable, IClus
2828

2929
private readonly StripedCallbackDictionary<CallbackData> callbacks;
3030
private InvokableObjectManager localObjects;
31+
private int _isStopping;
3132
private bool disposing;
3233
private bool disposed;
3334

@@ -155,12 +156,9 @@ public async Task StartAsync(CancellationToken cancellationToken)
155156

156157
public async Task StopAsync(CancellationToken cancellationToken)
157158
{
158-
foreach (var (_, callback) in callbacks)
159-
{
160-
callback.OnHostShutdown();
161-
}
162-
159+
Volatile.Write(ref _isStopping, 1);
163160
this.callbackTimer.Dispose();
161+
BreakOutstandingMessages();
164162

165163
if (this.callbackTimerTask is { } task)
166164
{
@@ -291,12 +289,28 @@ public void SendRequest(GrainReference target, IInvokable request, IResponseComp
291289
if (!oneWay)
292290
{
293291
var callbackData = new CallbackData(this.sharedCallbackData, context, message, _applicationRequestInstruments);
294-
callbackData.SubscribeForCancellation(cancellationToken);
292+
if (Volatile.Read(ref _isStopping) != 0)
293+
{
294+
callbackData.OnHostShutdown();
295+
return;
296+
}
297+
295298
callbacks.TryAdd(message.Id, callbackData);
299+
callbackData.SubscribeForCancellation(cancellationToken);
300+
301+
if (Volatile.Read(ref _isStopping) != 0)
302+
{
303+
callbackData.OnHostShutdown();
304+
return;
305+
}
296306
}
297307
else
298308
{
299309
context?.Complete();
310+
if (Volatile.Read(ref _isStopping) != 0)
311+
{
312+
return;
313+
}
300314
}
301315

302316
LogSendingMessage(logger, message);
@@ -418,8 +432,10 @@ public void Dispose()
418432
{
419433
if (this.disposing) return;
420434
this.disposing = true;
435+
Volatile.Write(ref _isStopping, 1);
421436

422437
Utils.SafeExecute(() => this.callbackTimer.Dispose());
438+
BreakOutstandingMessages();
423439

424440
Utils.SafeExecute(() => MessageCenter?.Dispose());
425441

@@ -438,6 +454,21 @@ public void BreakOutstandingMessagesToSilo(SiloAddress deadSilo)
438454
}
439455
}
440456

457+
private void BreakOutstandingMessages()
458+
{
459+
foreach (var (_, callback) in callbacks)
460+
{
461+
try
462+
{
463+
callback.OnHostShutdown();
464+
}
465+
catch (Exception exception)
466+
{
467+
LogErrorWhileProcessingCallbackExpiry(logger, exception);
468+
}
469+
}
470+
}
471+
441472
public int GetRunningRequestsCount(GrainInterfaceType grainInterfaceType)
442473
=> this.callbacks.CountWhere(c => c.Value.Message.InterfaceType == grainInterfaceType);
443474

src/Orleans.Runtime/Core/InsideRuntimeClient.cs

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ internal sealed partial class InsideRuntimeClient : IRuntimeClient, ILifecyclePa
3535
private readonly SharedCallbackData sharedCallbackData;
3636
private readonly SharedCallbackData systemSharedCallbackData;
3737
private readonly PeriodicTimer callbackTimer;
38+
private int _isStopping;
3839

3940
private GrainLocator grainLocator;
4041
private MessageCenter messageCenter;
@@ -44,7 +45,7 @@ internal sealed partial class InsideRuntimeClient : IRuntimeClient, ILifecyclePa
4445
private IGrainCallCancellationManager _cancellationManager;
4546
private HostedClient hostedClient;
4647

47-
private HostedClient HostedClient => this.hostedClient ??= this.ServiceProvider.GetRequiredService<HostedClient>();
48+
private HostedClient HostedClient => this.hostedClient;
4849
private readonly MessageFactory messageFactory;
4950
private IGrainReferenceRuntime grainReferenceRuntime;
5051
private Task callbackTimerTask;
@@ -110,15 +111,25 @@ public InsideRuntimeClient(
110111

111112
public GrainFactory ConcreteGrainFactory { get; }
112113

113-
private GrainLocator GrainLocator
114-
=> this.grainLocator ?? (this.grainLocator = this.ServiceProvider.GetRequiredService<GrainLocator>());
114+
private GrainLocator GrainLocator => this.grainLocator;
115115

116-
private List<IIncomingGrainCallFilter> GrainCallFilters
117-
=> this.grainCallFilters ??= new List<IIncomingGrainCallFilter>(this.ServiceProvider.GetServices<IIncomingGrainCallFilter>());
116+
private List<IIncomingGrainCallFilter> GrainCallFilters => this.grainCallFilters;
118117

119-
private MessageCenter MessageCenter => this.messageCenter ?? (this.messageCenter = this.ServiceProvider.GetRequiredService<MessageCenter>());
118+
private MessageCenter MessageCenter => this.messageCenter;
120119

121-
public IGrainReferenceRuntime GrainReferenceRuntime => this.grainReferenceRuntime ?? (this.grainReferenceRuntime = this.ServiceProvider.GetRequiredService<IGrainReferenceRuntime>());
120+
public IGrainReferenceRuntime GrainReferenceRuntime => this.grainReferenceRuntime;
121+
122+
internal void ConsumeServices()
123+
{
124+
this.grainLocator = this.ServiceProvider.GetRequiredService<GrainLocator>();
125+
this.grainCallFilters = new List<IIncomingGrainCallFilter>(this.ServiceProvider.GetServices<IIncomingGrainCallFilter>());
126+
this.messageCenter = this.ServiceProvider.GetRequiredService<MessageCenter>();
127+
this.grainReferenceRuntime = this.ServiceProvider.GetRequiredService<IGrainReferenceRuntime>();
128+
this.hostedClient = this.ServiceProvider.GetRequiredService<HostedClient>();
129+
_cancellationManager = this.ServiceProvider.GetRequiredService<IGrainCallCancellationManager>();
130+
sharedCallbackData.CancellationManager = _cancellationManager;
131+
systemSharedCallbackData.CancellationManager = _cancellationManager;
132+
}
122133

123134
public void SendRequest(
124135
GrainReference target,
@@ -170,18 +181,35 @@ public void SendRequest(
170181
}
171182

172183
var oneWay = (options & InvokeMethodOptions.OneWay) != 0;
184+
CallbackData callbackData = null;
173185
if (!oneWay)
174186
{
175187
Debug.Assert(context is not null);
176188

177189
// Register a callback for the request.
178-
var callbackData = new CallbackData(sharedData, context, message, _applicationRequestInstruments);
190+
callbackData = new CallbackData(sharedData, context, message, _applicationRequestInstruments);
191+
if (Volatile.Read(ref _isStopping) != 0)
192+
{
193+
callbackData.OnHostShutdown();
194+
return;
195+
}
196+
179197
callbacks.TryAdd(message.Id, callbackData);
180198
callbackData.SubscribeForCancellation(cancellationToken);
181199
}
182200
else
183201
{
184202
context?.Complete();
203+
if (Volatile.Read(ref _isStopping) != 0)
204+
{
205+
return;
206+
}
207+
}
208+
209+
if (Volatile.Read(ref _isStopping) != 0)
210+
{
211+
callbackData?.OnHostShutdown();
212+
return;
185213
}
186214

187215
this.messagingTrace.OnSendRequest(message);
@@ -497,18 +525,31 @@ public void DeleteObjectReference(IAddressable obj)
497525

498526
private async Task OnRuntimeInitializeStop(CancellationToken tc)
499527
{
500-
foreach (var (_, callback) in callbacks)
501-
{
502-
callback.OnHostShutdown();
503-
}
504-
528+
Volatile.Write(ref _isStopping, 1);
505529
this.callbackTimer.Dispose();
530+
BreakOutstandingMessages();
531+
506532
if (this.callbackTimerTask is { } task)
507533
{
508534
await task.WaitAsync(tc);
509535
}
510536
}
511537

538+
private void BreakOutstandingMessages()
539+
{
540+
foreach (var (_, callback) in callbacks)
541+
{
542+
try
543+
{
544+
callback.OnHostShutdown();
545+
}
546+
catch (Exception exception)
547+
{
548+
LogWarningWhileProcessingCallbackExpiry(this.logger, exception);
549+
}
550+
}
551+
}
552+
512553
private Task OnRuntimeInitializeStart(CancellationToken tc)
513554
{
514555
var stopWatch = ValueStopwatch.StartNew();
@@ -541,9 +582,7 @@ public void BreakOutstandingMessagesToSilo(SiloAddress deadSilo)
541582

542583
public void Participate(ISiloLifecycle lifecycle)
543584
{
544-
_cancellationManager = this.ServiceProvider.GetRequiredService<IGrainCallCancellationManager>();
545-
sharedCallbackData.CancellationManager = _cancellationManager;
546-
systemSharedCallbackData.CancellationManager = _cancellationManager;
585+
ConsumeServices();
547586
lifecycle.Subscribe<InsideRuntimeClient>(ServiceLifecycleStage.RuntimeInitialize, OnRuntimeInitializeStart, OnRuntimeInitializeStop);
548587
}
549588

0 commit comments

Comments
 (0)