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: Modified UpdateTimer() to check both _timer and _enabled before updating the timer. This centralizes the check so all callers get the right behavior automatically.

// UpdateTimer() now returns early if timer shouldn't be updated
private void UpdateTimer()
{
    if (_timer is null || !_enabled)
    {
        return;
    }

    int i = (int)Math.Ceiling(_interval);
    _timer.Change(i, _autoReset ? i : Timeout.Infinite);
}

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 and adds a defensive check in UpdateTimer() 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
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);

…valOnRunningTimer test

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

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
{
_autoReset = value;
if (_timer != null)
if (_timer != null && _enabled)
Copy link
Member

Choose a reason for hiding this comment

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

@copilot, I think it'd be better to move these checks into UpdateTimer itself; that way all callers of UpdateTimer get the right behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved the _enabled check into UpdateTimer() itself. The method now returns early if _timer is null || !_enabled, so all callers get the right behavior. Fixed in 419c4d9.

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