xref: /wasmtime-44.0.1/crates/cache/src/lib.rs (revision bbd12e92)
14c8edb95SAlex Crichton //! > **⚠️ Warning ⚠️**: this crate is an internal-only crate for the Wasmtime
24c8edb95SAlex Crichton //! > project and is not intended for general use. APIs are not strictly
34c8edb95SAlex Crichton //! > reviewed for safety and usage outside of Wasmtime may have bugs. If
44c8edb95SAlex Crichton //! > you're interested in using this feel free to file an issue on the
54c8edb95SAlex Crichton //! > Wasmtime repository to start a discussion about doing so, but otherwise
64c8edb95SAlex Crichton //! > be aware that your usage of this crate is not supported.
74c8edb95SAlex Crichton 
808403c99SBrendan Burns use base64::Engine;
908f9eb17SAlex Crichton use log::{debug, trace, warn};
1008f9eb17SAlex Crichton use serde::{Deserialize, Serialize};
1108f9eb17SAlex Crichton use sha2::{Digest, Sha256};
1208f9eb17SAlex Crichton use std::hash::Hash;
1308f9eb17SAlex Crichton use std::hash::Hasher;
1408f9eb17SAlex Crichton use std::io::Write;
1508f9eb17SAlex Crichton use std::path::{Path, PathBuf};
162cd52b76SBen Brandt use std::sync::Arc;
1790ac295eSAlex Crichton use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
182cd52b76SBen Brandt use std::time::Duration;
191ced2ef4SXinzhao Xu use std::{fs, io};
20*bbd12e92SNick Fitzgerald use wasmtime_environ::error::Result;
2108f9eb17SAlex Crichton 
2208f9eb17SAlex Crichton #[macro_use] // for tests
2308f9eb17SAlex Crichton mod config;
2408f9eb17SAlex Crichton mod worker;
2508f9eb17SAlex Crichton 
2690ac295eSAlex Crichton pub use config::{CacheConfig, create_new_config};
2708f9eb17SAlex Crichton use worker::Worker;
2808f9eb17SAlex Crichton 
292cd52b76SBen Brandt /// Global configuration for how the cache is managed
302cd52b76SBen Brandt #[derive(Debug, Clone)]
312cd52b76SBen Brandt pub struct Cache {
322cd52b76SBen Brandt     config: CacheConfig,
332cd52b76SBen Brandt     worker: Worker,
342cd52b76SBen Brandt     state: Arc<CacheState>,
352cd52b76SBen Brandt }
3608f9eb17SAlex Crichton 
372cd52b76SBen Brandt macro_rules! generate_config_setting_getter {
382cd52b76SBen Brandt     ($setting:ident: $setting_type:ty) => {
39390e1549SSaúl Cabrera         #[doc = concat!("Returns ", "`", stringify!($setting), "`.")]
402cd52b76SBen Brandt         pub fn $setting(&self) -> $setting_type {
412cd52b76SBen Brandt             self.config.$setting()
422cd52b76SBen Brandt         }
432cd52b76SBen Brandt     };
442cd52b76SBen Brandt }
452cd52b76SBen Brandt 
462cd52b76SBen Brandt impl Cache {
472cd52b76SBen Brandt     /// Builds a [`Cache`] from the configuration and spawns the cache worker.
482cd52b76SBen Brandt     ///
492cd52b76SBen Brandt     /// If you want to load the cache configuration from a file, use [`CacheConfig::from_file`].
502cd52b76SBen Brandt     /// You can call [`CacheConfig::new`] for the default configuration.
512cd52b76SBen Brandt     ///
522cd52b76SBen Brandt     /// # Errors
532cd52b76SBen Brandt     /// Returns an error if the configuration is invalid.
new(mut config: CacheConfig) -> Result<Self>542cd52b76SBen Brandt     pub fn new(mut config: CacheConfig) -> Result<Self> {
552cd52b76SBen Brandt         config.validate()?;
562cd52b76SBen Brandt         Ok(Self {
572cd52b76SBen Brandt             worker: Worker::start_new(&config),
582cd52b76SBen Brandt             config,
592cd52b76SBen Brandt             state: Default::default(),
602cd52b76SBen Brandt         })
612cd52b76SBen Brandt     }
622cd52b76SBen Brandt 
632cd52b76SBen Brandt     /// Loads cache configuration specified at `path`.
642cd52b76SBen Brandt     ///
652cd52b76SBen Brandt     /// This method will read the file specified by `path` on the filesystem and
662cd52b76SBen Brandt     /// attempt to load cache configuration from it. This method can also fail
672cd52b76SBen Brandt     /// due to I/O errors, misconfiguration, syntax errors, etc. For expected
682cd52b76SBen Brandt     /// syntax in the configuration file see the [documentation online][docs].
692cd52b76SBen Brandt     ///
702cd52b76SBen Brandt     /// Passing in `None` loads cache configuration from the system default path.
712cd52b76SBen Brandt     /// This is located, for example, on Unix at `$HOME/.config/wasmtime/config.toml`
722cd52b76SBen Brandt     /// and is typically created with the `wasmtime config new` command.
732cd52b76SBen Brandt     ///
742cd52b76SBen Brandt     /// # Errors
752cd52b76SBen Brandt     ///
762cd52b76SBen Brandt     /// This method can fail due to any error that happens when loading the file
772cd52b76SBen Brandt     /// pointed to by `path` and attempting to load the cache configuration.
782cd52b76SBen Brandt     ///
792cd52b76SBen Brandt     /// [docs]: https://bytecodealliance.github.io/wasmtime/cli-cache.html
from_file(path: Option<&Path>) -> Result<Self>802cd52b76SBen Brandt     pub fn from_file(path: Option<&Path>) -> Result<Self> {
812cd52b76SBen Brandt         let config = CacheConfig::from_file(path)?;
822cd52b76SBen Brandt         Self::new(config)
832cd52b76SBen Brandt     }
842cd52b76SBen Brandt 
852cd52b76SBen Brandt     generate_config_setting_getter!(worker_event_queue_size: u64);
862cd52b76SBen Brandt     generate_config_setting_getter!(baseline_compression_level: i32);
872cd52b76SBen Brandt     generate_config_setting_getter!(optimized_compression_level: i32);
882cd52b76SBen Brandt     generate_config_setting_getter!(optimized_compression_usage_counter_threshold: u64);
892cd52b76SBen Brandt     generate_config_setting_getter!(cleanup_interval: Duration);
902cd52b76SBen Brandt     generate_config_setting_getter!(optimizing_compression_task_timeout: Duration);
912cd52b76SBen Brandt     generate_config_setting_getter!(allowed_clock_drift_for_files_from_future: Duration);
922cd52b76SBen Brandt     generate_config_setting_getter!(file_count_soft_limit: u64);
932cd52b76SBen Brandt     generate_config_setting_getter!(files_total_size_soft_limit: u64);
942cd52b76SBen Brandt     generate_config_setting_getter!(file_count_limit_percent_if_deleting: u8);
952cd52b76SBen Brandt     generate_config_setting_getter!(files_total_size_limit_percent_if_deleting: u8);
962cd52b76SBen Brandt 
972cd52b76SBen Brandt     /// Returns path to the cache directory.
directory(&self) -> &PathBuf982cd52b76SBen Brandt     pub fn directory(&self) -> &PathBuf {
9961a371acSJesse Rusak         &self
10061a371acSJesse Rusak             .config
10161a371acSJesse Rusak             .directory()
10261a371acSJesse Rusak             .expect("directory should be validated in Config::new")
1032cd52b76SBen Brandt     }
1042cd52b76SBen Brandt 
1052cd52b76SBen Brandt     #[cfg(test)]
worker(&self) -> &Worker1062cd52b76SBen Brandt     fn worker(&self) -> &Worker {
1072cd52b76SBen Brandt         &self.worker
1082cd52b76SBen Brandt     }
1092cd52b76SBen Brandt 
1102cd52b76SBen Brandt     /// Returns the number of cache hits seen so far
cache_hits(&self) -> usize1112cd52b76SBen Brandt     pub fn cache_hits(&self) -> usize {
1122cd52b76SBen Brandt         self.state.hits.load(SeqCst)
1132cd52b76SBen Brandt     }
1142cd52b76SBen Brandt 
1152cd52b76SBen Brandt     /// Returns the number of cache misses seen so far
cache_misses(&self) -> usize1162cd52b76SBen Brandt     pub fn cache_misses(&self) -> usize {
1172cd52b76SBen Brandt         self.state.misses.load(SeqCst)
1182cd52b76SBen Brandt     }
1192cd52b76SBen Brandt 
on_cache_get_async(&self, path: impl AsRef<Path>)1202cd52b76SBen Brandt     pub(crate) fn on_cache_get_async(&self, path: impl AsRef<Path>) {
1212cd52b76SBen Brandt         self.state.hits.fetch_add(1, SeqCst);
1222cd52b76SBen Brandt         self.worker.on_cache_get_async(path)
1232cd52b76SBen Brandt     }
1242cd52b76SBen Brandt 
on_cache_update_async(&self, path: impl AsRef<Path>)1252cd52b76SBen Brandt     pub(crate) fn on_cache_update_async(&self, path: impl AsRef<Path>) {
1262cd52b76SBen Brandt         self.state.misses.fetch_add(1, SeqCst);
1272cd52b76SBen Brandt         self.worker.on_cache_update_async(path)
1282cd52b76SBen Brandt     }
1292cd52b76SBen Brandt }
1302cd52b76SBen Brandt 
1312cd52b76SBen Brandt #[derive(Default, Debug)]
1322cd52b76SBen Brandt struct CacheState {
1332cd52b76SBen Brandt     hits: AtomicUsize,
1342cd52b76SBen Brandt     misses: AtomicUsize,
1352cd52b76SBen Brandt }
1362cd52b76SBen Brandt 
1372cd52b76SBen Brandt /// Module level cache entry.
1382cd52b76SBen Brandt pub struct ModuleCacheEntry<'cache>(Option<ModuleCacheEntryInner<'cache>>);
1392cd52b76SBen Brandt 
1402cd52b76SBen Brandt struct ModuleCacheEntryInner<'cache> {
14108f9eb17SAlex Crichton     root_path: PathBuf,
1422cd52b76SBen Brandt     cache: &'cache Cache,
14308f9eb17SAlex Crichton }
14408f9eb17SAlex Crichton 
14508f9eb17SAlex Crichton struct Sha256Hasher(Sha256);
14608f9eb17SAlex Crichton 
1472cd52b76SBen Brandt impl<'cache> ModuleCacheEntry<'cache> {
14808f9eb17SAlex Crichton     /// Create the cache entry.
new(compiler_name: &str, cache: Option<&'cache Cache>) -> Self1492cd52b76SBen Brandt     pub fn new(compiler_name: &str, cache: Option<&'cache Cache>) -> Self {
1502cd52b76SBen Brandt         Self(cache.map(|cache| ModuleCacheEntryInner::new(compiler_name, cache)))
15108f9eb17SAlex Crichton     }
15208f9eb17SAlex Crichton 
15308f9eb17SAlex Crichton     #[cfg(test)]
from_inner(inner: ModuleCacheEntryInner<'cache>) -> Self1542cd52b76SBen Brandt     fn from_inner(inner: ModuleCacheEntryInner<'cache>) -> Self {
15508f9eb17SAlex Crichton         Self(Some(inner))
15608f9eb17SAlex Crichton     }
15708f9eb17SAlex Crichton 
158c73be1f1SAlex Crichton     /// Gets cached data if state matches, otherwise calls `compute`.
159c73be1f1SAlex Crichton     ///
160c73be1f1SAlex Crichton     /// Data is automatically serialized/deserialized with `bincode`.
get_data<T, U, E>(&self, state: T, compute: fn(&T) -> Result<U, E>) -> Result<U, E> where T: Hash, U: Serialize + for<'a> Deserialize<'a>,161c73be1f1SAlex Crichton     pub fn get_data<T, U, E>(&self, state: T, compute: fn(&T) -> Result<U, E>) -> Result<U, E>
16208f9eb17SAlex Crichton     where
16308f9eb17SAlex Crichton         T: Hash,
16408f9eb17SAlex Crichton         U: Serialize + for<'a> Deserialize<'a>,
16508f9eb17SAlex Crichton     {
166c73be1f1SAlex Crichton         self.get_data_raw(
167c73be1f1SAlex Crichton             &state,
168c73be1f1SAlex Crichton             compute,
16960539520SAlex Crichton             |_state, data| postcard::to_allocvec(data).ok(),
17060539520SAlex Crichton             |_state, data| postcard::from_bytes(&data).ok(),
171c73be1f1SAlex Crichton         )
172c73be1f1SAlex Crichton     }
173c73be1f1SAlex Crichton 
174c73be1f1SAlex Crichton     /// Gets cached data if state matches, otherwise calls `compute`.
175c73be1f1SAlex Crichton     ///
176c73be1f1SAlex Crichton     /// If the cache is disabled or no cached data is found then `compute` is
177c73be1f1SAlex Crichton     /// called to calculate the data. If the data was found in cache it is
178c73be1f1SAlex Crichton     /// passed to `deserialize`, which if successful will be the returned value.
179c73be1f1SAlex Crichton     /// When computed the `serialize` function is used to generate the bytes
180c73be1f1SAlex Crichton     /// from the returned value.
get_data_raw<T, U, E>( &self, state: &T, compute: fn(&T) -> Result<U, E>, serialize: fn(&T, &U) -> Option<Vec<u8>>, deserialize: fn(&T, Vec<u8>) -> Option<U>, ) -> Result<U, E> where T: Hash,181c73be1f1SAlex Crichton     pub fn get_data_raw<T, U, E>(
182c73be1f1SAlex Crichton         &self,
183c73be1f1SAlex Crichton         state: &T,
184c73be1f1SAlex Crichton         // NOTE: These are function pointers instead of closures so that they
185c73be1f1SAlex Crichton         // don't accidentally close over something not accounted in the cache.
186c73be1f1SAlex Crichton         compute: fn(&T) -> Result<U, E>,
187c73be1f1SAlex Crichton         serialize: fn(&T, &U) -> Option<Vec<u8>>,
188c73be1f1SAlex Crichton         deserialize: fn(&T, Vec<u8>) -> Option<U>,
189c73be1f1SAlex Crichton     ) -> Result<U, E>
190c73be1f1SAlex Crichton     where
191c73be1f1SAlex Crichton         T: Hash,
192c73be1f1SAlex Crichton     {
1935c4c03d2SAlex Crichton         let inner = match &self.0 {
1945c4c03d2SAlex Crichton             Some(inner) => inner,
1955c4c03d2SAlex Crichton             None => return compute(state),
1965c4c03d2SAlex Crichton         };
1975c4c03d2SAlex Crichton 
19808f9eb17SAlex Crichton         let mut hasher = Sha256Hasher(Sha256::new());
19908f9eb17SAlex Crichton         state.hash(&mut hasher);
200e4c3fc5cSAlex Crichton         let hash: [u8; 32] = hasher.0.finalize().into();
20108f9eb17SAlex Crichton         // standard encoding uses '/' which can't be used for filename
20208403c99SBrendan Burns         let hash = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&hash);
20308f9eb17SAlex Crichton 
20408f9eb17SAlex Crichton         if let Some(cached_val) = inner.get_data(&hash) {
205c73be1f1SAlex Crichton             if let Some(val) = deserialize(state, cached_val) {
20608f9eb17SAlex Crichton                 let mod_cache_path = inner.root_path.join(&hash);
2072cd52b76SBen Brandt                 inner.cache.on_cache_get_async(&mod_cache_path); // call on success
208c73be1f1SAlex Crichton                 return Ok(val);
209c73be1f1SAlex Crichton             }
21008f9eb17SAlex Crichton         }
21108f9eb17SAlex Crichton         let val_to_cache = compute(state)?;
212c73be1f1SAlex Crichton         if let Some(bytes) = serialize(state, &val_to_cache) {
213c73be1f1SAlex Crichton             if inner.update_data(&hash, &bytes).is_some() {
21408f9eb17SAlex Crichton                 let mod_cache_path = inner.root_path.join(&hash);
2152cd52b76SBen Brandt                 inner.cache.on_cache_update_async(&mod_cache_path); // call on success
21608f9eb17SAlex Crichton             }
217c73be1f1SAlex Crichton         }
21808f9eb17SAlex Crichton         Ok(val_to_cache)
21908f9eb17SAlex Crichton     }
22008f9eb17SAlex Crichton }
22108f9eb17SAlex Crichton 
2222cd52b76SBen Brandt impl<'cache> ModuleCacheEntryInner<'cache> {
new(compiler_name: &str, cache: &'cache Cache) -> Self2232cd52b76SBen Brandt     fn new(compiler_name: &str, cache: &'cache Cache) -> Self {
22408f9eb17SAlex Crichton         // If debug assertions are enabled then assume that we're some sort of
22508f9eb17SAlex Crichton         // local build. We don't want local builds to stomp over caches between
22608f9eb17SAlex Crichton         // builds, so just use a separate cache directory based on the mtime of
22708f9eb17SAlex Crichton         // our executable, which should roughly correlate with "you changed the
22808f9eb17SAlex Crichton         // source code so you get a different directory".
22908f9eb17SAlex Crichton         //
23008f9eb17SAlex Crichton         // Otherwise if this is a release build we use the `GIT_REV` env var
23108f9eb17SAlex Crichton         // which is either the git rev if installed from git or the crate
23208f9eb17SAlex Crichton         // version if installed from crates.io.
23308f9eb17SAlex Crichton         let compiler_dir = if cfg!(debug_assertions) {
23408f9eb17SAlex Crichton             fn self_mtime() -> Option<String> {
23508f9eb17SAlex Crichton                 let path = std::env::current_exe().ok()?;
23608f9eb17SAlex Crichton                 let metadata = path.metadata().ok()?;
23708f9eb17SAlex Crichton                 let mtime = metadata.modified().ok()?;
23808f9eb17SAlex Crichton                 Some(match mtime.duration_since(std::time::UNIX_EPOCH) {
23908f9eb17SAlex Crichton                     Ok(dur) => format!("{}", dur.as_millis()),
24008f9eb17SAlex Crichton                     Err(err) => format!("m{}", err.duration().as_millis()),
24108f9eb17SAlex Crichton                 })
24208f9eb17SAlex Crichton             }
24308f9eb17SAlex Crichton             let self_mtime = self_mtime().unwrap_or("no-mtime".to_string());
24408f9eb17SAlex Crichton             format!(
24508f9eb17SAlex Crichton                 "{comp_name}-{comp_ver}-{comp_mtime}",
24608f9eb17SAlex Crichton                 comp_name = compiler_name,
24708f9eb17SAlex Crichton                 comp_ver = env!("GIT_REV"),
24808f9eb17SAlex Crichton                 comp_mtime = self_mtime,
24908f9eb17SAlex Crichton             )
25008f9eb17SAlex Crichton         } else {
25108f9eb17SAlex Crichton             format!(
25208f9eb17SAlex Crichton                 "{comp_name}-{comp_ver}",
25308f9eb17SAlex Crichton                 comp_name = compiler_name,
25408f9eb17SAlex Crichton                 comp_ver = env!("GIT_REV"),
25508f9eb17SAlex Crichton             )
25608f9eb17SAlex Crichton         };
2572cd52b76SBen Brandt         let root_path = cache.directory().join("modules").join(compiler_dir);
25808f9eb17SAlex Crichton 
2592cd52b76SBen Brandt         Self { root_path, cache }
26008f9eb17SAlex Crichton     }
26108f9eb17SAlex Crichton 
get_data(&self, hash: &str) -> Option<Vec<u8>>262c73be1f1SAlex Crichton     fn get_data(&self, hash: &str) -> Option<Vec<u8>> {
26308f9eb17SAlex Crichton         let mod_cache_path = self.root_path.join(hash);
26408f9eb17SAlex Crichton         trace!("get_data() for path: {}", mod_cache_path.display());
26508f9eb17SAlex Crichton         let compressed_cache_bytes = fs::read(&mod_cache_path).ok()?;
26608f9eb17SAlex Crichton         let cache_bytes = zstd::decode_all(&compressed_cache_bytes[..])
2672bac6574SAlex Crichton             .map_err(|err| warn!("Failed to decompress cached code: {err}"))
26808f9eb17SAlex Crichton             .ok()?;
269c73be1f1SAlex Crichton         Some(cache_bytes)
27008f9eb17SAlex Crichton     }
27108f9eb17SAlex Crichton 
update_data(&self, hash: &str, serialized_data: &[u8]) -> Option<()>272c73be1f1SAlex Crichton     fn update_data(&self, hash: &str, serialized_data: &[u8]) -> Option<()> {
27308f9eb17SAlex Crichton         let mod_cache_path = self.root_path.join(hash);
27408f9eb17SAlex Crichton         trace!("update_data() for path: {}", mod_cache_path.display());
27508f9eb17SAlex Crichton         let compressed_data = zstd::encode_all(
27608f9eb17SAlex Crichton             &serialized_data[..],
2772cd52b76SBen Brandt             self.cache.baseline_compression_level(),
27808f9eb17SAlex Crichton         )
2792bac6574SAlex Crichton         .map_err(|err| warn!("Failed to compress cached code: {err}"))
28008f9eb17SAlex Crichton         .ok()?;
28108f9eb17SAlex Crichton 
28208f9eb17SAlex Crichton         // Optimize syscalls: first, try writing to disk. It should succeed in most cases.
28308f9eb17SAlex Crichton         // Otherwise, try creating the cache directory and retry writing to the file.
2841ced2ef4SXinzhao Xu         if fs_write_atomic(&mod_cache_path, "mod", &compressed_data).is_ok() {
28508f9eb17SAlex Crichton             return Some(());
28608f9eb17SAlex Crichton         }
28708f9eb17SAlex Crichton 
28808f9eb17SAlex Crichton         debug!(
28908f9eb17SAlex Crichton             "Attempting to create the cache directory, because \
29008f9eb17SAlex Crichton              failed to write cached code to disk, path: {}",
29108f9eb17SAlex Crichton             mod_cache_path.display(),
29208f9eb17SAlex Crichton         );
29308f9eb17SAlex Crichton 
29408f9eb17SAlex Crichton         let cache_dir = mod_cache_path.parent().unwrap();
29508f9eb17SAlex Crichton         fs::create_dir_all(cache_dir)
29608f9eb17SAlex Crichton             .map_err(|err| {
29708f9eb17SAlex Crichton                 warn!(
29808f9eb17SAlex Crichton                     "Failed to create cache directory, path: {}, message: {}",
29908f9eb17SAlex Crichton                     cache_dir.display(),
30008f9eb17SAlex Crichton                     err
30108f9eb17SAlex Crichton                 )
30208f9eb17SAlex Crichton             })
30308f9eb17SAlex Crichton             .ok()?;
30408f9eb17SAlex Crichton 
3051ced2ef4SXinzhao Xu         match fs_write_atomic(&mod_cache_path, "mod", &compressed_data) {
3061ced2ef4SXinzhao Xu             Ok(_) => Some(()),
3071ced2ef4SXinzhao Xu             Err(err) => {
3081ced2ef4SXinzhao Xu                 warn!(
3091ced2ef4SXinzhao Xu                     "Failed to write file with rename, target path: {}, err: {}",
3101ced2ef4SXinzhao Xu                     mod_cache_path.display(),
3111ced2ef4SXinzhao Xu                     err
3121ced2ef4SXinzhao Xu                 );
31308f9eb17SAlex Crichton                 None
31408f9eb17SAlex Crichton             }
31508f9eb17SAlex Crichton         }
31608f9eb17SAlex Crichton     }
3171ced2ef4SXinzhao Xu }
31808f9eb17SAlex Crichton 
31908f9eb17SAlex Crichton impl Hasher for Sha256Hasher {
finish(&self) -> u6432008f9eb17SAlex Crichton     fn finish(&self) -> u64 {
32108f9eb17SAlex Crichton         panic!("Sha256Hasher doesn't support finish!");
32208f9eb17SAlex Crichton     }
32308f9eb17SAlex Crichton 
write(&mut self, bytes: &[u8])32408f9eb17SAlex Crichton     fn write(&mut self, bytes: &[u8]) {
325e4c3fc5cSAlex Crichton         self.0.update(bytes);
32608f9eb17SAlex Crichton     }
32708f9eb17SAlex Crichton }
32808f9eb17SAlex Crichton 
32908f9eb17SAlex Crichton // Assumption: path inside cache directory.
33008f9eb17SAlex Crichton // Then, we don't have to use sound OS-specific exclusive file access.
33108f9eb17SAlex Crichton // Note: there's no need to remove temporary file here - cleanup task will do it later.
fs_write_atomic(path: &Path, reason: &str, contents: &[u8]) -> io::Result<()>3321ced2ef4SXinzhao Xu fn fs_write_atomic(path: &Path, reason: &str, contents: &[u8]) -> io::Result<()> {
333a0442ea0SHamir Mahal     let lock_path = path.with_extension(format!("wip-atomic-write-{reason}"));
33408f9eb17SAlex Crichton     fs::OpenOptions::new()
33508f9eb17SAlex Crichton         .create_new(true) // atomic file creation (assumption: no one will open it without this flag)
33608f9eb17SAlex Crichton         .write(true)
33708f9eb17SAlex Crichton         .open(&lock_path)
33808f9eb17SAlex Crichton         .and_then(|mut file| file.write_all(contents))
33908f9eb17SAlex Crichton         // file should go out of scope and be closed at this point
34008f9eb17SAlex Crichton         .and_then(|()| fs::rename(&lock_path, &path)) // atomic file rename
34108f9eb17SAlex Crichton }
34208f9eb17SAlex Crichton 
34308f9eb17SAlex Crichton #[cfg(test)]
34408f9eb17SAlex Crichton mod tests;
345