lsm_tree/close.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026-present, Dmitry Prudnikov
3
4//! Closing a tree, and learning when it no longer touches its directory.
5//!
6//! Everything of a tree that owns a file of its directory or works in it holds
7//! the tree's [`Life`]: the tree handles, the table file pool (joined refills
8//! included), the background deleter until it drained its queue, and, through
9//! the deletion pause they all share, every table, blob file and pin. A tree
10//! is closed once the last of them drops; [`TreeClose`] reports that moment.
11
12use crate::{AnyTree, BlobTree, Tree, path::PathBuf, tree::inner::TreeInner};
13use alloc::{boxed::Box, sync::Arc, sync::Weak, vec::Vec};
14use core::sync::atomic::{AtomicBool, Ordering};
15// no-std: spin::Mutex (parking_lot is std-only); waiting needs std.
16#[cfg(feature = "std")]
17use parking_lot::Mutex;
18#[cfg(not(feature = "std"))]
19use spin::Mutex;
20
21/// A callback waiting for a tree to close.
22type OnClosed = Box<dyn FnOnce() + Send>;
23
24/// What a tree's closing signal shares with its [`TreeClose`] handles.
25struct Signal {
26 closed: AtomicBool,
27 /// The callbacks to run once closed; emptied by the closing.
28 waiting: Mutex<Vec<OnClosed>>,
29 /// Woken once closed, for the callers that block.
30 #[cfg(feature = "std")]
31 settled: parking_lot::Condvar,
32}
33
34impl Signal {
35 fn fire(&self) {
36 let callbacks = {
37 let mut waiting = self.waiting.lock();
38 self.closed.store(true, Ordering::Release);
39 #[cfg(feature = "std")]
40 self.settled.notify_all();
41 core::mem::take(&mut *waiting)
42 };
43 // Outside the lock: a callback may register another, or wait.
44 for callback in callbacks {
45 run(callback);
46 }
47 }
48}
49
50/// Runs a callback of a closing tree. It runs inside a drop of the tree's own
51/// parts, possibly while a panic unwinds, or at registration on the caller's
52/// thread; either way its panic is contained, so it aborts no process and
53/// keeps no other callback from running.
54fn run(callback: impl FnOnce()) {
55 #[cfg(feature = "std")]
56 if std::panic::catch_unwind(core::panic::AssertUnwindSafe(callback)).is_err() {
57 log::error!("a TreeClose callback panicked; the others still run");
58 }
59 #[cfg(not(feature = "std"))]
60 callback();
61}
62
63/// A tree's liveness: held, as an `Arc`, by everything of the tree that owns
64/// a file of its directory. Its last drop closes the tree.
65pub struct Life {
66 signal: Arc<Signal>,
67}
68
69impl Life {
70 /// The liveness of a tree being opened.
71 pub fn new() -> Arc<Self> {
72 Arc::new(Self {
73 signal: Arc::new(Signal {
74 closed: AtomicBool::new(false),
75 waiting: Mutex::new(Vec::new()),
76 #[cfg(feature = "std")]
77 settled: parking_lot::Condvar::new(),
78 }),
79 })
80 }
81
82 /// A handle on the signal, holding no liveness itself.
83 pub fn signal(&self) -> TreeClose {
84 TreeClose {
85 signal: Arc::clone(&self.signal),
86 }
87 }
88}
89
90impl Drop for Life {
91 fn drop(&mut self) {
92 self.signal.fire();
93 }
94}
95
96/// The signal of a tree being closed, returned by
97/// [`Tree::close`](crate::Tree::close) and its counterparts.
98///
99/// A tree is closed once nothing of it owns a file of its directory or works
100/// in it any more: no tree handle, iterator, snapshot or pin holds it, its
101/// table file pool made its last file and the background deleter removed
102/// the last file it was handed. From then on the tree writes nothing into its
103/// directory, and removing the directory is safe.
104///
105/// The handle keeps nothing open itself, nor do the callbacks registered on
106/// it, so it can be held for as long as wanted.
107#[derive(Clone)]
108#[must_use = "the signal is the only way to learn when the tree is closed"]
109pub struct TreeClose {
110 signal: Arc<Signal>,
111}
112
113impl TreeClose {
114 /// Runs `f` once the tree is closed, on the thread that finished closing
115 /// it, or at once on this thread if it is closed already. Nothing waits or
116 /// polls meanwhile: an async caller completes its own oneshot from it.
117 ///
118 /// `f` runs inside the drop that closed the tree, so it should be short.
119 /// With `std` a panic in `f` is caught and logged, and the other
120 /// callbacks still run.
121 ///
122 /// # Examples
123 ///
124 /// ```
125 /// # let folder = tempfile::tempdir()?;
126 /// use std::sync::mpsc;
127 ///
128 /// let tree = lsm_tree::Config::new(
129 /// folder.path(),
130 /// lsm_tree::SequenceNumberCounter::default(),
131 /// lsm_tree::SequenceNumberCounter::default(),
132 /// )
133 /// .open()?;
134 ///
135 /// let (closed, on_closed) = mpsc::channel();
136 /// // The receiver may be gone by then: a failed send is nothing to report.
137 /// tree.close().on_closed(move || {
138 /// let _ = closed.send(());
139 /// });
140 /// on_closed.recv()?;
141 /// std::fs::remove_dir_all(folder.path())?;
142 /// # Ok::<(), Box<dyn std::error::Error>>(())
143 /// ```
144 pub fn on_closed(&self, f: impl FnOnce() + Send + 'static) {
145 let mut waiting = self.signal.waiting.lock();
146 if self.signal.closed.load(Ordering::Acquire) {
147 drop(waiting);
148 run(f);
149 } else {
150 waiting.push(Box::new(f));
151 }
152 }
153
154 /// Whether the tree is closed.
155 #[must_use]
156 pub fn is_closed(&self) -> bool {
157 self.signal.closed.load(Ordering::Acquire)
158 }
159
160 /// Blocks until the tree is closed.
161 ///
162 /// Blocks for good if the caller itself still holds a tree handle, an
163 /// iterator, a snapshot or a pin of the tree: drop them first, or use
164 /// [`on_closed`](Self::on_closed).
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// # let folder = tempfile::tempdir()?;
170 /// use lsm_tree::AbstractTree;
171 ///
172 /// let tree = lsm_tree::Config::new(
173 /// folder.path(),
174 /// lsm_tree::SequenceNumberCounter::default(),
175 /// lsm_tree::SequenceNumberCounter::default(),
176 /// )
177 /// .open()?;
178 /// tree.insert("a", "1", 0);
179 /// tree.flush_active_memtable(0)?;
180 ///
181 /// tree.close().wait();
182 /// std::fs::remove_dir_all(folder.path())?;
183 /// # Ok::<(), Box<dyn std::error::Error>>(())
184 /// ```
185 #[cfg(feature = "std")]
186 pub fn wait(&self) {
187 let mut waiting = self.signal.waiting.lock();
188 while !self.signal.closed.load(Ordering::Acquire) {
189 self.signal.settled.wait(&mut waiting);
190 }
191 }
192
193 /// Blocks until the tree is closed or `timeout` passes; whether it is
194 /// closed.
195 ///
196 /// Returns `false` at the timeout while anything of the tree is held, the
197 /// caller's own handles, iterators, snapshots and pins included.
198 #[cfg(feature = "std")]
199 #[must_use]
200 pub fn wait_timeout(&self, timeout: core::time::Duration) -> bool {
201 let mut waiting = self.signal.waiting.lock();
202 let _ = self.signal.settled.wait_while_for(
203 &mut waiting,
204 |_| !self.signal.closed.load(Ordering::Acquire),
205 timeout,
206 );
207 self.signal.closed.load(Ordering::Acquire)
208 }
209}
210
211impl core::fmt::Debug for TreeClose {
212 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
213 f.debug_struct("TreeClose")
214 .field("closed", &self.is_closed())
215 .finish()
216 }
217}
218
219/// A handle on a tree that does not keep it open, made by
220/// [`Tree::downgrade`](crate::Tree::downgrade) and its counterparts.
221///
222/// For a holder that only acts on a live tree, such as a compaction monitor
223/// or a queued flush request: it lets a dropped or closing tree close without
224/// waiting for the holder.
225#[derive(Clone)]
226pub struct WeakTree {
227 inner: Weak<TreeInner>,
228 /// The blob folder of a key-value-separated tree.
229 blobs_folder: Option<Arc<PathBuf>>,
230}
231
232impl WeakTree {
233 pub(crate) fn of(tree: &Tree, blobs_folder: Option<Arc<PathBuf>>) -> Self {
234 Self {
235 inner: Arc::downgrade(&tree.0),
236 blobs_folder,
237 }
238 }
239
240 /// A strong handle on the tree, or `None` once every strong handle is
241 /// gone or the tree is being closed.
242 ///
243 /// A tree being closed is not raised again, even while strong handles
244 /// taken earlier still finish their work, so a holder woken in between
245 /// starts no new work the closing would then wait for.
246 #[must_use]
247 pub fn upgrade(&self) -> Option<AnyTree> {
248 let inner = self.inner.upgrade()?;
249 #[cfg(test)]
250 crate::tree::inner::TestHooks::fire(&inner.test_hooks.upgrade);
251 // The one point `close` and `upgrade` meet: read after the strong
252 // handle is taken, so a flag read clear means `close` comes after,
253 // and the closing waits for this handle like any other.
254 if inner.closing.load(Ordering::SeqCst) {
255 return None;
256 }
257 let index = Tree(inner);
258 Some(match &self.blobs_folder {
259 None => AnyTree::Standard(index),
260 Some(blobs_folder) => AnyTree::Blob(BlobTree {
261 index,
262 blobs_folder: Arc::clone(blobs_folder),
263 }),
264 })
265 }
266}
267
268impl core::fmt::Debug for WeakTree {
269 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
270 f.debug_struct("WeakTree")
271 .field("blob", &self.blobs_folder.is_some())
272 .finish_non_exhaustive()
273 }
274}
275
276/// Gives up `tree`'s handle and returns its closing signal: from here no weak
277/// handle raises the tree again.
278pub fn close(tree: Tree) -> TreeClose {
279 let signal = tree.life.signal();
280 tree.closing.store(true, Ordering::SeqCst);
281 drop(tree);
282 signal
283}
284
285#[cfg(test)]
286#[expect(clippy::unwrap_used, clippy::expect_used, reason = "test code")]
287mod tests;