1 Commits
Author SHA1 Message Date
swaits 0b9fe272d3 fix(mux/rust): prevent multiple instances via fd-lock (v0.1.1) 2026-04-06 15:20:26 -06:00
5 changed files with 77 additions and 5 deletions

No files matched your search

+9
View File
@@ -1,5 +1,14 @@
# Changelog
## 0.1.1 — 2026-04-06
### Fixed
- Prevent multiple mux instances from running simultaneously via file-based
exclusive lock (`fd-lock`); stale sockets from crashed instances are cleaned
up automatically
- Clearer error message when TCP port is already in use
## 0.1.0 — 2026-04-06
Initial release.
Generated
+41 -1
View File
@@ -171,11 +171,12 @@ dependencies = [
[[package]]
name = "donglora-mux"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"anyhow",
"clap",
"donglora-client",
"fd-lock",
"tokio",
"tokio-serial",
"tokio-util",
@@ -193,6 +194,17 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "fd-lock"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "futures"
version = "0.3.32"
@@ -335,6 +347,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -522,6 +540,19 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags 2.11.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -826,6 +857,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "donglora-mux"
version = "0.1.0"
version = "0.1.1"
edition = "2024"
authors = ["Stephen Waits <steve@waits.net>"]
license = "MIT"
@@ -18,6 +18,7 @@ tokio-serial = "5"
tokio-util = "0.7"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
fd-lock = "4"
[lints.clippy]
unwrap_used = "deny"
+1
View File
@@ -11,6 +11,7 @@ dongle simultaneously.
- RxPacket frames broadcast to all connected clients
- StartRx/StopRx reference-counted across clients
- SetConfig locked once set (single client can change freely)
- Single-instance enforcement via file lock (stale sockets auto-cleaned)
- Automatic dongle reconnect on hot-plug
- No panics — enforced by clippy deny lints
+24 -3
View File
@@ -91,7 +91,24 @@ impl MuxDaemon {
if let Some(parent) = sock_path.parent() {
tokio::fs::create_dir_all(parent).await.ok();
}
// Acquire an exclusive lock to prevent multiple mux instances.
// The lock is held for the process lifetime and auto-released on exit (even SIGKILL).
let lock_path = format!("{}.lock", self.socket_path);
let lock_file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&lock_path)
.map_err(|e| anyhow::anyhow!("failed to open lock file {lock_path}: {e}"))?;
let mut lock = fd_lock::RwLock::new(lock_file);
let _lock_guard = match lock.try_write() {
Ok(guard) => guard,
Err(_) => anyhow::bail!("another donglora-mux is already running (lock held on {lock_path})"),
};
if sock_path.exists() {
// Lock acquired but socket file exists — stale from a crashed instance.
info!("removing stale socket {}", self.socket_path);
tokio::fs::remove_file(sock_path).await.ok();
}
@@ -103,9 +120,13 @@ impl MuxDaemon {
// Start TCP server (optional)
let tcp_listener = if let Some((ref host, port)) = self.tcp_addr {
let addr = format!("{host}:{port}");
let listener = TcpListener::bind(&addr)
.await
.map_err(|e| anyhow::anyhow!("failed to bind TCP {addr}: {e}"))?;
let listener = TcpListener::bind(&addr).await.map_err(|e| {
if e.kind() == std::io::ErrorKind::AddrInUse {
anyhow::anyhow!("another donglora-mux is already listening on TCP {addr}")
} else {
anyhow::anyhow!("failed to bind TCP {addr}: {e}")
}
})?;
info!("TCP listening on {addr}");
Some(listener)
} else {