Commit cc4a165
# Description of Changes
fixes #3174 .
During initialization, entries were added to the `DelayQueue` but not to
`key_map`.
### Detailed Explanation:
1. `DelayQueue` is **not set-semantic**, so we track uniqueness with a
`key_map: FxHashMap` but that wasn't updated during initiliaziation.
2. `Scheduler::schedule` is **not transactional**: it enqueues reducers
even if the DB transaction later fails (abort, duplicate row, etc.). On
yield, `SchedulerActor` checks the DB before execution.
**Combined Effect**: A transaction that does not actually change a
scheduled entry but still calls `Scheduler::schedule` after a module
update will cause a duplicate entry in the `DelayQueue`, since `key_map`
does not yet contain that entry.
**Why It Didn’t Show Earlier**:
When a repeating reducer executes, we re-schedule it by updating both
`DelayQueue` and `key_map` correctly.
The bug only appears in the window after updating module but before the
first execution, if a transaction calls schedule without actually
modifying the DB row.
Which was indeed happening as per discord chat:
> but yeah most likely order of event was modue was updated
> and then update_scheduled_timers_from_static_data was called
window between update module and first execution is 1 hour for this
case.
## Repo steps:
1. publish this module, it makes `send_scheduled_message` reducer to be
called every 10 secs.
```rust
#[spacetimedb::table(name = scheduled_message, public, scheduled(send_scheduled_message))]
pub struct ScheduledMessage {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
scheduled_at: ScheduleAt,
}
#[spacetimedb::reducer]
fn send_scheduled_message(ctx: &ReducerContext, sched: ScheduledMessage) -> Result<(), String> {
info!("Sending scheduled message: {:?}", ctx.timestamp);
Ok(())
}
#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) {
ctx.db.scheduled_message().insert(ScheduledMessage {
scheduled_id: 0,
scheduled_at: Duration::from_secs(10).into(),
});
}
#[spacetimedb::reducer]
pub fn update_timer(ctx: &ReducerContext) {
for mut timer in ctx.db.scheduled_message().iter() {
timer.scheduled_at = Duration::from_secs(10).into();
ctx.db.scheduled_message().scheduled_id().update(timer);
log::info!("building decay agent timer was updated");
}
}
```
2. Update module to support automigration (add a table) and re-publish
it.
3. Call reducer `update_timer` and do it before first execution of
`send_scheduled_message` after updating module.
4. As `update_timer` doesn't change the existing scheduler but calls
`Scheduler::schedule` it will cause duplicate entry in `DelayQueue`.
# API and ABI breaking changes
N/A
# Expected complexity level and risk
1, pretty obvious fix.
# Testing
manually.
The code fix is straightforward, but the issue only becomes visible
under specific conditions.
1 parent f7d94ef commit cc4a165
1 file changed
Lines changed: 13 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
126 | 127 | | |
127 | 128 | | |
128 | 129 | | |
129 | | - | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
130 | 141 | | |
131 | 142 | | |
132 | 143 | | |
133 | 144 | | |
134 | 145 | | |
135 | 146 | | |
136 | 147 | | |
137 | | - | |
| 148 | + | |
138 | 149 | | |
139 | 150 | | |
140 | 151 | | |
| |||
0 commit comments