Skip to content

Commit f6efcad

Browse files
committed
Fix PR #80 review follow-ups
1 parent b5ffa3b commit f6efcad

4 files changed

Lines changed: 70 additions & 14 deletions

File tree

DotPilot.Runtime.Host/Features/RuntimeFoundation/EmbeddedRuntimeHostBuilderExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ internal static void ConfigureSilo(ISiloBuilder siloBuilder, EmbeddedRuntimeHost
4343
cluster.ClusterId = options.ClusterId;
4444
cluster.ServiceId = options.ServiceId;
4545
});
46+
siloBuilder.AddStartupTask(
47+
static (serviceProvider, _) =>
48+
{
49+
serviceProvider
50+
.GetRequiredService<EmbeddedRuntimeHostCatalog>()
51+
.SetState(EmbeddedRuntimeHostState.Running);
52+
53+
return Task.CompletedTask;
54+
},
55+
ServiceLifecycleStage.Active);
4656
siloBuilder.AddMemoryGrainStorage(EmbeddedRuntimeHostNames.GrainStorageProviderName);
4757
siloBuilder.UseInMemoryReminderService();
4858
}

DotPilot.Runtime.Host/Features/RuntimeFoundation/EmbeddedRuntimeHostLifecycleService.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ namespace DotPilot.Runtime.Host.Features.RuntimeFoundation;
44

55
internal sealed class EmbeddedRuntimeHostLifecycleService(EmbeddedRuntimeHostCatalog catalog) : IHostedService
66
{
7-
public Task StartAsync(CancellationToken cancellationToken)
8-
{
9-
catalog.SetState(DotPilot.Core.Features.RuntimeFoundation.EmbeddedRuntimeHostState.Running);
10-
return Task.CompletedTask;
11-
}
7+
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
128

139
public Task StopAsync(CancellationToken cancellationToken)
1410
{

DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace DotPilot.Runtime.Features.ToolchainCenter;
55
internal static class ToolchainCommandProbe
66
{
77
private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(2);
8+
private static readonly TimeSpan RedirectDrainTimeout = TimeSpan.FromSeconds(1);
89
private const string VersionSeparator = "version";
910
private const string EmptyOutput = "";
1011

@@ -87,14 +88,19 @@ private static ToolchainCommandExecution Execute(string executablePath, IReadOnl
8788

8889
using (process)
8990
{
90-
var standardOutputTask = process.StandardOutput.ReadToEndAsync();
91-
var standardErrorTask = process.StandardError.ReadToEndAsync();
91+
var standardOutputTask = ObserveRedirectedStream(process.StandardOutput.ReadToEndAsync());
92+
var standardErrorTask = ObserveRedirectedStream(process.StandardError.ReadToEndAsync());
9293

9394
if (!process.WaitForExit((int)CommandTimeout.TotalMilliseconds))
9495
{
9596
TryTerminate(process);
96-
ObserveRedirectedStreamFaults(standardOutputTask, standardErrorTask);
97-
return new(true, false, EmptyOutput, EmptyOutput);
97+
WaitForTermination(process);
98+
99+
return new(
100+
true,
101+
false,
102+
AwaitStreamRead(standardOutputTask),
103+
AwaitStreamRead(standardErrorTask));
98104
}
99105

100106
return new(
@@ -105,10 +111,26 @@ private static ToolchainCommandExecution Execute(string executablePath, IReadOnl
105111
}
106112
}
107113

114+
private static Task<string> ObserveRedirectedStream(Task<string> readTask)
115+
{
116+
_ = readTask.ContinueWith(
117+
static task => _ = task.Exception,
118+
CancellationToken.None,
119+
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
120+
TaskScheduler.Default);
121+
122+
return readTask;
123+
}
124+
108125
private static string AwaitStreamRead(Task<string> readTask)
109126
{
110127
try
111128
{
129+
if (!readTask.Wait(RedirectDrainTimeout))
130+
{
131+
return EmptyOutput;
132+
}
133+
112134
return readTask.GetAwaiter().GetResult();
113135
}
114136
catch
@@ -117,19 +139,28 @@ private static string AwaitStreamRead(Task<string> readTask)
117139
}
118140
}
119141

120-
private static void ObserveRedirectedStreamFaults(Task<string> standardOutputTask, Task<string> standardErrorTask)
142+
private static void TryTerminate(Process process)
121143
{
122-
_ = standardOutputTask.Exception;
123-
_ = standardErrorTask.Exception;
144+
try
145+
{
146+
if (!process.HasExited)
147+
{
148+
process.Kill(entireProcessTree: true);
149+
}
150+
}
151+
catch
152+
{
153+
// Best-effort cleanup only.
154+
}
124155
}
125156

126-
private static void TryTerminate(Process process)
157+
private static void WaitForTermination(Process process)
127158
{
128159
try
129160
{
130161
if (!process.HasExited)
131162
{
132-
process.Kill(entireProcessTree: true);
163+
process.WaitForExit((int)RedirectDrainTimeout.TotalMilliseconds);
133164
}
134165
}
135166
catch

DotPilot.Tests/Features/RuntimeFoundation/EmbeddedRuntimeHostTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@ public async Task CatalogTransitionsToRunningStateAfterHostStartAsync()
6060
snapshot.State.Should().Be(EmbeddedRuntimeHostState.Running);
6161
}
6262

63+
[Test]
64+
public async Task LifecycleServiceDoesNotMarkTheCatalogRunningBeforeTheSiloStartupTaskRuns()
65+
{
66+
var options = CreateOptions();
67+
using var host = CreateHost(options);
68+
var lifecycleService = host.Services
69+
.GetServices<IHostedService>()
70+
.Single(service => service.GetType().Name == "EmbeddedRuntimeHostLifecycleService");
71+
72+
await lifecycleService.StartAsync(CancellationToken.None);
73+
74+
host.Services
75+
.GetRequiredService<IEmbeddedRuntimeHostCatalog>()
76+
.GetSnapshot()
77+
.State
78+
.Should()
79+
.Be(EmbeddedRuntimeHostState.Stopped);
80+
}
81+
6382
[Test]
6483
public async Task InitialGrainsReturnNullBeforeTheirFirstWrite()
6584
{

0 commit comments

Comments
 (0)