Skip to content

Commit

Permalink
feat: provide a mechanism to check if an iroh program is already runn…
Browse files Browse the repository at this point in the history
…ing. (#293)
  • Loading branch information
fabricedesre authored Oct 10, 2022
1 parent dd1dc70 commit bddbae9
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
9 changes: 9 additions & 0 deletions iroh-one/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use iroh_one::{
};
use iroh_rpc_client::Client as RpcClient;
use iroh_rpc_types::Addr;
use iroh_util::lock::ProgramLock;
use iroh_util::{iroh_config_path, make_config};
#[cfg(feature = "uds-gateway")]
use tempdir::TempDir;
Expand All @@ -20,6 +21,14 @@ use tracing::{debug, error};

#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
let mut lock = ProgramLock::new("iroh-one")?;
if lock.is_locked() {
println!("iroh-one is already running, stopping.");
return Ok(());
} else {
lock.acquire()?;
}

let args = Args::parse();

let cfg_path = iroh_config_path(CONFIG_FILE_NAME)?;
Expand Down
2 changes: 2 additions & 0 deletions iroh-util/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lock_test.result
*.lock
6 changes: 5 additions & 1 deletion iroh-util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ description = "Utilities for iroh"
[dependencies]
cid = "0.8.4"
ctrlc = "3.2.2"
file-guard = "0.1"
futures = "0.3.21"
anyhow = "1.0.57"
toml = "0.5.9"
Expand All @@ -18,4 +19,7 @@ config = "0.13.1"
tracing = "0.1.34"
temp-env = "0.3.1"
rlimit = "0.8.3"
dirs-next = "2.0.0"
dirs-next = "2.0.0"

[target.'cfg(unix)'.dev-dependencies]
nix = "0.25"
2 changes: 2 additions & 0 deletions iroh-util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use cid::{
use config::{Config, ConfigError, Environment, File, Map, Source, Value, ValueKind};
use tracing::debug;

pub mod lock;

/// name of directory that wraps all iroh files in a given application directory
const IROH_DIR: &str = "iroh";
const DEFAULT_NOFILE_LIMIT: u64 = 65536;
Expand Down
110 changes: 110 additions & 0 deletions iroh-util/src/lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use anyhow::Result;
use file_guard::{FileGuard, Lock};
use std::fs::File;
use std::path::PathBuf;
use std::rc::Rc;

/// Manages a lock file used to track if an iroh program
/// is already running.
/// The lock is released either when the object is dropped
/// or when the program stops.
pub struct ProgramLock {
path: PathBuf,
lock: Option<FileGuard<Rc<File>>>,
}

impl ProgramLock {
/// Create a new lock for the given program. This does not yet acquire the lock.
pub fn new(prog_name: &str) -> Result<Self> {
let path = crate::iroh_data_path(&format!("{}.lock", prog_name))?;
Ok(Self { path, lock: None })
}

/// Check if the current program is locked or not.
pub fn is_locked(&self) -> bool {
if !self.path.exists() {
return false;
}

// Even if we manage to lock the file this won't last since the drop implementation
// of FileGuard releases the underlying lock.
if let Ok(file) = File::create(&self.path) {
file_guard::try_lock(&file, Lock::Exclusive, 0, 1).is_err()
} else {
false
}
}

/// Try to acquire a lock for this program.
pub fn acquire(&mut self) -> Result<()> {
let file = Rc::new(File::create(&self.path)?);

file_guard::lock(file, Lock::Exclusive, 0, 1)
.map(|lock| self.lock = Some(lock))
.map_err(|err| err.into())
}
}

#[cfg(all(test, unix))]
mod test {
use super::*;

fn create_test_lock(name: &str) -> ProgramLock {
ProgramLock {
path: PathBuf::new().join(name),
lock: None,
}
}

#[test]
fn test_locks() {
use nix::unistd::{fork, ForkResult::*};
use std::io::{Read, Write};
use std::time::Duration;

// Start we no lock file.
let _ = std::fs::remove_file("test1.lock");

let mut lock = create_test_lock("test1.lock");
assert!(!lock.is_locked());

lock.acquire().unwrap();

// Spawn a child process to check we can't get the same lock.
// assert!() failures in the child are not reported by the test
// harness, so we write the result in a file from the child and
// read them back in the parent after a reasonnable delay :(
unsafe {
match fork() {
Ok(Parent { child: _ }) => {
let _ = std::fs::remove_file("lock_test.result");

std::thread::sleep(Duration::from_secs(1));

let mut result = std::fs::File::open("lock_test.result").unwrap();
let mut buf = String::new();
let _ = result.read_to_string(&mut buf);
assert_eq!(buf, "locked1=true, locked2=false");

let _ = std::fs::remove_file("lock_test.result");
}
Ok(Child) => {
let lock = create_test_lock("test1.lock");
let lock2 = create_test_lock("test2.lock");
{
let mut result = std::fs::File::create("lock_test.result").unwrap();
let _ = result.write_all(
format!(
"locked1={}, locked2={}",
lock.is_locked(),
lock2.is_locked()
)
.as_bytes(),
);
}
}
Err(err) => panic!("Failed to fork: {}", err),
}
}
}
}

0 comments on commit bddbae9

Please sign in to comment.