When scheduling scripts via cron that shouldn't run concurrently, flock
provides an elegant solution. The lock file management is automatic - you don't need to manually delete lock files as the kernel releases them when:
- The holding process terminates
- All file descriptors to the lock are closed
The magic happens at the kernel level through advisory file locking. The empty lock file serves as a synchronization point. Here's what happens under the hood:
1. First process opens/creates the lock file
2. Acquires an exclusive lock (fcntl-style)
3. Subsequent processes block or fail (depending on -w/-n options)
4. Kernel automatically releases lock when process exits
For your cron use case, here are three robust implementation patterns:
# Basic blocking (wait indefinitely)
*/20 * * * * root flock /var/cron.lock -c "/usr/bin/myscript"
# Non-blocking (fail if locked)
*/20 * * * * root flock -n /var/cron.lock -c "/usr/bin/myscript || echo 'Script locked'"
# Timeout version (wait 60 sec max)
*/20 * * * * root flock -w 60 /var/cron.lock -c "/usr/bin/myscript"
For your specific startup race condition, consider this improved approach:
# In upstart config
exec flock -n /var/cron.lock /usr/bin/myscript
# In crontab
*/20 * * * * root flock -w 0 /var/cron.lock /usr/bin/myscript
To verify locking is working:
# Check lock status
lslocks | grep cron.lock
# Alternative method
fuser -v /var/cron.lock
# Test without cron
flock -x /var/cron.lock -c "sleep 30" &
flock -n /var/cron.lock echo $? # Should return 1
- Lock files should be on persistent storage (not /tmp)
- Set appropriate permissions (usually 644)
- Consider symbolic links for lock file management
- For network scenarios, use NFSv4+ (supports proper file locking)
When using flock
with cron jobs, the lock file (/var/cron.lock
in your example) doesn't require manual cleanup. The kernel automatically releases the lock when either:
- The process holding the lock terminates (even if crashes)
- The file descriptor is closed
The magic happens at the kernel level through advisory locking. Here's what occurs during execution:
# When your cron runs:
flock -w 0 /var/cron.lock /usr/bin/myscript
# Internally:
1. Opens /var/cron.lock (creates if nonexistent)
2. Attempts to acquire an exclusive lock (blocking with -w 0)
3. If successful, executes myscript while maintaining lock
4. Releases lock automatically upon script completion
To check which process holds the lock (Linux-specific):
sudo lslocks | grep '/var/cron.lock'
# Or for real-time monitoring:
sudo watch -n 1 'lsof /var/cron.lock'
For your upstart+cron scenario, consider this enhanced approach:
# In upstart config:
exec flock -n /var/cron.lock -c "/usr/bin/myscript --startup"
# In crontab:
*/20 * * * * root flock -n /var/cron.lock -c "/usr/bin/myscript --cron"
Commonly used parameters:
-n, --nonblock # Fail immediately if locked
-w, --wait # Seconds to wait (0 means fail immediately)
-E, --conflict-exit-code # Custom exit code when cannot acquire lock
-o, --close # Release lock after fork/exec
Here's a complete implementation:
#!/bin/bash
LOCK_FILE="/var/lock/dbbackup.lock"
(
flock -n 9 || exit 1
# Critical section
pg_dump -U postgres mydb > /backups/db_$(date +%F).sql
gzip /backups/db_*.sql
) 9>${LOCK_FILE}
# Lock released automatically here
- Permission issues: Ensure cron user has write access to lock file directory
- NFS warning: Avoid using flock on network filesystems
- Stale locks: Kernel cleans them up, but you can add
flock -n || rm -f /var/cron.lock
as fallback
Instead of wrapping in cron, embed locking directly:
#!/bin/bash
LOCKFILE=/tmp/myscript.lock
exec 200>"$LOCKFILE"
flock -n 200 || {
echo "Script already running"
exit 1
}
# Main script logic here
sleep 10
# Lock released when script exits