Skip to content

fix(scheduler): stop canonical tasks after delete#24

Open
madflojo wants to merge 5 commits intomainfrom
scheduler-fix
Open

fix(scheduler): stop canonical tasks after delete#24
madflojo wants to merge 5 commits intomainfrom
scheduler-fix

Conversation

@madflojo
Copy link
Copy Markdown
Owner

@madflojo madflojo commented Apr 13, 2026

Summary

Fix task deletion so the scheduler stops the live stored task rather than a cloned copy, and avoid rearming recurring timers after cancellation.

Changes

  • delete tasks directly from the scheduler map and stop the canonical task instance
  • skip timer reset when a task context has already been canceled
  • add a regression test for deleting a recurring task while its task function is still running

Rationale

The previous delete path went through Lookup, which returns a clone. That made task lifecycle control harder to reason about and left execTask free to rearm canceled tasks. This change makes cancellation authoritative and adds coverage for the most suspicious interleaving.

Risk & Impact

  • Breaking changes: no
  • Performance/Security: no material impact; behavior is safer around task deletion and rescheduling

Summary by CodeRabbit

  • Bug Fixes

    • Tasks are reliably removed and no longer continue running after deletion.
  • Performance

    • Reduced scheduling contention and tightened cancellation flow for more efficient operation.
  • Tests

    • Added a test ensuring deleted recurring tasks do not resume execution.
  • Chores

    • CI workflow modernized and simplified test/coverage steps.

Use the live scheduled task in Del instead of going through Lookup,
and skip timer rearming once task cancellation has been signaled.
Also add a regression test that holds a running task, deletes it,
and verifies no later invocation slips through.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce114981-0ebe-47a1-af73-fb8f429d3c86

📥 Commits

Reviewing files that changed from the base of the PR and between dbc35fb and a44eba4.

📒 Files selected for processing (2)
  • .github/workflows/tests.yml
  • tasks_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • tasks_test.go
  • .github/workflows/tests.yml

📝 Walkthrough

Walkthrough

Scheduler.Del now removes tasks from the scheduler map immediately under a single lock and explicitly cancels/stops timers; execTask prevents timer.Reset when the task context is canceled or the timer is nil. A new test verifies tasks do not run after deletion during execution.

Changes

Cohort / File(s) Summary
Task deletion & rescheduling
tasks.go
Remove task from schd.tasks immediately while holding one lock; call t.cancel() only if non-nil and stop t.timer only when non-nil; in execTask return early when t.ctx.Err() != nil or t.timer == nil to avoid Reset.
Test: deletion during execution
tasks_test.go
Added subtest to TestSchedulerDoesntRun that schedules a repeating task, blocks its first run, deletes the task mid-execution, and asserts no subsequent runs via atomic counters and contexts.
CI workflow update
.github/workflows/tests.yml
Added permissions for token, upgraded actions/checkout and actions/setup-go, switched to go-version-file: go.mod, simplified test steps, and replaced Codecov upload with codecov/codecov-action@v5 using conditional file upload.

Sequence Diagram

sequenceDiagram
    participant Test as Test Harness
    participant Sched as Scheduler
    participant Task as Task Goroutine
    participant Timer as Timer

    Test->>Sched: schedule task (short interval)
    Sched->>Timer: create/start timer
    Timer->>Task: trigger first run
    Task->>Task: begin work (blocks)
    Test->>Sched: call Del(taskName)
    activate Sched
    Sched->>Sched: acquire lock
    Sched->>Sched: remove task from map
    Sched->>Task: if t.cancel != nil call t.cancel()
    Sched->>Timer: if t.timer != nil call t.timer.Stop()
    deactivate Sched
    Task->>Task: execTask checks t.ctx.Err() or t.timer == nil
    alt cancelled or timer nil
        Task->>Task: early return, do not Reset
    else
        Task->>Timer: Reset interval
    end
    Test->>Test: verify no post-delete execution
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I’m a rabbit in the scheduler lair,
I pause, I stop, I tidy the lair,
No sneaky reset, no rerun chase,
Deleted tasks stay out of place,
Hop, hop — concurrency with grace! 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(scheduler): stop canonical tasks after delete' accurately describes the main change: fixing task deletion to stop the live canonical task instance instead of a clone.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scheduler-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Align the test workflow with the working Codecov pattern from testlazy.
The old goveralls step was dead weight and tripped over modern Go tool
installation behavior, which is a rough hobby for a CI job.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.26%. Comparing base (fd089b0) to head (a44eba4).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #24      +/-   ##
==========================================
+ Coverage   98.21%   98.26%   +0.04%     
==========================================
  Files           1        1              
  Lines         112      115       +3     
==========================================
+ Hits          110      113       +3     
  Misses          1        1              
  Partials        1        1              
Flag Coverage Δ
unittests 98.26% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/tests.yml:
- Around line 18-35: Replace mutable action tags (actions/checkout@v6,
actions/setup-go@v6, codecov/codecov-action@v5) with their corresponding pinned
commit SHAs; update the workflow to reference the full commit SHA for each
action and keep the human-friendly tag (e.g., `@v6` or `@v5`) as a trailing comment
for readability, ensuring the checkout, setup-go, and codecov steps use the
SHA-pinned refs so CI uses immutable versions.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c4e0e1f8-b74f-4153-bee2-4acf51576f91

📥 Commits

Reviewing files that changed from the base of the PR and between 73f26fe and 00f2bf7.

📒 Files selected for processing (1)
  • .github/workflows/tests.yml

Comment thread .github/workflows/tests.yml Outdated
Copy link
Copy Markdown

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 scheduler task deletion semantics so deleting a task stops the canonical scheduled instance (not a cloned copy) and prevents recurring timers from being re-armed after cancellation, with a regression test to cover a key delete-while-running interleaving.

Changes:

  • Update Scheduler.Del to remove tasks directly from the scheduler map and cancel/stop the canonical task instance.
  • Guard recurring reschedule logic to avoid timer.Reset after task cancellation.
  • Add a regression test for deleting a recurring task while its first invocation is still running, and modernize the CI workflow steps.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tasks.go Deletes canonical tasks from the scheduler map and avoids resetting timers once a task is canceled.
tasks_test.go Adds a regression test for deleting a task during its in-flight execution.
.github/workflows/tests.yml Updates Go setup/checkout steps and refines Codecov upload configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tasks.go Outdated
Comment thread tasks_test.go
Comment on lines +697 to +715
id, err := scheduler.Add(&Task{
Interval: time.Duration(100 * time.Millisecond),
TaskFunc: func() error {
currentRun := atomic.AddInt32(&runCount, 1)

if currentRun == 1 {
started()
<-releaseCtx.Done()
finished()
return nil
}

if atomic.LoadUint32(&deleted) == 1 {
postDelete()
}

t.Errorf("task executed after delete, run=%d", currentRun)
return nil
},
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

This test calls t.Errorf from inside the scheduled task function, which runs in a scheduler goroutine. If the task runs after the subtest has completed, this can trigger "testing.T called after test finished" and make the test flaky. Consider reporting failures via a channel/atomic flag that the test goroutine asserts on instead of calling t.Errorf inside TaskFunc.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@copilot you are right, it should instead return an error and let the error function cancel an errors context which is checked in the main test. Can you fix it?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed in commit 4c1b4b7. The TaskFunc now returns an error when it runs after deletion, the ErrFunc cancels an errCtx, and the main test goroutine asserts that errCtx is not cancelled within the timeout window. t.Errorf and the old postDeleteCtx/postDelete variables are removed.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

An unexpected error occurred while generating fixes: Resource not accessible by integration - https://docs.github.com/rest/git/trees#create-a-tree

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants