From 4252e9c8e6710aeba74bcece92a5a0413d67ceb0 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Wed, 22 Jul 2026 09:04:43 -0600 Subject: [PATCH] feat: add thread safety to error timer management in StatusMonitorBase --- .../Monitoring/StatusMonitorBase.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs b/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs index 381ac6ba..cf6962b6 100644 --- a/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs +++ b/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs @@ -87,6 +87,14 @@ namespace PepperDash.Essentials.Core long ErrorTime; Timer WarningTimer; Timer ErrorTimer; + + // Guards WarningTimer/ErrorTimer against concurrent access - StartErrorTimers/StopErrorTimers/ + // ResetErrorTimers can each be invoked from different threads (e.g. a socket data-received + // callback vs. a connection-status-changed callback), and without this lock a null-check followed + // by a dereference (e.g. in ResetErrorTimers) can race with StopErrorTimers nulling the field in + // between, throwing an unhandled NullReferenceException on a background thread that crashes the + // whole program. + private readonly object _timerLock = new object(); /// Constructor /// /// parent device @@ -154,6 +162,8 @@ namespace PepperDash.Essentials.Core /// protected void StartErrorTimers() { + lock (_timerLock) + { if (WarningTimer == null) { WarningTimer = new Timer(WarningTime) { AutoReset = false }; @@ -167,16 +177,20 @@ namespace PepperDash.Essentials.Core ErrorTimer.Start(); } } + } /// /// Stops the error timers /// protected void StopErrorTimers() { - if (WarningTimer != null) WarningTimer.Stop(); - if (ErrorTimer != null) ErrorTimer.Stop(); - WarningTimer = null; - ErrorTimer = null; + lock (_timerLock) + { + if (WarningTimer != null) WarningTimer.Stop(); + if (ErrorTimer != null) ErrorTimer.Stop(); + WarningTimer = null; + ErrorTimer = null; + } } /// @@ -184,6 +198,8 @@ namespace PepperDash.Essentials.Core /// protected void ResetErrorTimers() { + lock (_timerLock) + { if (WarningTimer != null) { WarningTimer.Stop(); @@ -196,6 +212,7 @@ namespace PepperDash.Essentials.Core ErrorTimer.Interval = ErrorTime; ErrorTimer.Start(); } + } } } } \ No newline at end of file