Skip to content

Commit 7e57829

Browse files
committed
add timeout handling - default 3x polling interval
1 parent 6343313 commit 7e57829

File tree

1 file changed

+39
-2
lines changed

1 file changed

+39
-2
lines changed

src/Webserver.API/Services/PlcProgram/Subscription/ApiPlcProgramSubscriptionFaker.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ public class ApiPlcProgramSubscriptionFaker : IDisposable
7373
/// </summary>
7474
public int MaxBackoffMultiplier { get; set; } = 10;
7575

76+
/// <summary>
77+
/// Maximum duration (in milliseconds) allowed for a single poll before timing out.
78+
/// Default is null (3x PollingInterval is calculated automatically).
79+
/// Set to a specific value to override the default timeout behavior.
80+
/// </summary>
81+
public int? PollTimeoutMs { get; set; } = null;
82+
7683
/// <summary>
7784
/// Indicates whether the subscription is currently active and polling.
7885
/// </summary>
@@ -175,23 +182,53 @@ private void PollingTimerCallback(object state)
175182
{
176183
var pollMonitoredItemsStarted = DateTime.UtcNow;
177184
var changedItemsList = new List<(ApiPlcProgramData item, object oldValue, object newValue)>();
185+
var pollTimedOut = false;
186+
178187
try
179188
{
180-
changedItemsList = PollMonitoredItemsAsync().GetAwaiter().GetResult();
189+
var timeoutMs = PollTimeoutMs ?? (PollingInterval * 3);
190+
using (var cts = new CancellationTokenSource(timeoutMs))
191+
{
192+
try
193+
{
194+
changedItemsList = PollMonitoredItemsAsync(cts.Token).GetAwaiter().GetResult();
195+
}
196+
catch (OperationCanceledException)
197+
{
198+
pollTimedOut = true;
199+
_logger?.LogError($"{nameof(ApiPlcProgramSubscriptionFaker)}: Poll timeout exceeded {timeoutMs}ms. " +
200+
"Cancelling poll operation. Applying exponential backoff.");
201+
}
202+
}
181203
}
182204
finally
183205
{
184206
var pollDuration = (DateTime.UtcNow - pollMonitoredItemsStarted).TotalMilliseconds;
207+
185208
if (changedItemsList.Count > 0 || PollingCycleCompleted != null)
186209
{
187210
OnPollingCycleCompleted(new PollingCycleCompletedEventArgs(changedItemsList, pollDuration, pollMonitoredItemsStarted));
188211
}
212+
189213
Interlocked.Exchange(ref _isPolling, 0);
214+
190215
if (_pollingTimer != null && !_isDisposed)
191216
{
192217
try
193218
{
194-
if (pollDuration > PollingInterval)
219+
if (pollTimedOut)
220+
{
221+
_consecutiveSlowPolls++;
222+
var backoffMultiplier = Math.Min(_consecutiveSlowPolls, MaxBackoffMultiplier);
223+
var backoffDelay = PollingInterval * backoffMultiplier;
224+
225+
_logger?.LogWarning($"{nameof(ApiPlcProgramSubscriptionFaker)}: Poll timeout triggered backoff. " +
226+
$"Consecutive slow polls: {_consecutiveSlowPolls}. " +
227+
$"Applying exponential backoff (multiplier: {backoffMultiplier}x, delay: {backoffDelay}ms)");
228+
229+
_pollingTimer.Change(backoffDelay, Timeout.Infinite);
230+
}
231+
else if (pollDuration > PollingInterval)
195232
{
196233
_consecutiveSlowPolls++;
197234
var backoffMultiplier = Math.Min(_consecutiveSlowPolls, MaxBackoffMultiplier);

0 commit comments

Comments
 (0)