Skip to main content

Module dispatch_guard

Module dispatch_guard 

Source
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:

  1. The same task is already being dispatched (intended skip).
  2. 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§

DispatchGuard
Removes a key from a shared DashMap<String, String> when dropped.