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}