{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/misc/run-single-instance/",
  "description": "Prevent multiple script instances with file locking. Use flock in Bash, fcntl in Python, and syscall.Flock in Go for single-instance processes.",
  "path": "/misc/run-single-instance/",
  "publishedAt": "2024-12-31T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Shell",
    "Unix",
    "Python",
    "Go"
  ],
  "textContent": "I've been having a ton of fun fiddling with [Tailscale] over the past few days. While\nsetting it up on a server, I came across this [ufw firewall script] that configures the\nfirewall on Linux to ensure direct communication across different nodes in my tailnet. It\nhas the following block of code that I found interesting (added comments for clarity):\n\nHere, flock is a Linux command that ensures only one instance of the script runs at a time\nby locking a specified file (e.g., PIDFILE) through a file descriptor (e.g., 200). If\nanother process already holds the lock, the script either waits or exits immediately. Above,\nit bails with an error message and exit code 1.\n\nIf you try running two instances of this script, the second one will exit with this message:\n\nOn most Linux distros, flock comes along with the coreutils. If not, it's easy to install\nwith your preferred package manager.\n\nA more portable version\n\nOn macOS, the file locking mechanism is different, and flock doesn't work there. To make\nyour script portable, you can use mkdir in the following manner to achieve a similar\nresult:\n\nThis works because mkdir is atomic. It creates the lock directory (LOCKDIR) in /tmp or\nfails if the directory already exists. This acts as a marker for the running instance. If\nsuccessful, the script sets up a trap to remove the directory on exit and continues to the\nmain logic. If mkdir fails, it means another instance of the process is running, and the\nscript exits with a message.\n\nThis is almost as effective as the flock version. Since I rarely write scripts for\nnon-Linux environments, either option is fine!\n\nWith Python\n\nOftentimes, I opt for Python when I need to write larger scripts. The same can be achieved\nin Python like this:\n\nThe script uses fcntl.flock to prevent multiple instances from running. It creates a lock\nfile (LOCKFILE) in the /tmp directory, named after the scripts filename. When the script\nstarts, it opens the file in write mode and tries to lock it with fcntl.flock using an\nexclusive lock (LOCK_EX). The LOCK_NB flag makes the operation non-blocking. If another\nprocess holds the lock, the script exits with a message.\n\n> This approach works on both Linux and macOS, as both support fcntl for file-based locks.\n> The lock is automatically released when the file is closed, either at the end of the\n> script or the with block.\n\nWith Go\n\nI was curious about doing it in Go. It's quite similar to Python:\n\nLike the Python example, this uses syscall.Flock to prevent multiple script instances. It\ncreates a lock file based on the script's name using filepath.Base(os.Args[0]) and stores\nit in /tmp. The script tries to acquire an exclusive, non-blocking lock\n(LOCK_EX | LOCK_NB). If unavailable, it exits with a message. The lock is automatically\nreleased when the file is closed in the defer block.\n\nUnderneath, Go makes sure that syscall.Flock works on both macOS and Linux.\n\n\n\n\n[tailscale]:\n    https://tailscale.com/\n\n[ufw firewall script]:\n    https://github.com/AT3K/Tailscale-Firewall-Setup/blob/main/update_tailscale_ufw_rules.sh",
  "title": "Running only a single instance of a process"
}