1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! 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
//!
//! ```rust,ignore
//! 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 ...
//! });
//! ```
use DashMap;
use Arc;
/// Removes a key from a shared [`DashMap<String, String>`] when dropped.
///
/// Create this guard (step 2) only after the key has already been inserted into
/// the map (step 1) and before calling `tokio::spawn` (step 3). Moving the guard
/// into the spawned async block ensures the key is removed regardless of whether
/// the block completes normally or unwinds via a panic.