fix(mesh): memoize dialer/provider on first use — a lost PreStart race self-heals at the first refresh (review-prescribed)

This commit is contained in:
Joseph Doherty
2026-08-06 08:07:34 -04:00
parent c0475f2df4
commit e54883a8e0
2 changed files with 44 additions and 16 deletions
@@ -73,6 +73,18 @@ public sealed class CentralCommunicationActor : ReceiveActor, IWithTimers
private IMeshCommandDialer? _dialer;
private IMeshPskProvider? _pskProvider;
// Memoize-on-first-use, NOT PreStart-only: PreStart runs on a dispatcher thread concurrently
// with the builder's remaining starters, so its resolve can lose a millisecond-wide race
// against AuditWriterActorKey's registration (starter #4) and Resolve() would swallow that
// into a PERMANENT silent null — the known-cluster gate never published, fail-open. These
// properties retry on first use instead; the first RefreshContacts arrives as a message,
// long after every starter finished, so a lost boot race self-heals there. Actor-thread
// only, so ??= needs no synchronisation; both are container singletons, so a repeat resolve
// is a dictionary hit.
private IMeshPskProvider? PskProvider => _pskProvider ??= Resolve(_pskProviderFactory, nameof(IMeshPskProvider));
private IMeshCommandDialer? Dialer => _dialer ??= Resolve(_dialerFactory, nameof(IMeshCommandDialer));
private readonly ILoggingAdapter _log = Context.GetLogger();
private readonly bool _useClusterClient;
private readonly bool _useGrpc;
@@ -382,7 +394,7 @@ public sealed class CentralCommunicationActor : ReceiveActor, IWithTimers
{
var commandType = cmd.Message.GetType().Name;
var dialer = _dialer;
var dialer = Dialer;
if (dialer is null)
{
_log.Error(
@@ -796,8 +808,8 @@ public sealed class CentralCommunicationActor : ReceiveActor, IWithTimers
/// </remarks>
private void PublishKnownClusters(ContactsLoaded msg)
{
_pskProvider?.SetKnownClusters(msg.ClusterIds);
_pskProvider?.InvalidateAll();
PskProvider?.SetKnownClusters(msg.ClusterIds);
PskProvider?.InvalidateAll();
}
/// <summary>Stops a cluster's existing client and creates a replacement for its new contact set.</summary>