fix(host): resolve Host-012..015 — consume DownIfAlone in HOCON, sub-second timing precision, config-driven Serilog sinks, transient-only startup retry

This commit is contained in:
Joseph Doherty
2026-05-17 03:18:33 -04:00
parent eae4077414
commit aca65e85bb
9 changed files with 395 additions and 33 deletions

View File

@@ -37,7 +37,8 @@ public class HoconBuilderTests
};
var hocon = AkkaHostedService.BuildHocon(
node, DefaultCluster(), new[] { "Central" }, 5, 15);
node, DefaultCluster(), new[] { "Central" },
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
var config = ConfigurationFactory.ParseString(hocon);
Assert.Equal("central-node1", config.GetString("akka.remote.dot-netty.tcp.hostname"));
@@ -57,7 +58,8 @@ public class HoconBuilderTests
};
var hocon = AkkaHostedService.BuildHocon(
node, DefaultCluster(), new[] { "Central" }, 5, 15);
node, DefaultCluster(), new[] { "Central" },
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
// Must still parse, and the keys after the hostname must remain intact.
var config = ConfigurationFactory.ParseString(hocon);
@@ -79,9 +81,83 @@ public class HoconBuilderTests
};
var hocon = AkkaHostedService.BuildHocon(
node, cluster, new[] { "Central" }, 5, 15);
node, cluster, new[] { "Central" },
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
var config = ConfigurationFactory.ParseString(hocon);
Assert.Equal("keep oldest", config.GetString("akka.cluster.split-brain-resolver.active-strategy"));
}
private static NodeOptions DefaultNode() => new()
{
Role = "Central",
NodeHostname = "node1",
RemotingPort = 8081,
};
[Fact]
public void BuildHocon_DownIfAloneTrue_EmitsOn()
{
// Host-012: BuildHocon must consume ClusterOptions.DownIfAlone, not
// hard-code the down-if-alone token.
var cluster = DefaultCluster();
cluster.DownIfAlone = true;
var hocon = AkkaHostedService.BuildHocon(
DefaultNode(), cluster, new[] { "Central" },
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
var config = ConfigurationFactory.ParseString(hocon);
Assert.True(config.GetBoolean(
"akka.cluster.split-brain-resolver.keep-oldest.down-if-alone"));
}
[Fact]
public void BuildHocon_DownIfAloneFalse_EmitsOff()
{
// Host-012: setting DownIfAlone=false must actually flip the emitted token.
var cluster = DefaultCluster();
cluster.DownIfAlone = false;
var hocon = AkkaHostedService.BuildHocon(
DefaultNode(), cluster, new[] { "Central" },
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
var config = ConfigurationFactory.ParseString(hocon);
Assert.False(config.GetBoolean(
"akka.cluster.split-brain-resolver.keep-oldest.down-if-alone"));
}
[Fact]
public void BuildHocon_SubSecondTimings_PreservedWithoutRounding()
{
// Host-013: cluster timing values below one second must not be rounded to
// whole seconds. A 750ms heartbeat must survive as 750ms, not collapse to
// 1s — and a 500ms value must not collapse to a degenerate 0s.
var cluster = DefaultCluster();
cluster.HeartbeatInterval = TimeSpan.FromMilliseconds(750);
cluster.FailureDetectionThreshold = TimeSpan.FromMilliseconds(2600);
cluster.StableAfter = TimeSpan.FromMilliseconds(500);
var hocon = AkkaHostedService.BuildHocon(
DefaultNode(), cluster, new[] { "Central" },
TimeSpan.FromMilliseconds(2500), TimeSpan.FromMilliseconds(7500));
var config = ConfigurationFactory.ParseString(hocon);
Assert.Equal(
TimeSpan.FromMilliseconds(750),
config.GetTimeSpan("akka.cluster.failure-detector.heartbeat-interval"));
Assert.Equal(
TimeSpan.FromMilliseconds(2600),
config.GetTimeSpan("akka.cluster.failure-detector.acceptable-heartbeat-pause"));
Assert.Equal(
TimeSpan.FromMilliseconds(500),
config.GetTimeSpan("akka.cluster.split-brain-resolver.stable-after"));
Assert.Equal(
TimeSpan.FromMilliseconds(2500),
config.GetTimeSpan("akka.remote.transport-failure-detector.heartbeat-interval"));
Assert.Equal(
TimeSpan.FromMilliseconds(7500),
config.GetTimeSpan("akka.remote.transport-failure-detector.acceptable-heartbeat-pause"));
}
}