Skip to main content

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;