fix(host): fatal-log, flush and exit non-zero when the host fails to start (#618)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -262,7 +262,10 @@ public sealed class OtOpcUaServerHostedService : IHostedService, IAsyncDisposabl
|
||||
_logger.LogWarning(
|
||||
"OtOpcUaServerHostedService: node ACL enforcement is ENABLED — EVERY data-plane operation "
|
||||
+ "(read, browse, subscribe, write, alarm commands, HistoryRead) now requires a matching "
|
||||
+ "NodeAcl grant. A principal with no grant can connect and see nothing.");
|
||||
+ "NodeAcl grant. A principal with no grant can still connect and read the standard OPC UA "
|
||||
+ "server nodes (ns=0: ServerStatus, NamespaceArray, ServiceLevel, ...) — those are served by "
|
||||
+ "the SDK core node manager and are deliberately ungated because clients need them for "
|
||||
+ "session setup — but it sees no plant data (raw/UNS namespaces).");
|
||||
}
|
||||
|
||||
// Propagate the configured tie-cluster over-fetch bound onto the node manager so HistoryRead-Raw
|
||||
|
||||
@@ -57,6 +57,14 @@ using ZB.MOM.WW.Secrets.DependencyInjection;
|
||||
using ZB.MOM.WW.Secrets.Replicator.AkkaDotNet;
|
||||
using ZB.MOM.WW.Secrets.Sqlite;
|
||||
|
||||
// #618 — the composition root runs inside one try/catch (not re-indented, to keep the diff reviewable)
|
||||
// so ANY failure to start — an unknown role, a ValidateOnStart rejection, a hosted service that
|
||||
// throws from StartAsync — is logged Fatal, flushed, and ends the process with a NON-ZERO exit code.
|
||||
// Left as an unhandled exception the host could stay up as a zombie under a supervisor/container
|
||||
// (partially-started Akka threads), reporting "running" while serving nothing. See the catch at the
|
||||
// bottom of this file.
|
||||
try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// The SINGLE role authority: Cluster:Roles first, OTOPCUA_ROLES as the fallback (archreview
|
||||
@@ -740,6 +748,23 @@ if (LogPipelineDiagnostics.CheckFileSinkGrowth(app.Configuration,
|
||||
}
|
||||
|
||||
await app.RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log.Logger is still the silent bootstrap logger when the failure happened before AddZbSerilog
|
||||
// ran (e.g. EffectiveRoles.Resolve), so ALSO write to stderr — the one channel that always exists.
|
||||
Log.Fatal(ex, "OtOpcUa.Host terminated during startup/run");
|
||||
Console.Error.WriteLine($"FATAL: OtOpcUa.Host terminated: {ex}");
|
||||
Log.CloseAndFlush();
|
||||
// Exit explicitly: non-background threads left by a partially-started host (Akka, Kestrel) would
|
||||
// otherwise keep the process alive after the failure. Failure path only — a clean shutdown returns
|
||||
// from RunAsync normally and never reaches here, so CoordinatedShutdown is not skipped.
|
||||
Environment.Exit(1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Host
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user