diff --git a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Replicator.Grpc/ISecretsHubSyncStatus.cs b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Replicator.Grpc/ISecretsHubSyncStatus.cs
index dccc292..d4ad04c 100644
--- a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Replicator.Grpc/ISecretsHubSyncStatus.cs
+++ b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Replicator.Grpc/ISecretsHubSyncStatus.cs
@@ -36,21 +36,46 @@ public interface ISecretsHubSyncStatus
}
/// The mutable implementation the sync service writes to; consumers see only the interface.
+///
+/// Writes (/) run under ,
+/// and every getter takes the same lock rather than reading its backing field bare — a plain field
+/// read races the writer with no synchronization otherwise, and a torn/half-written
+/// (a multi-field struct) or an interleaving that pairs one write's
+/// LastSweepUtc with another write's LastError is exactly the inconsistency a status
+/// consumer must not see.
+///
internal sealed class SecretsHubSyncStatus : ISecretsHubSyncStatus
{
private readonly object _gate = new();
- ///
- public DateTimeOffset? LastSweepUtc { get; private set; }
+ private DateTimeOffset? _lastSweepUtc;
+ private DateTimeOffset? _lastSuccessUtc;
+ private int _consecutiveFailures;
+ private string? _lastError;
///
- public DateTimeOffset? LastSuccessUtc { get; private set; }
+ public DateTimeOffset? LastSweepUtc
+ {
+ get { lock (_gate) { return _lastSweepUtc; } }
+ }
///
- public int ConsecutiveFailures { get; private set; }
+ public DateTimeOffset? LastSuccessUtc
+ {
+ get { lock (_gate) { return _lastSuccessUtc; } }
+ }
///
- public string? LastError { get; private set; }
+ public int ConsecutiveFailures
+ {
+ get { lock (_gate) { return _consecutiveFailures; } }
+ }
+
+ ///
+ public string? LastError
+ {
+ get { lock (_gate) { return _lastError; } }
+ }
///
/// Records a successful sweep, resetting the failure streak.
@@ -64,10 +89,10 @@ internal sealed class SecretsHubSyncStatus : ISecretsHubSyncStatus
{
lock (_gate)
{
- int prior = ConsecutiveFailures;
- LastSweepUtc = LastSuccessUtc = now;
- ConsecutiveFailures = 0;
- LastError = null;
+ int prior = _consecutiveFailures;
+ _lastSweepUtc = _lastSuccessUtc = now;
+ _consecutiveFailures = 0;
+ _lastError = null;
return prior;
}
}
@@ -79,9 +104,9 @@ internal sealed class SecretsHubSyncStatus : ISecretsHubSyncStatus
{
lock (_gate)
{
- LastSweepUtc = now;
- ConsecutiveFailures++;
- LastError = ex.GetType().Name + ": " + ex.Message;
+ _lastSweepUtc = now;
+ _consecutiveFailures++;
+ _lastError = ex.GetType().Name + ": " + ex.Message;
}
}
}