Description
In both async_scheduler.rs and sync_scheduler.rs, the worker task/thread is spawned before the entry is inserted into the map. If the task completes quickly (e.g. count(1)), it executes its
self-removal (tasks.write().remove(&id)) on a key that doesn't exist yet — the remove is a no-op. Then schedule() inserts the entry at line 108. The result is a permanent zombie entry in the map that never
gets cleaned up.
Affected files
src/scheduler/async_scheduler.rs — line 50 (spawn) vs line 108 (insert)
src/scheduler/sync_scheduler.rs — line 50 (spawn) vs line 108 (insert)
Reproduction
let mut scheduler = Scheduler::build();
let task = MyTask::new();
// count(1) with a very short interval — task completes before insert
scheduler.schedule(Box::new(task.clone()), every(1.seconds()).count(1)).unwrap();
// Later — same task can never be scheduled again:
let result = scheduler.schedule(Box::new(task), every(1.seconds()).count(1));
assert!(matches!(result, Err(PulsyncError::AlreadyScheduled(_)))); // stuck forever
Impact
- The TaskId is permanently stuck in the scheduler map.
- All future schedule() calls for the same task parameters return Err(AlreadyScheduled) indefinitely.
- Memory is leaked (the AsyncTask/SyncTask entry is never freed).
Fix
Insert the entry into the map before spawning the worker. The spawned closure already holds a clone of the Arc, so inserting first is safe:
// 1. Insert first
self.write().unwrap().insert(id, task_entry);
// 2. Then spawn
let handle = tokio::spawn(async move { ... });
// 3. Update the handle in the map after spawn
self.write().unwrap().get_mut(&id).unwrap().handle = handle;
Or restructure the AsyncTask/SyncTask to accept the handle as a separate step via an Option<JoinHandle>.
Description
In both
async_scheduler.rsandsync_scheduler.rs, the worker task/thread is spawned before the entry is inserted into the map. If the task completes quickly (e.g.count(1)), it executes itsself-removal (
tasks.write().remove(&id)) on a key that doesn't exist yet — the remove is a no-op. Thenschedule()inserts the entry at line 108. The result is a permanent zombie entry in the map that nevergets cleaned up.
Affected files
src/scheduler/async_scheduler.rs— line 50 (spawn) vs line 108 (insert)src/scheduler/sync_scheduler.rs— line 50 (spawn) vs line 108 (insert)Reproduction