zeppelin_embed/lifecycle/
close.rs1use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::{Arc, Condvar, Mutex, mpsc};
5use std::thread::JoinHandle;
6
7use super::{Store, StoreError, StoreState};
8
9#[cfg(test)]
10pub(crate) struct TeardownProbe {
11 sequence: AtomicU64,
12 background_stopped: AtomicU64,
13 snapshot_released: AtomicU64,
14}
15
16#[cfg(test)]
17impl TeardownProbe {
18 pub(crate) const fn new() -> Self {
19 Self {
20 sequence: AtomicU64::new(0),
21 background_stopped: AtomicU64::new(0),
22 snapshot_released: AtomicU64::new(0),
23 }
24 }
25
26 fn record_background_stopped(&self) {
27 let event = self.sequence.fetch_add(1, Ordering::SeqCst) + 1;
28 let _ =
29 self.background_stopped
30 .compare_exchange(0, event, Ordering::SeqCst, Ordering::SeqCst);
31 }
32
33 pub(crate) fn record_snapshot_released(&self) {
34 let event = self.sequence.fetch_add(1, Ordering::SeqCst) + 1;
35 let _ =
36 self.snapshot_released
37 .compare_exchange(0, event, Ordering::SeqCst, Ordering::SeqCst);
38 }
39
40 pub(crate) fn events(&self) -> (u64, u64) {
41 (
42 self.background_stopped.load(Ordering::SeqCst),
43 self.snapshot_released.load(Ordering::SeqCst),
44 )
45 }
46}
47
48struct BackgroundControl {
49 stop: Mutex<bool>,
50 changed: Condvar,
51}
52
53pub(crate) struct BackgroundThread {
54 control: Arc<BackgroundControl>,
55 handle: Option<JoinHandle<()>>,
56 #[cfg(test)]
57 teardown_probe: Option<Arc<TeardownProbe>>,
58}
59
60impl BackgroundThread {
61 pub(crate) fn start() -> Result<Self, StoreError> {
62 static NEXT_THREAD: AtomicU64 = AtomicU64::new(1);
63
64 let control = Arc::new(BackgroundControl {
65 stop: Mutex::new(false),
66 changed: Condvar::new(),
67 });
68 let worker_control = Arc::clone(&control);
69 let (ready_tx, ready_rx) = mpsc::sync_channel(0);
70 let thread_id = NEXT_THREAD.fetch_add(1, Ordering::Relaxed);
71 let handle = std::thread::Builder::new()
72 .name(format!("ze-lifecycle-{thread_id}"))
73 .spawn(move || {
74 if ready_tx.send(()).is_err() {
75 return;
76 }
77 let Ok(mut stop) = worker_control.stop.lock() else {
78 return;
79 };
80 while !*stop {
81 let Ok(next) = worker_control.changed.wait(stop) else {
82 return;
83 };
84 stop = next;
85 }
86 })
87 .map_err(|source| StoreError::BackgroundStart { source })?;
88 if ready_rx.recv().is_err() {
89 let mut background = Self {
90 control,
91 handle: Some(handle),
92 #[cfg(test)]
93 teardown_probe: None,
94 };
95 background.stop_best_effort();
96 return Err(StoreError::BackgroundHandshake);
97 }
98 Ok(Self {
99 control,
100 handle: Some(handle),
101 #[cfg(test)]
102 teardown_probe: None,
103 })
104 }
105
106 #[cfg(test)]
107 pub(crate) fn set_teardown_probe(&mut self, probe: Arc<TeardownProbe>) {
108 self.teardown_probe = Some(probe);
109 }
110
111 fn stop_and_join(mut self) -> Result<(), StoreError> {
112 {
113 let mut stop = self
114 .control
115 .stop
116 .lock()
117 .map_err(|_| StoreError::Synchronization {
118 component: "lifecycle thread",
119 })?;
120 *stop = true;
121 self.control.changed.notify_all();
122 }
123 let Some(handle) = self.handle.take() else {
124 return Ok(());
125 };
126 let result = handle
127 .join()
128 .map_err(|_| StoreError::BackgroundThreadPanicked);
129 #[cfg(test)]
130 if result.is_ok()
131 && let Some(probe) = &self.teardown_probe
132 {
133 probe.record_background_stopped();
134 }
135 result
136 }
137
138 fn stop_best_effort(&mut self) {
139 if let Ok(mut stop) = self.control.stop.lock() {
140 *stop = true;
141 self.control.changed.notify_all();
142 }
143 if let Some(handle) = self.handle.take() {
144 let _ = handle.join();
145 }
146 #[cfg(test)]
147 if let Some(probe) = &self.teardown_probe {
148 probe.record_background_stopped();
149 }
150 }
151}
152
153impl Drop for BackgroundThread {
154 fn drop(&mut self) {
155 self.stop_best_effort();
156 }
157}
158
159impl Store {
160 pub fn close(&self) -> Result<(), StoreError> {
170 let mut state = self
171 .state
172 .lock()
173 .map_err(|_| StoreError::Synchronization { component: "state" })?;
174 loop {
175 match *state {
176 StoreState::Open => {
177 *state = StoreState::Closing;
178 break;
179 }
180 StoreState::Closing => {
181 state = self
182 .state_changed
183 .wait(state)
184 .map_err(|_| StoreError::Synchronization { component: "state" })?;
185 }
186 StoreState::Closed => return Ok(()),
187 }
188 }
189 drop(state);
190
191 let mut background = self
192 .background
193 .lock()
194 .map_err(|_| StoreError::Synchronization {
195 component: "lifecycle thread",
196 })?;
197 let stopped_background = background.take();
198 drop(background);
199 let background_result = stopped_background.map_or(Ok(()), |thread| thread.stop_and_join());
200
201 let mut snapshot = self
202 .snapshot
203 .write()
204 .map_err(|_| StoreError::Synchronization {
205 component: "published snapshot",
206 })?;
207 let released_snapshot = snapshot.take();
208 drop(snapshot);
209 if let Some(snapshot) = released_snapshot.as_ref() {
210 snapshot.drain_readers(self.reader_drain_timeout)?;
211 }
212 let mut query_pool = self
213 .query_pool
214 .lock()
215 .map_err(|_| StoreError::Synchronization {
216 component: "query pool",
217 })?;
218 let stopped_query_pool = query_pool.take();
219 drop(query_pool);
220 let query_pool_result = stopped_query_pool.map_or(Ok(()), |pool| pool.stop_and_join());
221 let mut lexical_worker =
222 self.lexical_worker
223 .lock()
224 .map_err(|_| StoreError::Synchronization {
225 component: "pooled lexical worker",
226 })?;
227 let stopped_lexical_worker = lexical_worker.take();
228 drop(lexical_worker);
229 let lexical_worker_result =
230 stopped_lexical_worker.map_or(Ok(()), |worker| worker.stop_and_join());
231 let lexical_cache_result = self
234 .lexical_index_cache
235 .entry
236 .lock()
237 .map(|mut entry| {
238 drop(entry.take());
239 })
240 .map_err(|_| StoreError::Synchronization {
241 component: "lexical index cache",
242 });
243 drop(released_snapshot);
244
245 let mut active = self
246 .active
247 .lock()
248 .map_err(|_| StoreError::Synchronization {
249 component: "active segment",
250 })?;
251 let released_active = active.take();
252 drop(active);
253 drop(released_active);
254
255 let mut wal_writer = self
256 .wal_writer
257 .lock()
258 .map_err(|_| StoreError::Synchronization {
259 component: "WAL writer",
260 })?;
261 let released_wal = wal_writer.take();
262 drop(wal_writer);
263 drop(released_wal);
264
265 let mut writer_lock = self
266 .writer_lock
267 .lock()
268 .map_err(|_| StoreError::Synchronization {
269 component: "writer lock",
270 })?;
271 let released = writer_lock.take();
272 drop(writer_lock);
273 drop(released);
274
275 let mut state = self
276 .state
277 .lock()
278 .map_err(|_| StoreError::Synchronization { component: "state" })?;
279 *state = StoreState::Closed;
280 self.state_changed.notify_all();
281 background_result
282 .and(query_pool_result)
283 .and(lexical_worker_result)
284 .and(lexical_cache_result)
285 }
286
287 pub(crate) fn close_best_effort(&mut self) {
291 let state = match self.state.get_mut() {
292 Ok(state) => state,
293 Err(poisoned) => poisoned.into_inner(),
294 };
295 if *state == StoreState::Closed {
296 return;
297 }
298 *state = StoreState::Closing;
299
300 let background_slot = match self.background.get_mut() {
301 Ok(background) => background,
302 Err(poisoned) => poisoned.into_inner(),
303 };
304 if let Some(mut background) = background_slot.take() {
305 background.stop_best_effort();
306 }
307
308 let snapshot_slot = match self.snapshot.get_mut() {
309 Ok(snapshot) => snapshot,
310 Err(poisoned) => poisoned.into_inner(),
311 };
312 let released_snapshot = snapshot_slot.take();
313 if let Some(snapshot) = released_snapshot.as_ref() {
314 snapshot.cancel_readers();
315 }
316
317 let query_pool_slot = match self.query_pool.get_mut() {
318 Ok(query_pool) => query_pool,
319 Err(poisoned) => poisoned.into_inner(),
320 };
321 if let Some(pool) = query_pool_slot.take() {
322 let _ = pool.stop_and_join();
323 }
324 let lexical_worker_slot = match self.lexical_worker.get_mut() {
325 Ok(worker) => worker,
326 Err(poisoned) => poisoned.into_inner(),
327 };
328 if let Some(worker) = lexical_worker_slot.take() {
329 worker.stop_best_effort();
330 }
331 let lexical_cache = match self.lexical_index_cache.entry.get_mut() {
332 Ok(cache) => cache,
333 Err(poisoned) => poisoned.into_inner(),
334 };
335 drop(lexical_cache.take());
336 drop(released_snapshot);
337
338 let active_slot = match self.active.get_mut() {
339 Ok(active) => active,
340 Err(poisoned) => poisoned.into_inner(),
341 };
342 drop(active_slot.take());
343
344 let wal_slot = match self.wal_writer.get_mut() {
345 Ok(wal) => wal,
346 Err(poisoned) => poisoned.into_inner(),
347 };
348 drop(wal_slot.take());
349
350 let writer_slot = match self.writer_lock.get_mut() {
351 Ok(writer_lock) => writer_lock,
352 Err(poisoned) => poisoned.into_inner(),
353 };
354 let released = writer_slot.take();
355 drop(released);
356 *state = StoreState::Closed;
357 self.state_changed.notify_all();
358 }
359}
360
361#[cfg(test)]
362#[allow(clippy::expect_used)]
363mod tests {
364 use std::sync::Arc;
365
366 use tempfile::tempdir;
367
368 use crate::lifecycle::{OpenOptions, Store};
369
370 #[test]
371 fn background_thread_stops_before_snapshot_is_released_on_drop() {
372 let directory = tempdir().expect("store directory");
373 let store = Store::open(directory.path(), OpenOptions::default()).expect("open");
374 let probe = Arc::clone(&store.teardown_probe);
375
376 drop(store);
377
378 let (background_stopped, snapshot_released) = probe.events();
379 assert!(background_stopped > 0, "background stop was not observed");
380 assert!(snapshot_released > 0, "snapshot release was not observed");
381 assert!(
382 background_stopped < snapshot_released,
383 "snapshot released at event {snapshot_released} before background stopped at event {background_stopped}"
384 );
385 }
386}