What's wrong
RestartCoreAsync (IntervalAction/IntervalAction.cs:~153-164) only stops and waits for the old PollingTask when ShouldPoll is true:
if (shouldPoll)
{
Stop();
await WaitAndDiscardOutcomeAsync(PollingTask).ConfigureAwait(false);
}
lock (Lock) { ShouldPoll = true; ... start new loop ... }
After Stop(), ShouldPoll is already false, but the old loop is still inside Task.Delay(PollingInterval). The restart skips the wait, sets ShouldPoll = true and starts a second loop. When the old loop wakes up it sees ShouldPoll == true and carries on. Nothing references it any more.
Failure scenario (reproduced with a temporary MSTest)
Settings: PollingInterval = 200ms, ActionInterval = 0, FromLastStart.
Sequence: Start(), wait 50 ms, then Stop(); await RestartAsync();
Observed:
- The old polling task was not completed, and was not the same task as the new
PollingTask.
- The action ran 20 times in 2 s, where one loop gives about 10.
Every further Stop()/Restart() pair can add another orphaned loop.
Two loops also call TryRun concurrently. TryRun reads and assigns ActionTask outside the lock, so the action can overlap itself, which is the guarantee this class exists to provide.
This is a sibling of #49. That fix serialized restarts against each other, but not a restart against a loop that Stop left winding down.
Suggested fix
In RestartCoreAsync, always Stop() and await WaitAndDiscardOutcomeAsync(PollingTask) before setting ShouldPoll = true, whatever ShouldPoll was. Optionally, move TryRun's ActionTask check-and-assign under Lock as well.
Acceptance: a test for Stop, then immediately Restart, then count executions over N polling intervals shows the rate of a single loop, and the pre-restart PollingTask has completed.
What's wrong
RestartCoreAsync(IntervalAction/IntervalAction.cs:~153-164) only stops and waits for the oldPollingTaskwhenShouldPollis true:After
Stop(),ShouldPollis already false, but the old loop is still insideTask.Delay(PollingInterval). The restart skips the wait, setsShouldPoll = trueand starts a second loop. When the old loop wakes up it seesShouldPoll == trueand carries on. Nothing references it any more.Failure scenario (reproduced with a temporary MSTest)
Settings:
PollingInterval = 200ms,ActionInterval = 0,FromLastStart.Sequence:
Start(), wait 50 ms, thenStop(); await RestartAsync();Observed:
PollingTask.Every further
Stop()/Restart()pair can add another orphaned loop.Two loops also call
TryRunconcurrently.TryRunreads and assignsActionTaskoutside the lock, so the action can overlap itself, which is the guarantee this class exists to provide.This is a sibling of #49. That fix serialized restarts against each other, but not a restart against a loop that
Stopleft winding down.Suggested fix
In
RestartCoreAsync, alwaysStop()andawait WaitAndDiscardOutcomeAsync(PollingTask)before settingShouldPoll = true, whateverShouldPollwas. Optionally, moveTryRun'sActionTaskcheck-and-assign underLockas well.Acceptance: a test for Stop, then immediately Restart, then count executions over N polling intervals shows the rate of a single loop, and the pre-restart
PollingTaskhas completed.