git_tempfile/
handler.rs

1//!
2use std::sync::atomic::Ordering;
3
4use crate::{SignalHandlerMode, NEXT_MAP_INDEX, REGISTER, SIGNAL_HANDLER_MODE};
5
6/// Remove all tempfiles still registered on our global registry.
7///
8/// # Safety
9/// Note that Mutexes of any kind are not allowed, and so aren't allocation or deallocation of memory.
10/// We are using lock-free datastructures and sprinkle in `std::mem::forget` to avoid deallocating.
11/// Most importantly, we use `try_lock()` which uses an atomic int only without waiting, making our register safe to use,
12/// at the expense of possibly missing a lock file if another thread wants to obtain it or put it back
13/// (i.e. mutates the register shard).
14pub fn cleanup_tempfiles() {
15    let current_pid = std::process::id();
16    let one_past_last_index = NEXT_MAP_INDEX.load(Ordering::SeqCst);
17    for idx in 0..one_past_last_index {
18        if let Some(entry) = REGISTER.try_entry(idx) {
19            entry.and_modify(|tempfile| {
20                if tempfile
21                    .as_ref()
22                    .map_or(false, |tf| tf.owning_process_id == current_pid)
23                {
24                    if let Some(tempfile) = tempfile.take() {
25                        tempfile.drop_without_deallocation();
26                    }
27                }
28            });
29        }
30    }
31}
32
33/// On linux we can handle the actual signal as we know it.
34#[cfg(not(windows))]
35pub(crate) fn cleanup_tempfiles_nix(sig: &libc::siginfo_t) {
36    cleanup_tempfiles();
37    let restore_original_behaviour = SignalHandlerMode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour as usize;
38    if SIGNAL_HANDLER_MODE.load(std::sync::atomic::Ordering::SeqCst) == restore_original_behaviour {
39        signal_hook::low_level::emulate_default_handler(sig.si_signo).ok();
40    }
41}
42
43/// On windows, assume sig-term and emulate sig-term unconditionally.
44#[cfg(windows)]
45pub(crate) fn cleanup_tempfiles_windows() {
46    cleanup_tempfiles();
47    let restore_original_behaviour = SignalHandlerMode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour as usize;
48    if SIGNAL_HANDLER_MODE.load(std::sync::atomic::Ordering::SeqCst) == restore_original_behaviour {
49        signal_hook::low_level::emulate_default_handler(signal_hook::consts::SIGTERM).ok();
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use std::path::Path;
56
57    use crate::{AutoRemove, ContainingDirectory};
58
59    fn filecount_in(path: impl AsRef<Path>) -> usize {
60        std::fs::read_dir(path).expect("valid dir").count()
61    }
62
63    #[test]
64    fn various_termination_signals_remove_tempfiles_unconditionally() -> Result<(), Box<dyn std::error::Error>> {
65        crate::setup(Default::default());
66        let dir = tempfile::tempdir()?;
67        for sig in signal_hook::consts::TERM_SIGNALS {
68            let _tempfile = crate::new(dir.path(), ContainingDirectory::Exists, AutoRemove::Tempfile)?;
69            assert_eq!(
70                filecount_in(dir.path()),
71                1,
72                "only one tempfile exists no matter the iteration"
73            );
74            signal_hook::low_level::raise(*sig)?;
75            assert_eq!(
76                filecount_in(dir.path()),
77                0,
78                "the signal triggers removal but won't terminate the process (anymore)"
79            );
80        }
81        Ok(())
82    }
83}