Expand description
RAII guard for the per-task dispatching DashMap.
DispatchGuard removes a key from the shared dispatching map when it is
dropped — whether the owning async task completes normally or panics.
§Problem it solves
Without this guard, the cleanup code (map.remove(&key)) only runs when the
spawned async block reaches the end of its normal execution path. If the block
panics (e.g. from an unwrap() inside review_and_merge), Tokio catches the
panic and terminates the task, but the manual remove never executes. The key
leaks in the map permanently: subsequent attempts to review the task are skipped
by the guard check, and stuck-task recovery keeps resetting the task to
NeedsReview in an infinite loop until the service is restarted.
§Why DashMap instead of Mutex<HashMap>
The dispatching map is accessed from both async contexts (tick, sync, subscribers)
and synchronous Drop implementations. DashMap is a lock-free concurrent map
that works in both contexts without risk of blocking Tokio worker threads — unlike
std::sync::Mutex which blocks the OS thread, or tokio::sync::Mutex which
cannot be used in Drop.
§Why DashMap instead of DashSet
DashSet::insert returns false in two distinct cases:
- The same task is already being dispatched (intended skip).
- A different task happens to produce the same key (should never happen, but silently skips a valid dispatch if it does).
By storing the task-id as the map value, the caller can distinguish these cases: a matching task-id means an expected duplicate; a mismatched task-id is an unexpected collision that warrants a warning log.
§Usage
use dashmap::mapref::entry::Entry;
// 1. Attempt to claim the dispatch slot atomically.
match dispatching.entry(dispatch_key.clone()) {
Entry::Occupied(existing) => {
let existing_id = existing.get().clone();
drop(existing); // release shard lock before logging
if existing_id == task.id.0 {
tracing::debug!(task_id = %task.id.0, "task already dispatching, skipping duplicate");
} else {
tracing::warn!(
task_id = %task.id.0,
existing_task_id = %existing_id,
dispatch_key,
"dispatch key collision: unexpected task already holds this key"
);
}
continue;
}
Entry::Vacant(slot) => {
slot.insert(task.id.0.clone());
}
}
// 2. Create a guard that owns the removal obligation.
let guard = DispatchGuard::new(Arc::clone(&dispatching), dispatch_key.clone());
// 3. Move the guard into the spawned task.
tokio::spawn(async move {
let _guard = guard; // key removed on drop, even on panic
// ... do work ...
});Structs§
- Dispatch
Guard - Removes a key from a shared
DashMap<String, String>when dropped.