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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! RAII termination guard for the reindex task.
//!
//! Why: a Rust panic inside a `tokio::spawn` task unwinds that task silently
//! — the `broadcast::Sender` in `ReindexProgress` drops, live SSE subscribers
//! never receive a terminal frame, and the CLI reports "stream ended without
//! completion event" indefinitely. Worse (issue #1428): on a CUDA build a
//! mid-batch GPU stall could cancel or panic the runner future, the guard set
//! `status=Failed` and broadcast a generic SSE error, but it logged **nothing**
//! to stderr — so the daemon log showed no error at all ("silent" `status=error`
//! with `duration=None`). Placing this guard at the start of the spawned future
//! and calling `disarm()` only after `emit_complete_event` completes ensures
//! that ANY early exit (panic, early return, or `.await` cancellation) emits
//! `{"event":"error","message":"…"}` **and** logs the underlying cause at
//! `error!` on stderr.
//!
//! What: holds `Arc<ReindexProgress>`, an `armed: bool` flag, and a shared
//! `failure_reason` slot. The runner records a specific cause via
//! `set_failure_reason` whenever it knows one (e.g. a captured producer-task
//! panic). `Drop` checks the flag — if still armed, it (1) logs the captured
//! reason (or a generic fallback) at `error!` to stderr so the daemon log is
//! never silent, (2) pushes a blocking-channel send of the error event directly
//! onto the broadcast sender (no `.await` in `Drop`; use the non-blocking
//! `send`), and (3) marks the progress status as `Failed`.
//!
//! Test: `reindex_guard_fires_on_early_return`,
//! `reindex_guard_uses_captured_failure_reason`, and
//! `reindex_guard_failure_reason_is_none_by_default` in `tests.rs`.
use ;
use ;
/// Shared, writable slot carrying the most specific failure reason known to the
/// reindex task at the moment it exits early.
///
/// Why: the guard's `Drop` runs after the runner future has unwound, so it
/// cannot inspect local error values directly. The runner writes the cause it
/// observed (e.g. a producer-task `JoinError`, or an aborting carryover-copy
/// failure) into this slot **before** the early return; `Drop` then reads it so
/// the operator sees the real cause instead of "exited unexpectedly".
/// What: an `Arc<Mutex<Option<String>>>` cloneable handle. `None` means no
/// specific cause was recorded and `Drop` falls back to the generic message.
/// Test: `reindex_guard_uses_captured_failure_reason`.
pub type FailureReasonSlot = ;
/// RAII guard that emits a terminal SSE error event AND an `error!` stderr log
/// if the reindex task exits without having emitted one via the normal path.
///
/// Why: ensures that ANY early exit from the spawned reindex task (panic,
/// early return, `.await` cancellation) is non-silent — it emits
/// `{"event":"error","message":"…"}` for live SSE subscribers and logs the
/// underlying cause at `error!` so the daemon log always records WHY a reindex
/// ended in `status=error` (issue #1428).
/// What: armed on construction; `Drop` fires the error event + stderr log
/// synchronously when still armed. Call `disarm()` after the normal terminal
/// event is emitted. Call `set_failure_reason()` to record a specific cause.
/// Test: `reindex_guard_fires_on_early_return`,
/// `reindex_guard_uses_captured_failure_reason`.
pub