Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 3, 2026

main PR

Description

When a one-shot timer (AutoReset=false) fires, it sets _enabled = false directly without nulling _timer. Subsequently setting Interval or AutoReset calls UpdateTimer() because those setters only check if (_timer != null). This reactivates the timer while _enabled remains false, causing Stop() to no-op.

Fix: Added && _enabled check in both AutoReset and Interval setters before calling UpdateTimer().

// Before
if (_timer != null)
    UpdateTimer();

// After  
if (_timer != null && _enabled)
    UpdateTimer();

Customer Impact

Timer cannot be stopped after changing AutoReset or Interval on a disabled one-shot timer, leading to runaway timers.

Regression

No. This behavior has existed since .NET Framework.

Testing

  • All 7877 System.ComponentModel.TypeConverter tests pass
  • Updated TestTimerStartAutoReset to explicitly call Start() after changing AutoReset
  • Added SettingAutoResetOrIntervalOnDisabledOneShotTimerDoesNotRestartIt test that verifies Enabled stays false after setting properties on a disabled timer (deterministic, no timing-dependent assertions)

Risk

Low. The fix is minimal (2 lines changed) and adds a defensive check that prevents unintended timer reactivation. Breaking change for code relying on the buggy implicit restart behavior—such code must now explicitly call Start().

Package authoring no longer needed in .NET 9

IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older versions.

Original prompt

This section details on the original issue you should resolve

<issue_title>System.Timers.Timer may activate repeating timer in a way in which Stop() would not stop it</issue_title>
<issue_description>When a non-repeating timer ticks, the _enabled field is set instead of the Enabled property here, which does not null out the internal timer:

if (!_autoReset)
{
_enabled = false;
}

Then, when the Interval or AutoReset properties are set, the timer is reactivated because it does not check whether the timer is enabled:

_interval = value;
if (_timer != null)
{
UpdateTimer();
}

_autoReset = value;
if (_timer != null)
{
UpdateTimer();
}

Also, along with reactivating the timer the _enabled field is not set even though the timer is now enabled, and Stop() would not stop the timer because it's already not enabled.

Maybe the original intention was to set the Enabled property to false upon tick, such that timer != null would also imply that the timer is enabled. It would be a breaking change to fix that part, but probably at least when the timer is activated the _enabled field can also be updated such that it reflects the correct state.</issue_description>

Comments on the Issue (you are @copilot in this section)

@ericstj Good bug. Seems like something that we could fix but would want to do so earlier in a release since this has been this way since .NETFramework. Moving this to 6.0.0.

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…d one-shot timer

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix issue with Stop() not functioning correctly on System.Timers.Timer Fix System.Timers.Timer Stop() not working after setting AutoReset or Interval on disabled one-shot timer Jan 3, 2026
Copilot AI requested a review from stephentoub January 3, 2026 23:15
@stephentoub stephentoub marked this pull request as ready for review January 4, 2026 04:42
Copilot AI review requested due to automatic review settings January 4, 2026 04:42
timer.Stop();
Assert.False(timer.Enabled);

int countAfterStop = count;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot, is it possible that a pending timer callback is racing with this such that we'll get spurious failures?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right. Added a 100ms delay after Stop() before capturing countAfterStop to allow any in-flight callbacks to complete. Fixed in 72cb668.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot, a delay like this will still lead to spurious false positives in CI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Removed the timing-dependent StopWorksAfterSettingAutoResetOrIntervalOnRunningTimer test entirely and simplified SettingAutoResetOrIntervalOnDisabledOneShotTimerDoesNotRestartIt to only check that Enabled stays false after setting properties - no Thread.Sleep() or count assertions needed. Fixed in 0579fc7.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug in System.Timers.Timer where Stop() would fail to stop the timer after setting AutoReset or Interval on a disabled one-shot timer. When a one-shot timer fires, it sets _enabled = false directly without nulling _timer, allowing subsequent property changes to inadvertently restart the timer while leaving _enabled false, causing Stop() to become ineffective.

Key Changes:

  • Added && _enabled check in both AutoReset and Interval setters before calling UpdateTimer()
  • Updated existing test to explicitly call Start() after changing AutoReset
  • Added two new tests to verify the fix and ensure Stop() works correctly

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/libraries/System.ComponentModel.TypeConverter/src/System/Timers/Timer.cs Added && _enabled condition to AutoReset and Interval setters to prevent UpdateTimer() from being called when the timer is disabled
src/libraries/System.ComponentModel.TypeConverter/tests/TimerTests.cs Updated TestTimerStartAutoReset to explicitly call Start(); added SettingAutoResetOrIntervalOnDisabledOneShotTimerDoesNotRestartIt test to verify disabled timers don't restart; added StopWorksAfterSettingAutoResetOrIntervalOnRunningTimer test to ensure Stop() works after property changes
Comments suppressed due to low confidence (2)

src/libraries/System.ComponentModel.TypeConverter/tests/TimerTests.cs:146

  • The sleep duration of 100ms may be insufficient to reliably verify that the timer did not restart, especially on slower or heavily loaded systems. Consider increasing the sleep duration to at least 200-500ms or using a more deterministic approach with a longer wait combined with assertion on the count remaining zero.
                Thread.Sleep(100);

src/libraries/System.ComponentModel.TypeConverter/tests/TimerTests.cs:178

  • The sleep duration of 100ms may be insufficient to reliably verify that the timer remained stopped, especially on slower or heavily loaded systems. Consider increasing the sleep duration to at least 200-500ms to reduce the likelihood of test flakiness.
                Thread.Sleep(100);


count = 0;
timer.AutoReset = true;
Thread.Sleep(100);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sleep duration of 100ms may be insufficient to reliably verify that the timer did not restart, especially on slower or heavily loaded systems. Consider increasing the sleep duration to at least 200-500ms or using a more deterministic approach with a longer wait combined with assertion on the count remaining zero.

This issue also appears in the following locations of the same file:

  • line 146
  • line 178

Copilot uses AI. Check for mistakes.
…valOnRunningTimer test

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
…ditions

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.Timers.Timer may activate repeating timer in a way in which Stop() would not stop it

3 participants