Stop Shell-Script Collisions with WaitLock: A Friendly Guide for Everyone

When two scripts try to back up the same database, download the same file, or occupy the same GPU at the same time, something usually breaks.
WaitLock is a tiny, portable command-line tool that gives your shell scripts traffic lights—mutexes and semaphores that work on any Unix-like system.
Below you will find every detail you need: how to install it, how to use it, and how to adapt it to real production work. Nothing has been added from outside sources; everything comes straight from the official project documentation.


Traffic lights at night

Why WaitLock Exists

Traditional locking tools (flock, lockfile) often leave stale locks behind when a process crashes.
WaitLock was written to fix three pain points:

  1. Automatic cleanup – locks vanish as soon as the process ends, no matter how it ends.
  2. Single-command usage – one line to acquire a lock and run a script.
  3. Cross-platform portability – pure C, tested on Linux, FreeBSD, OpenBSD, NetBSD, and macOS.

It supports two modes:

  • Mutex (default) – one owner at a time.
  • Semaphore – up to N owners in parallel.

Busy crossroads

Quick Installation in Three Moves

1. Install build tools

Ubuntu / Debian

sudo apt-get update
sudo apt-get install build-essential autoconf

CentOS / Rocky

sudo yum groupinstall "Development Tools"
sudo yum install autoconf

macOS

xcode-select --install          # command-line tools
brew install autoconf           # if you use Homebrew

2. Build from source

git clone https://github.com/user/waitlock.git
cd waitlock

autoreconf -fi                  # only needed when building from git
./configure
make

Optional build flavours

# Debug build
./configure CFLAGS="-g -O0 -DDEBUG"

# Release build
./configure CFLAGS="-O2 -DNDEBUG"

# Cross-compile for ARM
./configure --host=arm-linux-gnueabihf

3. Install system-wide or locally

sudo make install               # into /usr/local/bin
# or
./configure --prefix=/opt/waitlock
make install

Check success:

waitlock --version   # should print 1.0.0

Laptop with terminal open

One-Minute Primer

Exclusive daily backup

#!/bin/bash
# nightly_backup.sh

waitlock nightly_backup || { echo "Another backup is running"; exit 1; }

mysqldump --all-databases > all.sql
tar czf all.sql.tgz all.sql

Run it twice:

./nightly_backup.sh &
./nightly_backup.sh   # second instance exits immediately

Allow four parallel downloads

#!/bin/bash
# download.sh

waitlock --allowMultiple 4 download_slot || { echo "Pool full"; exit 1; }

wget "$1"

Feed it 100 URLs:

cat urls.txt | xargs -n1 -P8 ./download.sh

At most four downloads run at once; the rest queue automatically.


Highway merging lanes

Command Cheat-Sheet

Goal Example
Simple mutex waitlock mylock
Up to N concurrent waitlock -m N mylock
One lock per CPU waitlock -c mylock
Reserve 2 CPUs for OS waitlock -c -x 2 mylock
Wait max 30 s waitlock -t 30 mylock
Check lock availability waitlock --check mylock
Run command under lock waitlock mylock --exec "./run.sh"
List active locks waitlock --list
Show stale locks only waitlock --list --stale-only

Ten Real-World Recipes

1. Single-instance backup script

#!/bin/bash
waitlock db_backup --exec bash -c "
    mysqldump --all-databases > backup.sql
    gzip backup.sql
"

The lock disappears when the subshell exits.


Database server

2. GPU resource pool

Eight-GPU server, keep one GPU free for the OS:

waitlock -c -x 1 gpu_pool
export CUDA_VISIBLE_DEVICES=$WAITLOCK_SLOT
python train.py

$WAITLOCK_SLOT gives you an index between 0 and 6.


3. CPU-aware batch jobs

Run one job per core, minus two reserved cores:

waitlock -c -x 2 cpu_job --exec ./heavy_computation.sh

Server racks

4. Parallel file processing with controlled fan-out

find /data -name "*.csv" | \
  xargs -P10 -I{} waitlock -m 3 processor --exec "python process.py {}"

xargs starts ten workers; WaitLock keeps only three active at any moment.


5. NFS cross-node lock

Shared storage at /mnt/nfs:

export WAITLOCK_DIR="/mnt/nfs/locks"
waitlock cluster_job --timeout 300 --exec ./mpi_job.sh

Works across any POSIX machine mounting the same NFS share.


Network attached storage

6. CI/CD pipeline gate

GitLab example:

deploy:
  script:
    - waitlock prod_deploy --timeout 600 --exec ./deploy.sh

Only one pipeline deploys at a time; others wait or fail after ten minutes.


7. Lock inspection during incidents

List everything in human-readable form:

waitlock --list

CSV for scripts:

waitlock --list --format csv | tail -n +2 | wc -l   # count active locks

Control room monitors

8. Environment-driven defaults

Create /etc/profile.d/waitlock.sh:

export WAITLOCK_TIMEOUT=300
export WAITLOCK_DIR="/var/lock/myapp"
export WAITLOCK_DEBUG=1

Every script inherits these values; no need to repeat -t 300.


9. Error handling in shell scripts

waitlock --timeout 30 critical_section
case $? in
  0) echo "Lock acquired" ;;
  1) echo "Already busy"   >&2; exit 1 ;;
  2) echo "Timeout"        >&2; exit 1 ;;
  *) echo "Unknown error"  >&2; exit 1 ;;
esac

10. Syslog integration for audits

waitlock --syslog --syslog-facility local0 backup_job

Then watch:

tail -f /var/log/syslog | grep waitlock

Log terminal

Advanced Configuration

Environment variables reference

Variable Meaning Default
WAITLOCK_DIR Directory for lock files auto-detected
WAITLOCK_TIMEOUT Default timeout in seconds infinite
WAITLOCK_DEBUG Enable debug messages unset
WAITLOCK_SLOT Preferred semaphore slot auto

Example session:

WAITLOCK_TIMEOUT=60 \
WAITLOCK_DIR=$HOME/locks \
waitlock -m 4 gpu_pool

Exit codes

Code Meaning
0 Success
1 Lock is busy
2 Timeout
3 Usage error
4 System error
5 Permission denied
6 Lock directory not accessible
75 Temporary failure
126 Command not executable
127 Command not found

Troubleshooting Quick-Start

Problem Quick fix
Permission denied export WAITLOCK_DIR=$HOME/.waitlock
Stale locks visible waitlock --list --stale-only; WaitLock auto-cleans on next access
Too many lock files Use hierarchical names like app/module/task
Need debug output WAITLOCK_DEBUG=1 waitlock -v mylock

Mechanic fixing engine

Performance Tips

  1. Place lock directory on tmpfs

    sudo mount -t tmpfs -o size=10M tmpfs /var/lock/waitlock
    
  2. Avoid flat namespaces
    project/service/task reduces directory scan time.

  3. Tune timeout values
    Balance between fast failure and queue pressure.


Under the Hood (No Secrets, Just Facts)

  • Lock file format: binary header with magic 0x57414C4B, PID/UID metadata, CRC32 checksum.
  • Cleanup mechanism: fcntl advisory locks plus atexit handlers; if the kernel revokes the lock, the next opener detects the stale file and removes it.
  • Portability layer: POSIX fcntl, sysconf(_SC_NPROCESSORS_ONLN), and classic open(O_CREAT|O_EXCL)—no Linux-only syscalls.

Blueprint and tools

Contributing (If You Want To)

Development quick start

git clone https://github.com/user/waitlock.git
cd waitlock
sudo apt-get install autoconf automake libtool  # or the macOS equivalents
autoreconf -fi
./configure --enable-debug CFLAGS="-g -O0"
make && make check

Test suites

./src/waitlock --test          # built-in unit tests
./test_basic.sh
./test_semaphore.sh
./test_timeout.sh

Code style

  • Stick to C89/C90
  • 4-space indentation
  • Every new feature needs a test

Team collaboration

License and Support

WaitLock is released under the MIT License.
For questions or bug reports:

  • Documentation: man waitlock after installation
  • Issues: https://github.com/user/waitlock/issues
  • Discussions: https://github.com/user/waitlock/discussions

Closing Thoughts

Adding WaitLock to your toolkit is like installing traffic lights where previously there were none.
Backups no longer collide, GPUs are shared fairly, and your CI pipelines stop stepping on each other.
Install it once, use it everywhere, and forget about lock-file debris forever.

Happy scripting—and may your processes always run in the right lane.