Skip to content

Library Usage

Integrate File-Based Semaphore into your Rust applications for robust process coordination.

Installation

Add to your Cargo.toml:

toml
[dependencies]
file-based-semaphore = { git = "https://github.com/tuulbelt/file-based-semaphore.git" }

Basic Usage

Non-Blocking Acquisition

rust
use file_based_semaphore::{Semaphore, SemaphoreConfig};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let sem = Semaphore::with_defaults("/tmp/my.lock")?;

    {
        let _guard = sem.try_acquire()?;
        // Critical section - lock held
        println!("Doing exclusive work...");
    } // Guard dropped, lock automatically released

    Ok(())
}

Blocking Acquisition with Timeout

rust
use file_based_semaphore::{Semaphore, SemaphoreConfig};
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = SemaphoreConfig {
        acquire_timeout: Some(Duration::from_secs(30)),
        retry_interval: Duration::from_millis(100),
        ..Default::default()
    };

    let sem = Semaphore::new("/tmp/blocking.lock", config)?;

    {
        let _guard = sem.acquire()?;
        // Got the lock after waiting
        do_work();
    }

    Ok(())
}

Configuration Options

rust
use file_based_semaphore::SemaphoreConfig;
use std::time::Duration;

let config = SemaphoreConfig {
    // Detect locks older than 1 hour as stale
    stale_timeout: Some(Duration::from_secs(3600)),

    // Wait up to 30 seconds when acquiring
    acquire_timeout: Some(Duration::from_secs(30)),

    // Poll every 100ms while waiting
    retry_interval: Duration::from_millis(100),
};

Custom Lock Info

rust
use file_based_semaphore::{Semaphore, SemaphoreConfig, LockInfo};

let sem = Semaphore::with_defaults("/tmp/tagged.lock")?;

// Add a tag to identify the lock holder
let info = LockInfo::with_tag("backup-job-2024");

{
    let _guard = sem.try_acquire_with_info(info)?;
    run_backup();
}

Checking Lock Status

rust
use file_based_semaphore::Semaphore;

let sem = Semaphore::with_defaults("/tmp/check.lock")?;

// Check if locked
if sem.is_locked() {
    println!("Lock is held");

    // Get holder information
    if let Some(info) = sem.lock_info() {
        println!("Held by PID: {}", info.pid);
        println!("Since: {}", info.timestamp);
        if let Some(tag) = info.tag {
            println!("Tag: {}", tag);
        }
    }
}

Stale Lock Recovery

rust
use file_based_semaphore::{Semaphore, SemaphoreConfig};
use std::time::Duration;

let config = SemaphoreConfig {
    // Consider locks older than 5 minutes as stale
    stale_timeout: Some(Duration::from_secs(300)),
    ..Default::default()
};

let sem = Semaphore::new("/tmp/recoverable.lock", config)?;

// try_acquire will automatically clean up stale locks
let _guard = sem.try_acquire()?;

Force Release (Use with Caution)

rust
use file_based_semaphore::Semaphore;

let sem = Semaphore::with_defaults("/tmp/force.lock")?;

// Force release even if held by another process
// WARNING: This can break coordination!
sem.force_release()?;

Error Handling

SemaphoreError Types

rust
use file_based_semaphore::{Semaphore, SemaphoreError};

let sem = Semaphore::with_defaults("/tmp/error.lock")?;

match sem.try_acquire() {
    Ok(guard) => {
        // Lock acquired
        do_work();
    }
    Err(SemaphoreError::AlreadyLocked { holder_pid, locked_since }) => {
        if let Some(pid) = holder_pid {
            println!("Lock held by PID: {}", pid);
        }
    }
    Err(SemaphoreError::InvalidPath(msg)) => {
        eprintln!("Invalid path: {}", msg);
    }
    Err(SemaphoreError::IoError(e)) => {
        eprintln!("IO error: {}", e);
    }
    Err(SemaphoreError::Timeout) => {
        eprintln!("Timed out waiting for lock");
    }
    Err(e) => {
        eprintln!("Error: {}", e);
    }
}

Thread Safety

The Semaphore struct is Send + Sync and can be shared across threads:

rust
use file_based_semaphore::Semaphore;
use std::sync::Arc;
use std::thread;

let sem = Arc::new(Semaphore::with_defaults("/tmp/shared.lock")?);

let handles: Vec<_> = (0..4).map(|i| {
    let sem = Arc::clone(&sem);
    thread::spawn(move || {
        if let Ok(guard) = sem.try_acquire() {
            println!("Thread {} got the lock", i);
            thread::sleep(Duration::from_millis(100));
        }
    })
}).collect();

for h in handles {
    h.join().unwrap();
}

Best Practices

1. Always Use Guards

rust
// Good: RAII guard ensures release
{
    let _guard = sem.try_acquire()?;
    do_work();
} // Automatically released

// Avoid: Manual release can be forgotten
sem.try_acquire()?;
do_work();
// Oops, forgot to release!

2. Use Appropriate Timeouts

rust
// For short operations
SemaphoreConfig {
    acquire_timeout: Some(Duration::from_secs(5)),
    stale_timeout: Some(Duration::from_secs(60)),
    ..Default::default()
}

// For long-running jobs
SemaphoreConfig {
    acquire_timeout: Some(Duration::from_secs(300)),
    stale_timeout: Some(Duration::from_secs(3600)),
    ..Default::default()
}

3. Handle Errors Gracefully

rust
match sem.acquire() {
    Ok(guard) => {
        // Proceed with work
    }
    Err(SemaphoreError::Timeout) => {
        // Gracefully handle timeout
        log::warn!("Could not acquire lock, skipping operation");
    }
    Err(e) => {
        return Err(e.into());
    }
}

Released under the MIT License.