next_swc_napi/
lockfile.rs1use std::{
2 fs::{File, OpenOptions},
3 mem::ManuallyDrop,
4 sync::Mutex,
5};
6
7use anyhow::Context;
8use napi::bindgen_prelude::External;
9
10type JsLockfile = Mutex<ManuallyDrop<Option<LockfileInner>>>;
18
19pub struct LockfileInner {
20 file: File,
21 #[cfg(not(windows))]
22 path: std::path::PathBuf,
23}
24
25#[napi(ts_return_type = "{ __napiType: \"Lockfile\" } | null")]
26pub fn lockfile_try_acquire_sync(path: String) -> napi::Result<Option<External<JsLockfile>>> {
27 let mut open_options = OpenOptions::new();
28 open_options.write(true).create(true);
29
30 #[cfg(windows)]
39 return {
40 use std::os::windows::fs::OpenOptionsExt;
41
42 use windows_sys::Win32::{Foundation, Storage::FileSystem};
43
44 open_options
45 .share_mode(FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_DELETE)
46 .custom_flags(FileSystem::FILE_FLAG_DELETE_ON_CLOSE);
47 match open_options.open(&path) {
48 Ok(file) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some(
49 LockfileInner { file },
50 )))))),
51 Err(err)
52 if err.raw_os_error()
53 == Some(Foundation::ERROR_SHARING_VIOLATION.try_into().unwrap()) =>
54 {
55 Ok(None)
56 }
57 Err(err) => Err(err.into()),
58 }
59 };
60
61 #[cfg(not(windows))]
62 return {
63 use std::fs::TryLockError;
64
65 let file = open_options.open(&path)?;
66 match file.try_lock() {
67 Ok(_) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some(
68 LockfileInner {
69 file,
70 path: path.into(),
71 },
72 )))))),
73 Err(TryLockError::WouldBlock) => Ok(None),
74 Err(TryLockError::Error(err)) => Err(err.into()),
75 }
76 };
77}
78
79#[napi(ts_return_type = "Promise<{ __napiType: \"Lockfile\" } | null>")]
80pub async fn lockfile_try_acquire(path: String) -> napi::Result<Option<External<JsLockfile>>> {
81 tokio::task::spawn_blocking(move || lockfile_try_acquire_sync(path))
82 .await
83 .context("panicked while attempting to acquire lockfile")?
84}
85
86#[napi]
87pub fn lockfile_unlock_sync(
88 #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External<JsLockfile>,
89) {
90 let Some(inner): Option<LockfileInner> = lockfile
93 .lock()
94 .expect("poisoned: another thread panicked during `lockfile_unlock_sync`?")
95 .take()
96 else {
97 return;
98 };
99
100 #[cfg(not(windows))]
106 let _ = std::fs::remove_file(inner.path);
107
108 drop(inner.file);
109}
110
111#[napi]
112pub async fn lockfile_unlock(
113 #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External<JsLockfile>,
114) -> napi::Result<()> {
115 Ok(
116 tokio::task::spawn_blocking(move || lockfile_unlock_sync(lockfile))
117 .await
118 .context("panicked while attempting to unlock lockfile")?,
119 )
120}