Description
The async scheduler correctly cancels the Tokio task immediately via task.handle.abort(). The sync scheduler has no equivalent — after abort() sets TaskStatus::Abort and removes the entry from the map, the
background std::thread continues sleeping in std::thread::sleep(duration) for the full recurrence interval before it notices the status change.
Affected file
- src/scheduler/sync_scheduler.rs — abort() method (lines 186–193)
Reproduction
// Sync feature enabled
let mut scheduler = Scheduler::build();
let id = scheduler.schedule(Box::new(task), every(3600.seconds())).unwrap();
scheduler.abort(id).unwrap(); // returns Ok immediately
// The background thread is now sleeping for ~1 hour despite abort() returning Ok
Impact
- abort() silently lies: it returns Ok(()) but the task thread is still alive and consuming resources.
- For long intervals, threads can leak for hours.
- If the Scheduler is dropped after abort(), the orphaned threads keep the Arc clones alive, preventing memory from being freed.
Fix
Replace std::thread::sleep with a Condvar+Mutex based wait, or an std::sync::mpsc channel. The abort() method signals the condvar/channel to wake the thread immediately:
// In the loop, replace sleep with:
let timeout = condvar.wait_timeout(guard, duration).unwrap();
if *status.read().unwrap() == TaskStatus::Abort { break; }
// In abort():
condvar.notify_one(); // wakes the thread immediately
Description
The async scheduler correctly cancels the Tokio task immediately via task.handle.abort(). The sync scheduler has no equivalent — after abort() sets TaskStatus::Abort and removes the entry from the map, the
background std::thread continues sleeping in std::thread::sleep(duration) for the full recurrence interval before it notices the status change.
Affected file
Reproduction
Impact
Fix
Replace std::thread::sleep with a Condvar+Mutex based wait, or an std::sync::mpsc channel. The abort() method signals the condvar/channel to wake the thread immediately: