namespace ZB.MOM.NatsNet.Server.Internal; /// /// Provides an efficiently-cached Unix nanosecond timestamp updated every /// by a shared background timer. /// Register before use and Unregister when done; the timer shuts down when all /// registrants have unregistered. /// /// /// Mirrors the Go ats package. Intended for high-frequency cache /// access-time reads that do not need sub-100ms precision. /// public static class AccessTimeService { /// How often the cached time is refreshed. public static readonly TimeSpan TickInterval = TimeSpan.FromMilliseconds(100); private static long _utime; private static long _refs; private static Timer? _timer; private static readonly object _lock = new(); static AccessTimeService() { // Mirror Go's init(): nothing to pre-allocate in .NET. } /// /// Explicit init hook for Go parity. /// Mirrors package init() in server/ats/ats.go. /// This method is intentionally idempotent. /// public static void Init() { // Ensure a non-zero cached timestamp is present. var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L; Interlocked.CompareExchange(ref _utime, now, 0); } /// /// Registers a user. Starts the background timer when the first registrant calls this. /// Each call to must be paired with a call to . /// public static void Register() { var v = Interlocked.Increment(ref _refs); if (v == 1) { Interlocked.Exchange(ref _utime, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L); lock (_lock) { _timer?.Dispose(); _timer = new Timer(_ => { Interlocked.Exchange(ref _utime, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L); }, null, TickInterval, TickInterval); } } } /// /// Unregisters a user. Stops the background timer when the last registrant calls this. /// /// Thrown when unregister is called more times than register. public static void Unregister() { var v = Interlocked.Decrement(ref _refs); if (v == 0) { lock (_lock) { _timer?.Dispose(); _timer = null; } } else if (v < 0) { Interlocked.Exchange(ref _refs, 0); throw new InvalidOperationException("ats: unbalanced unregister for access time state"); } } /// /// Returns the last cached Unix nanosecond timestamp. /// If no registrant is active, returns a fresh timestamp (avoids returning zero). /// public static long AccessTime() { var v = Interlocked.Read(ref _utime); if (v == 0) { v = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L; Interlocked.CompareExchange(ref _utime, v, 0); v = Interlocked.Read(ref _utime); } return v; } /// /// Resets all state. For testing only. /// internal static void Reset() { lock (_lock) { _timer?.Dispose(); _timer = null; } Interlocked.Exchange(ref _refs, 0); Interlocked.Exchange(ref _utime, 0); } }