Skip to content

Commit 63acb6c

Browse files
CopilotReubenBond
andcommitted
Add test validating grain observer creation during lifecycle
Co-authored-by: ReubenBond <203839+ReubenBond@users.noreply.github.com>
1 parent 85b7bfd commit 63acb6c

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Hosting;
3+
using Orleans.Runtime;
4+
using Orleans.TestingHost;
5+
using Orleans.TestingHost.Utils;
6+
using UnitTests.GrainInterfaces;
7+
using Xunit;
8+
9+
namespace DefaultCluster.Tests.General
10+
{
11+
/// <summary>
12+
/// Tests for creating grain observers during lifecycle participation.
13+
/// This validates that grain observers can be created during silo lifecycle stages,
14+
/// which previously failed because lifecycle events were executed on a SystemTarget
15+
/// (grain context), preventing CreateObjectReference from working.
16+
/// </summary>
17+
[TestCategory("BVT"), TestCategory("Lifecycle"), TestCategory("Observer")]
18+
public class LifecycleObserverCreationTests : IClassFixture<LifecycleObserverCreationTests.Fixture>
19+
{
20+
private readonly IHost _host;
21+
22+
public class Fixture : IAsyncLifetime
23+
{
24+
private readonly TestClusterPortAllocator portAllocator;
25+
public IHost Host { get; private set; }
26+
public static bool ObserverCreated { get; set; }
27+
28+
public Fixture()
29+
{
30+
portAllocator = new TestClusterPortAllocator();
31+
}
32+
33+
public async Task InitializeAsync()
34+
{
35+
ObserverCreated = false;
36+
var (siloPort, gatewayPort) = portAllocator.AllocateConsecutivePortPairs(1);
37+
Host = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder()
38+
.UseOrleans(siloBuilder =>
39+
{
40+
siloBuilder
41+
.UseLocalhostClustering(siloPort, gatewayPort)
42+
.ConfigureServices(services =>
43+
{
44+
services.AddSingleton<ILifecycleParticipant<ISiloLifecycle>, TestLifecycleParticipant>();
45+
});
46+
})
47+
.Build();
48+
await Host.StartAsync();
49+
}
50+
51+
public async Task DisposeAsync()
52+
{
53+
try
54+
{
55+
await Host.StopAsync();
56+
}
57+
finally
58+
{
59+
Host.Dispose();
60+
portAllocator.Dispose();
61+
}
62+
}
63+
}
64+
65+
/// <summary>
66+
/// Lifecycle participant that creates a grain observer during the Active stage.
67+
/// This simulates the issue scenario where a hosted service or lifecycle participant
68+
/// needs to create grain observers during silo startup.
69+
/// </summary>
70+
private class TestLifecycleParticipant : ILifecycleParticipant<ISiloLifecycle>
71+
{
72+
private readonly IGrainFactory _grainFactory;
73+
74+
public TestLifecycleParticipant(IGrainFactory grainFactory)
75+
{
76+
_grainFactory = grainFactory;
77+
}
78+
79+
public void Participate(ISiloLifecycle lifecycle)
80+
{
81+
lifecycle.Subscribe(
82+
nameof(TestLifecycleParticipant),
83+
ServiceLifecycleStage.Active,
84+
OnStart,
85+
OnStop);
86+
}
87+
88+
private Task OnStart(CancellationToken ct)
89+
{
90+
// This is the critical test - creating a grain observer during lifecycle participation
91+
// should work now that lifecycle events run via Task.Run instead of on a SystemTarget
92+
var observer = new TestObserver();
93+
var reference = _grainFactory.CreateObjectReference<ISimpleGrainObserver>(observer);
94+
95+
// Verify the reference was created successfully
96+
Assert.NotNull(reference);
97+
98+
// Clean up
99+
_grainFactory.DeleteObjectReference<ISimpleGrainObserver>(reference);
100+
101+
Fixture.ObserverCreated = true;
102+
return Task.CompletedTask;
103+
}
104+
105+
private Task OnStop(CancellationToken ct)
106+
{
107+
return Task.CompletedTask;
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Simple observer implementation for testing.
113+
/// </summary>
114+
private class TestObserver : ISimpleGrainObserver
115+
{
116+
public void StateChanged(int a, int b)
117+
{
118+
// No-op for testing
119+
}
120+
}
121+
122+
public LifecycleObserverCreationTests(Fixture fixture)
123+
{
124+
_host = fixture.Host;
125+
}
126+
127+
/// <summary>
128+
/// Tests that grain observers can be created during lifecycle participation.
129+
/// This validates the fix for the issue where CreateObjectReference would fail
130+
/// with "Cannot create a local object reference from a grain" when called during
131+
/// lifecycle events, because those events were previously scheduled on a SystemTarget.
132+
/// </summary>
133+
[Fact]
134+
public void LifecycleParticipant_CanCreateGrainObserver()
135+
{
136+
// The test passes if the host started successfully and the observer was created
137+
// in the TestLifecycleParticipant.OnStart method without throwing an exception
138+
Assert.True(Fixture.ObserverCreated, "Observer should have been created during lifecycle participation");
139+
}
140+
141+
/// <summary>
142+
/// Tests that grains can be called from within lifecycle participants.
143+
/// This further validates that lifecycle events now run without a grain context,
144+
/// enabling normal grain calls and observer creation.
145+
/// </summary>
146+
[Fact]
147+
public async Task LifecycleParticipant_CanCallGrains()
148+
{
149+
var client = _host.Services.GetRequiredService<IClusterClient>();
150+
151+
// Call a grain to verify the silo is fully operational after lifecycle startup
152+
var grain = client.GetGrain<ISimpleGrain>(42);
153+
await grain.SetA(100);
154+
var value = await grain.GetA();
155+
156+
Assert.Equal(100, value);
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)