hcklib/
mmap.rs

1use std::fs::File;
2use std::path::Path;
3
4use memmap::Mmap;
5
6/// Controls the strategy used for determining when to use memory maps.
7///
8/// If a searcher is called in circumstances where it is possible to use memory
9/// maps, and memory maps are enabled, then it will attempt to do so if it
10/// believes it will make the search faster.
11///
12/// By default, memory maps are disabled.
13#[derive(Clone, Debug, Copy)]
14pub struct MmapChoice(MmapChoiceImpl);
15
16#[derive(Clone, Debug, Copy)]
17enum MmapChoiceImpl {
18    Auto,
19    Never,
20}
21
22impl Default for MmapChoice {
23    fn default() -> MmapChoice {
24        MmapChoice(MmapChoiceImpl::Never)
25    }
26}
27
28impl MmapChoice {
29    /// Use memory maps when they are believed to be advantageous.
30    ///
31    /// The heuristics used to determine whether to use a memory map or not
32    /// may depend on many things, including but not limited to, file size
33    /// and platform.
34    ///
35    /// If memory maps are unavailable or cannot be used for a specific input,
36    /// then normal OS read calls are used instead.
37    ///
38    /// # Safety
39    ///
40    /// This constructor is not safe because there is no obvious way to
41    /// encapsulate the safety of file backed memory maps on all platforms
42    /// without simultaneously negating some or all of their benefits.
43    ///
44    /// The specific contract the caller is required to uphold isn't precise,
45    /// but it basically amounts to something like, "the caller guarantees that
46    /// the underlying file won't be mutated." This, of course, isn't feasible
47    /// in many environments. However, command line tools may still decide to
48    /// take the risk of, say, a `SIGBUS` occurring while attempting to read a
49    /// memory map.
50    pub unsafe fn auto() -> MmapChoice {
51        MmapChoice(MmapChoiceImpl::Auto)
52    }
53
54    /// Never use memory maps, no matter what. This is the default.
55    pub fn never() -> MmapChoice {
56        MmapChoice(MmapChoiceImpl::Never)
57    }
58
59    /// Return a memory map if memory maps are enabled and if creating a
60    /// memory from the given file succeeded and if memory maps are believed
61    /// to be advantageous for performance.
62    ///
63    /// If this does attempt to open a memory map and it fails, then `None`
64    /// is returned and the corresponding error (along with the file path, if
65    /// present) is logged at the debug level.
66    pub(crate) fn open<P: AsRef<Path>>(&self, file: &File, path: Option<&P>) -> Option<Mmap> {
67        if !self.is_enabled() {
68            return None;
69        }
70        if !cfg!(target_pointer_width = "64") {
71            // For 32-bit systems, it looks like mmap will succeed even if it
72            // can't address the entire file. This seems to happen at least on
73            // Windows, even though it uses to work prior to ripgrep 13. The
74            // only Windows-related change in ripgrep 13, AFAIK, was statically
75            // linking vcruntime. So maybe that's related? But I'm not sure.
76            //
77            // See: https://github.com/BurntSushi/ripgrep/issues/1911
78            return None;
79        }
80        if cfg!(target_os = "macos") {
81            // I guess memory maps on macOS aren't great. Should re-evaluate.
82            return None;
83        }
84        // SAFETY: This is acceptable because the only way `MmapChoiceImpl` can
85        // be `Auto` is if the caller invoked the `auto` constructor, which
86        // is itself not safe. Thus, this is a propagation of the caller's
87        // assertion that using memory maps is safe.
88        match unsafe { Mmap::map(file) } {
89            Ok(mmap) => Some(mmap),
90            Err(err) => {
91                if let Some(path) = path {
92                    log::debug!(
93                        "{}: failed to open memory map: {}",
94                        path.as_ref().display(),
95                        err
96                    );
97                } else {
98                    log::debug!("failed to open memory map: {}", err);
99                }
100                None
101            }
102        }
103    }
104
105    /// Whether this strategy may employ memory maps or not.
106    pub(crate) fn is_enabled(&self) -> bool {
107        match self.0 {
108            MmapChoiceImpl::Auto => true,
109            MmapChoiceImpl::Never => false,
110        }
111    }
112}