Skip to content

Commit fe34018

Browse files
committed
fix(runtime): return completion sources on send failure
1 parent c6301c5 commit fe34018

2 files changed

Lines changed: 94 additions & 2 deletions

File tree

src/Orleans.Core/Runtime/GrainReferenceRuntime.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private async ValueTask InvokeMethodWithFiltersAsync(GrainReference reference, I
120120

121121
private ValueTask<TResult?> InvokeMethodAsyncCore<TResult>(GrainReference reference, IInvokable request, InvokeMethodOptions options)
122122
{
123-
ResponseCompletionSource<TResult> responseCompletionSource;
123+
ResponseCompletionSource<TResult>? responseCompletionSource = null;
124124
try
125125
{
126126
SetGrainCancellationTokensTarget(reference, request);
@@ -130,6 +130,7 @@ private async ValueTask InvokeMethodWithFiltersAsync(GrainReference reference, I
130130
}
131131
catch
132132
{
133+
responseCompletionSource?.Reset();
133134
DisposeRequest(request);
134135
throw;
135136
}
@@ -149,7 +150,7 @@ private async ValueTask InvokeMethodWithFiltersAsync(GrainReference reference, I
149150

150151
private ValueTask InvokeMethodAsyncCore(GrainReference reference, IInvokable request, InvokeMethodOptions options)
151152
{
152-
ResponseCompletionSource responseCompletionSource;
153+
ResponseCompletionSource? responseCompletionSource = null;
153154
try
154155
{
155156
SetGrainCancellationTokensTarget(reference, request);
@@ -159,6 +160,7 @@ private ValueTask InvokeMethodAsyncCore(GrainReference reference, IInvokable req
159160
}
160161
catch
161162
{
163+
responseCompletionSource?.Reset();
162164
DisposeRequest(request);
163165
throw;
164166
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Orleans.CodeGeneration;
3+
using Orleans.Runtime;
4+
using Orleans.Serialization.Invocation;
5+
using TestExtensions;
6+
using UnitTests.Messaging;
7+
using Xunit;
8+
9+
namespace UnitTests.Runtime;
10+
11+
[TestSuite("BVT")]
12+
[TestProvider("None")]
13+
[TestArea("Runtime")]
14+
public class GrainReferenceRuntimeTests
15+
{
16+
[Fact, TestCategory("BVT")]
17+
public void TypedCompletionSourceReturnsToPoolWhenSendThrows()
18+
{
19+
var runtimeClient = new ThrowingRuntimeClient();
20+
var runtime = CreateRuntime(runtimeClient);
21+
var request = new TestInvokableRequest();
22+
23+
Assert.Throws<InvalidOperationException>(() => runtime.InvokeMethodAsync<int>(null!, request, InvokeMethodOptions.None));
24+
25+
var captured = Assert.IsType<ResponseCompletionSource<int>>(runtimeClient.Context);
26+
var returned = ResponseCompletionSourcePool.Get<int>();
27+
Assert.Same(captured, returned);
28+
Assert.Equal(1, request.DisposeCount);
29+
returned.Reset();
30+
}
31+
32+
[Fact, TestCategory("BVT")]
33+
public void UntypedCompletionSourceReturnsToPoolWhenSendThrows()
34+
{
35+
var runtimeClient = new ThrowingRuntimeClient();
36+
var runtime = CreateRuntime(runtimeClient);
37+
var request = new TestInvokableRequest();
38+
39+
Assert.Throws<InvalidOperationException>(() => runtime.InvokeMethodAsync(null!, request, InvokeMethodOptions.None));
40+
41+
var captured = Assert.IsType<ResponseCompletionSource>(runtimeClient.Context);
42+
var returned = ResponseCompletionSourcePool.Get();
43+
Assert.Same(captured, returned);
44+
Assert.Equal(1, request.DisposeCount);
45+
returned.Reset();
46+
}
47+
48+
private static GrainReferenceRuntime CreateRuntime(IRuntimeClient runtimeClient)
49+
=> new(runtimeClient, null!, [], null!, null!);
50+
51+
private sealed class ThrowingRuntimeClient : IRuntimeClient
52+
{
53+
public IResponseCompletionSource? Context { get; private set; }
54+
55+
public TimeProvider TimeProvider => TimeProvider.System;
56+
public IInternalGrainFactory InternalGrainFactory => throw new NotSupportedException();
57+
public string CurrentActivationIdentity => string.Empty;
58+
public IServiceProvider ServiceProvider => EmptyServiceProvider.Instance;
59+
public IGrainReferenceRuntime GrainReferenceRuntime => throw new NotSupportedException();
60+
61+
public TimeSpan GetResponseTimeout() => throw new NotSupportedException();
62+
63+
public void SetResponseTimeout(TimeSpan timeout) => throw new NotSupportedException();
64+
65+
public void SendRequest(GrainReference target, IInvokable request, IResponseCompletionSource? context, InvokeMethodOptions options)
66+
{
67+
Context = context;
68+
throw new InvalidOperationException("Send failed.");
69+
}
70+
71+
public void SendResponse(Message request, Response response) => throw new NotSupportedException();
72+
73+
public void ReceiveResponse(Message message) => throw new NotSupportedException();
74+
75+
public IAddressable CreateObjectReference(IAddressable obj) => throw new NotSupportedException();
76+
77+
public void DeleteObjectReference(IAddressable obj) => throw new NotSupportedException();
78+
79+
public void BreakOutstandingMessagesToSilo(SiloAddress deadSilo) => throw new NotSupportedException();
80+
81+
public int GetRunningRequestsCount(GrainInterfaceType grainInterfaceType) => 0;
82+
}
83+
84+
private sealed class EmptyServiceProvider : IServiceProvider
85+
{
86+
public static EmptyServiceProvider Instance { get; } = new();
87+
88+
public object? GetService(Type serviceType) => null;
89+
}
90+
}

0 commit comments

Comments
 (0)