Skip to content

Commit 0cec413

Browse files
committed
Reduce scope of file mutex
1 parent 9265ab9 commit 0cec413

7 files changed

Lines changed: 98 additions & 50 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ casual = "0.2.0"
3333
clap_complete = "4.4.10"
3434
constcat = "0.6.0"
3535
curl = "0.4.46"
36-
fmutex = "0.1.0"
36+
fmutex = "0.3.0"
3737
git2 = "0.20.0"
3838
globwalk = "0.9.1"
3939
home = "0.5.9"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- Generated by cargo-onedoc. DO NOT EDIT. -->
1+
<!-- Generated by cargo-onedoc v0.2.2. DO NOT EDIT. -->
22

33
# sheldon
44

RELEASES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 📝 Release notes
22

3+
## 0.8.2
4+
5+
*Unreleased*
6+
7+
- [Reduce scope of file mutex][todo]. This fixes #196.
8+
39
## 0.8.1
410

511
*February 8th, 2025*

docs/README_TEMPLATE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
<!-- Generated by cargo-onedoc. DO NOT EDIT. -->
2-
31
# sheldon
42

53
*Fast, configurable, shell plugin manager*

src/cli/raw.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,6 @@ impl clap::ValueEnum for Shell {
226226
fn key_value_parser(s: &str) -> Result<(String, String), String> {
227227
match s.split_once('=') {
228228
Some((k, v)) => Ok((k.to_string(), v.to_string())),
229-
_ => Err(format!(
230-
"{} isn't a valid key-value pair separated with =",
231-
s
232-
)),
229+
_ => Err(format!("{s} isn't a valid key-value pair separated with =")),
233230
}
234231
}

src/main.rs

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,6 @@ fn main() {
4040

4141
/// The main entry point to execute the application.
4242
pub fn run_command(ctx: &Context, command: Command) -> Result<()> {
43-
// We always try to acquire the mutex but it is only strictly necessary for
44-
// the lock and source commands.
45-
let _guard = match acquire_mutex(ctx, ctx.config_dir()) {
46-
Ok(g) => Some(g),
47-
Err(_) if !matches!(command, Command::Lock | Command::Source) => None,
48-
Err(err) => {
49-
return Err(err).context("failed to acquire lock on config directory");
50-
}
51-
};
5243
let mut warnings = Vec::new();
5344
let result = match command {
5445
Command::Init { shell } => init(ctx, shell),
@@ -64,27 +55,11 @@ pub fn run_command(ctx: &Context, command: Command) -> Result<()> {
6455
result
6556
}
6657

67-
fn acquire_mutex(ctx: &Context, path: &Path) -> Result<fmutex::Guard> {
68-
match fmutex::try_lock(path).with_context(|| format!("failed to open `{}`", path.display()))? {
69-
Some(g) => Ok(g),
70-
None => {
71-
ctx.log_warning(
72-
"Blocking",
73-
&format!(
74-
"waiting for file lock on {}",
75-
ctx.replace_home(path).display()
76-
),
77-
);
78-
fmutex::lock(path)
79-
.with_context(|| format!("failed to acquire file lock `{}`", path.display()))
80-
}
81-
}
82-
}
83-
8458
/// Executes the `init` subcommand.
8559
///
8660
/// Initialize a new config file.
8761
fn init(ctx: &Context, shell: Option<Shell>) -> Result<()> {
62+
let _guard = access(ctx, Access::W);
8863
let path = ctx.config_file();
8964
match path
9065
.metadata()
@@ -105,6 +80,7 @@ fn init(ctx: &Context, shell: Option<Shell>) -> Result<()> {
10580
///
10681
/// Add a new plugin to the config file.
10782
fn add(ctx: &Context, name: String, plugin: &EditPlugin) -> Result<()> {
83+
let _guard = access(ctx, Access::W);
10884
let path = ctx.config_file();
10985
let mut config = match EditConfig::from_path(path) {
11086
Ok(config) => {
@@ -124,6 +100,7 @@ fn add(ctx: &Context, name: String, plugin: &EditPlugin) -> Result<()> {
124100
///
125101
/// Open up the config file in the default editor.
126102
fn edit(ctx: &Context) -> Result<()> {
103+
let _guard = access(ctx, Access::W);
127104
let path = ctx.config_file();
128105
let original_contents = match fs::read_to_string(path)
129106
.with_context(|| format!("failed to read from `{}`", path.display()))
@@ -152,6 +129,7 @@ fn edit(ctx: &Context) -> Result<()> {
152129
///
153130
/// Remove a plugin from the config file.
154131
fn remove(ctx: &Context, name: String) -> Result<()> {
132+
let _guard = access(ctx, Access::W);
155133
let path = ctx.config_file();
156134
let mut config = EditConfig::from_path(path)?;
157135
ctx.log_header("Loaded", path);
@@ -191,6 +169,8 @@ fn init_config(ctx: &Context, shell: Option<Shell>, path: &Path, err: Error) ->
191169
///
192170
/// Install the plugins sources and generate the lock file.
193171
fn lock(ctx: &Context, warnings: &mut Vec<Error>) -> Result<()> {
172+
let _guard = access(ctx, Access::W);
173+
194174
let mut locked = locked(ctx, warnings)?;
195175

196176
if let Some(last) = locked.errors.pop() {
@@ -216,36 +196,49 @@ fn source(ctx: &Context, warnings: &mut Vec<Error>) -> Result<()> {
216196
let mut to_path = true;
217197

218198
let locked_config = if ctx.lock_mode.is_some() || newer_than(config_path, lock_path) {
199+
let _g = access(ctx, Access::W)?;
219200
locked(ctx, warnings)?
220201
} else {
221-
match lock::from_path(lock_path) {
202+
let cfg = {
203+
let _g = access(ctx, Access::R)?;
204+
lock::from_path(lock_path)
205+
};
206+
match cfg {
222207
Ok(locked_config) => {
223208
if locked_config.verify(ctx) {
224209
to_path = false;
225210
ctx.log_verbose_header("Unlocked", lock_path);
226211
locked_config
227212
} else {
213+
let _g = access(ctx, Access::W)?;
228214
locked(ctx, warnings)?
229215
}
230216
}
231-
Err(_) => locked(ctx, warnings)?,
217+
Err(_) => {
218+
let _g = access(ctx, Access::W)?;
219+
locked(ctx, warnings)?
220+
}
232221
}
233222
};
234223

235-
let script = locked_config
236-
.script(ctx)
237-
.context("failed to render source")?;
224+
let script = {
225+
let _g = access(ctx, Access::R)?;
226+
let script = locked_config
227+
.script(ctx)
228+
.context("failed to render source")?;
238229

239-
if to_path && locked_config.errors.is_empty() {
240-
locked_config
241-
.to_path(lock_path)
242-
.context("failed to write lock file")?;
243-
ctx.log_header("Locked", lock_path);
244-
} else {
245-
for err in &locked_config.errors {
246-
ctx.log_error(err);
230+
if to_path && locked_config.errors.is_empty() {
231+
locked_config
232+
.to_path(lock_path)
233+
.context("failed to write lock file")?;
234+
ctx.log_header("Locked", lock_path);
235+
} else {
236+
for err in &locked_config.errors {
237+
ctx.log_error(err);
238+
}
247239
}
248-
}
240+
script
241+
};
249242

250243
print!("{script}");
251244
Ok(())
@@ -269,3 +262,56 @@ fn locked(ctx: &Context, warnings: &mut Vec<Error>) -> Result<LockedConfig> {
269262
config::clean(ctx, warnings, &config)?;
270263
lock::config(ctx, config)
271264
}
265+
266+
#[derive(Debug, Clone, Copy)]
267+
enum Access {
268+
R,
269+
W,
270+
}
271+
272+
fn access(ctx: &Context, mode: Access) -> Result<fmutex::Guard<'static>> {
273+
match mode {
274+
Access::R => lock_read(ctx).context("failed to acquire exclusive lock on config directory"),
275+
Access::W => lock_write(ctx).context("failed to acquire shared lock on config directory"),
276+
}
277+
}
278+
279+
fn lock_write(ctx: &Context) -> Result<fmutex::Guard<'static>> {
280+
let path = ctx.config_dir();
281+
match fmutex::try_lock_exclusive_path(path)
282+
.with_context(|| format!("failed to open `{}`", path.display()))?
283+
{
284+
Some(g) => Ok(g),
285+
None => {
286+
ctx.log_warning(
287+
"Blocking",
288+
&format!(
289+
"waiting for file lock on {}",
290+
ctx.replace_home(path).display()
291+
),
292+
);
293+
fmutex::lock_exclusive_path(path)
294+
.with_context(|| format!("failed to acquire file lock `{}`", path.display()))
295+
}
296+
}
297+
}
298+
299+
fn lock_read(ctx: &Context) -> Result<fmutex::Guard<'static>> {
300+
let path = ctx.config_dir();
301+
match fmutex::try_lock_shared_path(path)
302+
.with_context(|| format!("failed to open `{}`", path.display()))?
303+
{
304+
Some(g) => Ok(g),
305+
None => {
306+
ctx.log_warning(
307+
"Blocking",
308+
&format!(
309+
"waiting for file lock on {}",
310+
ctx.replace_home(path).display()
311+
),
312+
);
313+
fmutex::lock_shared_path(path)
314+
.with_context(|| format!("failed to acquire file lock `{}`", path.display()))
315+
}
316+
}
317+
}

0 commit comments

Comments
 (0)