Skip to content

Bug: Task panic leaves a ghost entry in the scheduler map permanently #19

Description

@ZialeHub

Description

In both schedulers, the cleanup call tasks.write().unwrap().remove(&id) is located at the very end of the loop, only reachable via a normal break. If task.run() (or .await) panics, the thread/future unwinds
without ever reaching remove. The entry stays in the map forever with status = Running.

There is no catch_unwind, no Drop guard, and no panic hook — panics in user task code are completely unhandled.

Affected files

  • src/scheduler/async_scheduler.rs — line 87 (task.run().await) and line 97 (remove)
  • src/scheduler/sync_scheduler.rs — line 87 (task.run()) and line 97 (remove)

Impact

  • Ghost task appears permanently in scheduler.get() with status = Running.
  • scheduler.schedule() for the same task returns Err(AlreadyScheduled) forever.
  • Memory is leaked.
  • The panic is silently swallowed with no error surfaced to the user.

Fix

Async: Wrap the loop in std::panic::catch_unwind using AssertUnwindSafe, or use a deferred cleanup via a guard type:

struct RemoveOnDrop { id: TaskId, tasks: Arc<RwLock<HashMap<TaskId, AsyncTask>>> }
impl Drop for RemoveOnDrop {
fn drop(&mut self) { self.tasks.write().unwrap().remove(&self.id); }
}

Sync: Same RemoveOnDrop guard pattern — the Drop runs on both normal exit and panic unwind.

Consider also propagating the panic info via tracing::error! so users are aware their task failed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions