Skip to content

Commit e6bf087

Browse files
committed
fix: unix socket locking broken on darwin
1 parent b2f92a0 commit e6bf087

3 files changed

Lines changed: 82 additions & 16 deletions

File tree

Cargo.lock

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

src/main.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
mod root;
22
mod util;
33

4-
use std::os::unix::net::{UnixListener, UnixStream};
54
use std::path::{Path, PathBuf};
65
use std::{ffi, fs, io, process};
76

87
use anyhow::{bail, format_err, Context, Result};
98
use chrono::Utc;
109
use clap::{Args, Parser, Subcommand};
1110
use rand::distributions::{Alphanumeric, DistString};
12-
use root::Root;
11+
use root::{mk_lock, Root};
1312
use tracing::{debug, error, warn};
1413
use tracing_subscriber::EnvFilter;
1514

@@ -154,17 +153,16 @@ fn run_exec(ExecOpts { opts, exec }: ExecOpts) -> Result<()> {
154153

155154
let sock_path = root.join(PathBuf::from(format!(
156155
"lock-{}",
157-
Alphanumeric.sample_string(&mut rand::thread_rng(), 10)
156+
Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
158157
)));
159158

160159
debug!(
161160
target: LOG_TARGET,
162161
sock_path = %sock_path.display(),
163162
"Binding liveness socket"
164163
);
165-
let _socket = UnixListener::bind(&sock_path)?;
166164

167-
assert!(UnixStream::connect(&sock_path).is_ok());
165+
let _lock = mk_lock(&sock_path)?;
168166

169167
let exec_dir = lock(None, opts, Some(sock_path.clone()))?;
170168

src/root.rs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
mod dto;
22

33
use std::collections::btree_map::Entry;
4-
use std::io::{self, Read as _};
5-
use std::os::unix::net::UnixStream;
4+
use std::io::{self};
5+
#[cfg(not(target_os = "macos"))]
6+
use std::os::unix::net::{UnixListener, UnixStream};
67
use std::path::{Path, PathBuf};
78
use std::time::Duration;
89
use std::{fs, thread};
@@ -175,18 +176,22 @@ impl<'a> LockedRoot<'a> {
175176
}
176177
Entry::Occupied(mut e) => {
177178
if let Some(prev_sock_path) = e.get().socket_path.as_ref() {
178-
if let Ok(mut s) = UnixStream::connect(prev_sock_path) {
179+
if let Ok(s) = try_lock(prev_sock_path) {
179180
info!(
180181
target: LOG_TARGET,
181182
key,
182183
lock_id,
183184
sock_path = %prev_sock_path.display(),
184-
"Previous lock holder still alive"
185+
"Previous lock holder still alive (potentially)"
185186
);
186187
had_to_wait |= true;
187188
self.r#yield_with(|| {
188-
// we are just waiting to get disconnected here
189-
let _ = s.read(&mut [0]);
189+
let _ = clear_lock(s, prev_sock_path).inspect_err(|err| {
190+
info!(
191+
%err,
192+
"Error during waiting for / clearing the old lock"
193+
)
194+
});
190195
})?;
191196
} else {
192197
debug!(
@@ -289,3 +294,66 @@ fn rm_prev_sock_path(prev_sock_path: &Path) {
289294
}
290295
}
291296
}
297+
298+
// On Darwin Unix Sockets are not automatically removed, and linger,
299+
// with processes that try to connect to them just hanging. This makes them
300+
// unsuitable for our needs. Just use a file that we lock exclusively.
301+
#[cfg(target_os = "macos")]
302+
pub fn mk_lock(path: &Path) -> Result<fs::File> {
303+
let lock_file = fs::File::create(path)?;
304+
lock_file.lock_exclusive()?;
305+
Ok(lock_file)
306+
}
307+
308+
// On Linux we can use Unix Sockets as they disappear automatically,
309+
// which is nice.
310+
#[cfg(not(target_os = "macos"))]
311+
pub fn mk_lock(path: &Path) -> Result<UnixListener> {
312+
use std::os::unix::net::UnixStream;
313+
314+
let socket = UnixListener::bind(path)?;
315+
316+
assert!(UnixStream::connect(path).is_ok());
317+
318+
Ok(socket)
319+
}
320+
321+
#[cfg(target_os = "macos")]
322+
pub fn try_lock(path: &Path) -> Result<fs::File> {
323+
let lock_file = fs::File::open(path)?;
324+
Ok(lock_file)
325+
}
326+
327+
#[cfg(not(target_os = "macos"))]
328+
pub fn try_lock(path: &Path) -> Result<UnixStream> {
329+
let socket = UnixStream::connect(path)?;
330+
331+
Ok(socket)
332+
}
333+
334+
#[cfg(target_os = "macos")]
335+
pub fn clear_lock(mut file: fs::File, path: &Path) -> Result<()> {
336+
file.lock_exclusive()?;
337+
338+
// We want to unlock the file, even if we failed to remove it
339+
let rm_res = fs::remove_file(path);
340+
341+
file.unlock()?;
342+
343+
// if removing failed, report it now
344+
rm_res?;
345+
346+
Ok(())
347+
}
348+
349+
#[cfg(not(target_os = "macos"))]
350+
pub fn clear_lock(mut s: UnixStream, _path: &Path) -> Result<()> {
351+
// we are just waiting to get disconnected here
352+
353+
use std::io::Read as _;
354+
355+
// ignore, we *will* disconnect, nothing interesting about it
356+
let _ = s.read(&mut [0]);
357+
358+
Ok(())
359+
}

0 commit comments

Comments
 (0)