Skip to main content

node_resolver/
cache.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3use std::borrow::Cow;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::path::Path;
7use std::path::PathBuf;
8
9use sys_traits::BaseFsCanonicalize;
10use sys_traits::BaseFsOpen;
11use sys_traits::BaseFsRead;
12use sys_traits::BaseFsReadDir;
13use sys_traits::FileType;
14use sys_traits::FsCanonicalize;
15use sys_traits::FsMetadata;
16use sys_traits::FsMetadataValue;
17use sys_traits::FsOpen;
18use sys_traits::FsRead;
19use sys_traits::FsReadDir;
20
21pub trait NodeResolutionCache:
22  std::fmt::Debug + deno_maybe_sync::MaybeSend + deno_maybe_sync::MaybeSync
23{
24  fn get_canonicalized(
25    &self,
26    path: &Path,
27  ) -> Option<Result<PathBuf, std::io::Error>>;
28  fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>);
29  fn get_file_type(&self, path: &Path) -> Option<Option<FileType>>;
30  fn set_file_type(&self, path: PathBuf, value: Option<FileType>);
31}
32
33thread_local! {
34  static CANONICALIZED_CACHE: RefCell<HashMap<PathBuf, Option<PathBuf>>> = RefCell::new(HashMap::new());
35  static FILE_TYPE_CACHE: RefCell<HashMap<PathBuf, Option<FileType>>> = RefCell::new(HashMap::new());
36}
37
38// We use thread local caches here because it's just more convenient
39// and easily allows workers to have separate caches.
40#[derive(Debug)]
41pub struct NodeResolutionThreadLocalCache;
42
43impl NodeResolutionThreadLocalCache {
44  pub fn clear() {
45    CANONICALIZED_CACHE.with_borrow_mut(|cache| cache.clear());
46    FILE_TYPE_CACHE.with_borrow_mut(|cache| cache.clear());
47  }
48}
49
50impl NodeResolutionCache for NodeResolutionThreadLocalCache {
51  fn get_canonicalized(
52    &self,
53    path: &Path,
54  ) -> Option<Result<PathBuf, std::io::Error>> {
55    CANONICALIZED_CACHE.with_borrow(|cache| {
56      let item = cache.get(path)?;
57      Some(match item {
58        Some(value) => Ok(value.clone()),
59        None => Err(std::io::Error::new(
60          std::io::ErrorKind::NotFound,
61          "Not found.",
62        )),
63      })
64    })
65  }
66
67  fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>) {
68    CANONICALIZED_CACHE.with_borrow_mut(|cache| match to {
69      Ok(to) => {
70        cache.insert(from, Some(to.clone()));
71      }
72      Err(err) => {
73        if err.kind() == std::io::ErrorKind::NotFound {
74          cache.insert(from, None);
75        }
76      }
77    });
78  }
79
80  fn get_file_type(&self, path: &Path) -> Option<Option<FileType>> {
81    FILE_TYPE_CACHE.with_borrow(|cache| cache.get(path).cloned())
82  }
83
84  fn set_file_type(&self, path: PathBuf, value: Option<FileType>) {
85    FILE_TYPE_CACHE.with_borrow_mut(|cache| {
86      cache.insert(path, value);
87    })
88  }
89}
90
91#[allow(clippy::disallowed_types, reason = "definition")]
92pub type NodeResolutionCacheRc =
93  deno_maybe_sync::MaybeArc<dyn NodeResolutionCache>;
94
95#[derive(Debug, Default)]
96pub struct NodeResolutionSys<TSys> {
97  sys: TSys,
98  cache: Option<NodeResolutionCacheRc>,
99}
100
101impl<TSys: Clone> Clone for NodeResolutionSys<TSys> {
102  fn clone(&self) -> Self {
103    Self {
104      sys: self.sys.clone(),
105      cache: self.cache.clone(),
106    }
107  }
108}
109
110impl<TSys: FsMetadata> NodeResolutionSys<TSys> {
111  pub fn new(sys: TSys, store: Option<NodeResolutionCacheRc>) -> Self {
112    Self { sys, cache: store }
113  }
114
115  pub fn has_cache(&self) -> bool {
116    self.cache.is_some()
117  }
118
119  pub fn is_file(&self, path: Cow<'_, Path>) -> bool {
120    match self.get_file_type(path) {
121      Ok(file_type) => file_type.is_file(),
122      Err(_) => false,
123    }
124  }
125
126  pub fn is_dir(&self, path: Cow<'_, Path>) -> bool {
127    match self.get_file_type(path) {
128      Ok(file_type) => file_type.is_dir(),
129      Err(_) => false,
130    }
131  }
132
133  pub fn exists_(&self, path: Cow<'_, Path>) -> bool {
134    self.get_file_type(path).is_ok()
135  }
136
137  pub fn get_file_type(
138    &self,
139    path: Cow<'_, Path>,
140  ) -> std::io::Result<FileType> {
141    {
142      if let Some(maybe_value) =
143        self.cache.as_ref().and_then(|c| c.get_file_type(&path))
144      {
145        return match maybe_value {
146          Some(value) => Ok(value),
147          None => Err(std::io::Error::new(
148            std::io::ErrorKind::NotFound,
149            "Not found.",
150          )),
151        };
152      }
153    }
154    match self.sys.fs_metadata(&path) {
155      Ok(metadata) => {
156        if let Some(cache) = &self.cache {
157          cache.set_file_type(path.into_owned(), Some(metadata.file_type()));
158        }
159        Ok(metadata.file_type())
160      }
161      Err(err) => {
162        if let Some(cache) = &self.cache {
163          cache.set_file_type(path.into_owned(), None);
164        }
165        Err(err)
166      }
167    }
168  }
169}
170
171impl<TSys: FsCanonicalize> BaseFsCanonicalize for NodeResolutionSys<TSys> {
172  fn base_fs_canonicalize(&self, from: &Path) -> std::io::Result<PathBuf> {
173    if let Some(cache) = &self.cache
174      && let Some(result) = cache.get_canonicalized(from)
175    {
176      return result;
177    }
178    let result = self.sys.base_fs_canonicalize(from);
179    if let Some(cache) = &self.cache {
180      cache.set_canonicalized(from.to_path_buf(), &result);
181    }
182    result
183  }
184}
185
186impl<TSys: FsReadDir> BaseFsReadDir for NodeResolutionSys<TSys> {
187  type ReadDirEntry = TSys::ReadDirEntry;
188
189  #[inline(always)]
190  fn base_fs_read_dir(
191    &self,
192    path: &Path,
193  ) -> std::io::Result<
194    Box<dyn Iterator<Item = std::io::Result<Self::ReadDirEntry>>>,
195  > {
196    self.sys.base_fs_read_dir(path)
197  }
198}
199
200impl<TSys: FsRead> BaseFsRead for NodeResolutionSys<TSys> {
201  #[inline(always)]
202  fn base_fs_read(
203    &self,
204    path: &Path,
205  ) -> std::io::Result<std::borrow::Cow<'static, [u8]>> {
206    self.sys.base_fs_read(path)
207  }
208}
209
210impl<TSys: FsOpen> BaseFsOpen for NodeResolutionSys<TSys> {
211  type File = TSys::File;
212
213  fn base_fs_open(
214    &self,
215    path: &Path,
216    flags: &sys_traits::OpenOptions,
217  ) -> std::io::Result<Self::File> {
218    self.sys.base_fs_open(path, flags)
219  }
220}