104 lines
4.3 KiB
C#
104 lines
4.3 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace Dada.Supervisor;
|
|
|
|
internal sealed class SupervisorRuntime : IAsyncDisposable
|
|
{
|
|
private readonly ICredentialStore credentials;
|
|
private ManagedComponentSupervisor? api;
|
|
private ManagedComponentSupervisor? worker;
|
|
private StructuredJsonlLogger? logger;
|
|
|
|
internal SupervisorRuntime(ICredentialStore credentials)
|
|
{
|
|
this.credentials = credentials;
|
|
}
|
|
|
|
internal event Action<SupervisorState>? StateChanged;
|
|
|
|
internal async Task<SupervisorState> StartAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
if (LoopbackPortGuard.Check() == SupervisorState.PortInUse) return SupervisorState.PortInUse;
|
|
var configuration = new InstanceConfigurationStore().Load();
|
|
if (configuration.LocalDataRoot is null || configuration.AssetRoot is null ||
|
|
!Directory.Exists(configuration.LocalDataRoot) || !Directory.Exists(configuration.AssetRoot))
|
|
{
|
|
return SupervisorState.StartupFailed;
|
|
}
|
|
try
|
|
{
|
|
logger = new StructuredJsonlLogger(Path.Combine(configuration.LocalDataRoot, "logs", "supervisor"), "supervisor");
|
|
logger.Write(new StructuredLogEvent("starting", ErrorCategory: "none"));
|
|
}
|
|
catch (LogWriteException)
|
|
{
|
|
return SupervisorState.StorageUnavailable;
|
|
}
|
|
if (CredentialCatalog.RequiredFor(ChildRole.Api).Concat(CredentialCatalog.RequiredFor(ChildRole.Worker)).Any(target => !credentials.IsConfigured(target)))
|
|
{
|
|
return SupervisorState.StartupFailed;
|
|
}
|
|
|
|
var node = Path.Combine(AppContext.BaseDirectory, "runtime", "node.exe");
|
|
var apiEntry = Path.Combine(AppContext.BaseDirectory, "server", "api.mjs");
|
|
var workerEntry = Path.Combine(AppContext.BaseDirectory, "server", "worker.mjs");
|
|
if (!File.Exists(node) || !File.Exists(apiEntry) || !File.Exists(workerEntry)) return SupervisorState.StartupFailed;
|
|
|
|
api = CreateComponent(node, apiEntry, ChildRole.Api, SupervisorState.ApiDegraded);
|
|
await api.StartAsync(cancellationToken);
|
|
|
|
worker = CreateComponent(node, workerEntry, ChildRole.Worker, SupervisorState.WorkerDegraded);
|
|
await worker.StartAsync(cancellationToken);
|
|
return TryLog(new StructuredLogEvent("ready", ErrorCategory: "none")) ? SupervisorState.Ready : SupervisorState.StorageUnavailable;
|
|
}
|
|
|
|
private ManagedComponentSupervisor CreateComponent(string node, string entry, ChildRole role, SupervisorState degradedState)
|
|
{
|
|
var component = new ManagedComponentSupervisor(async cancellationToken =>
|
|
{
|
|
var startInfo = new ProcessStartInfo(node);
|
|
startInfo.WorkingDirectory = AppContext.BaseDirectory;
|
|
startInfo.Environment["DADA_SQLITE_NATIVE_BINDING"] = Path.Combine(AppContext.BaseDirectory, "server", "native", "better_sqlite3.node");
|
|
startInfo.Environment["DADA_SUPPORT_GATE_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web", "support-gate");
|
|
startInfo.ArgumentList.Add(entry);
|
|
var child = await ManagedChildProcess.StartAsync(startInfo, role, credentials, cancellationToken);
|
|
child.StatusReceived += status =>
|
|
{
|
|
if (status == "storage_unavailable")
|
|
{
|
|
TryLog(new StructuredLogEvent("unavailable", ErrorCategory: "log_write_failed"));
|
|
StateChanged?.Invoke(SupervisorState.StorageUnavailable);
|
|
}
|
|
};
|
|
return child;
|
|
}, degradedState);
|
|
component.StateChanged += state =>
|
|
{
|
|
if (TryLog(new StructuredLogEvent("degraded", ErrorCategory: "service_unavailable"))) StateChanged?.Invoke(state);
|
|
};
|
|
return component;
|
|
}
|
|
|
|
private bool TryLog(StructuredLogEvent entry)
|
|
{
|
|
try
|
|
{
|
|
logger?.Write(entry);
|
|
return true;
|
|
}
|
|
catch (LogWriteException)
|
|
{
|
|
StateChanged?.Invoke(SupervisorState.StorageUnavailable);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
var stops = new List<Task>();
|
|
if (worker is not null) stops.Add(worker.DisposeAsync().AsTask());
|
|
if (api is not null) stops.Add(api.DisposeAsync().AsTask());
|
|
await Task.WhenAll(stops);
|
|
}
|
|
}
|