Skip to content

fix: save recurrence changes that keep the same expression - #8

Merged
jacalapa merged 1 commit into
mainfrom
fix/recurrence-update
Nov 11, 2026
Merged

jacalapa merged 1 commit into
mainfrom
fix/recurrence-update

Conversation

@jacalapa

Copy link
Copy Markdown
Contributor

Problem

The README says SetRecurrenceAsync is "safe to call on every startup — only updates if the expression or data changed", but:

  1. RecurringJobBuilder.UpdateAsync acted only on a new cron expression. New data or options with the same expression were silently dropped: neither the recurrence nor its next job changed.
  2. JobOptions.Equals compared RetryDelaysInSeconds by reference, so a recurrence read back from the store almost never equalled the new one.
  3. With a new expression, a second job was created next to the one already waiting, so the old one still ran once more on the old schedule.

Fix

  • JobOptions compares and hashes the retry delays by value.
  • Any change of the expression, data or options updates the recurrence:
    • the job waiting for the next occurrence is reconfigured in place (Job.Reconfigure) and, for a new expression, moved to the new time. No second job, nothing runs twice or with old settings;
    • a running job keeps its settings; the next one is created from the updated recurrence when it completes;
    • with no current job or a completed one, the next job is created from the new settings;
    • if a runner takes the waiting job meanwhile (concurrency exception), the recurrence is still updated.
  • The EF store saves a job's type, data, priority and options on update.
  • README describes the behaviour.

Tests

  • RecurrenceUpdateTests (unit): unchanged recurrence read back from the store writes nothing; data change, options change, expression change, running job, completed job, runner race; JobOptions value equality. 8 of them fail on the old code.
  • StoreJobTestsBase.UpdateAsync_Reconfigured_PersistsDataOptionsAndTime, run on PostgreSQL and SQL Server.
  • RecurringJobBuilderTests.SetRecurrenceAsync_ExistingRecurrenceWithSameRule_DoesNotUpdate passed only because of the bug: its stored data (anonymous type vs System.Object) and options differed from what the builder produces. It now compares a truly unchanged recurrence (..._ExistingRecurrenceUnchanged_DoesNotUpdate), and ..._WithSameRuleButOtherOptions_Updates covers the change.

71 unit tests pass on net8.0, net9.0, net10.0; 38 storage tests each on PostgreSQL and SQL Server.

Behaviour change

After changing the expression, the old schedule no longer gets one extra run.

Independent of #7; the two branches merge without conflicts.

🤖 Generated with Claude Code

SetRecurrenceAsync promised to update a recurrence when its data changed, but
UpdateAsync only acted on a new cron expression: new data or options with the same
expression were silently dropped. JobOptions also compared the retry delays by
reference, so a recurrence read from the store almost never equalled the new one.

- JobOptions compares and hashes the retry delays by value.
- Any change of the expression, data or options updates the recurrence. The job
  waiting for the next occurrence is reconfigured in place (Job.Reconfigure) and,
  for a new expression, moved to the new time, instead of a second job being added
  next to it (which used to run once more on the old schedule). A running job keeps
  its settings; the next one is created from the updated recurrence. If a runner
  takes the waiting job meanwhile, the recurrence is still updated.
- The EF store saves a job's data and options on update.

RecurringJobBuilderTests.SetRecurrenceAsync_ExistingRecurrenceWithSameRule_DoesNotUpdate
passed only because of the bug (its stored data and options differed from what the
builder produces); it now compares an unchanged recurrence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jacalapa
jacalapa merged commit 1cb2647 into main Nov 11, 2026
3 checks passed
@jacalapa
jacalapa deleted the fix/recurrence-update branch November 11, 2026 13:25
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.

1 participant