Skip to content

Instantly share code, notes, and snippets.

@ryran
Created April 25, 2017 22:37
Show Gist options
  • Save ryran/f074407ac8ea5565388603177a0dbbd8 to your computer and use it in GitHub Desktop.
Save ryran/f074407ac8ea5565388603177a0dbbd8 to your computer and use it in GitHub Desktop.
Simple mandatory file locking
#!/bin/bash
flock_open() {
# Open a blocking request for an exclusive lock against file $1, by
# loop-trying a mkdir on: "$(dirname $1)/.$(basename $1).lock"
# Lock can be stolen from a dead PID 3.2-19.2 seconds after they die, though
# this can be changed by calling flock_open w/custom deadmax= env variable.
local parentdir=$(dirname "${1}")
local lock=${parentdir}/.${1##*/}.lock~
local owner= lastOwner=
local -i deadloops=0 deadmax=${deadmax:-$(shuf -i 8-24 -n1)}
# Create parent dir if necessary
[[ ${parentdir} != . ]] && mkdir -p "${parentdir}"
until mkdir "${lock}" 2>/dev/null; do
# Sleep for 0.4sec - 0.8sec
usleep $(shuf -i 400000-800000 -n 1)
lastOwner=${owner}
owner=$(cat "${lock}/owner" 2>/dev/null)
if cat /proc/${owner}/fd/77 &>/dev/null; then
# Reset deadloops counter if owner is active
deadloops=0
else
# Log a deadloop if lock owner is dead and pid hasn't changed
[[ ${owner} == ${lastOwner} ]] && deadloops+=1 || deadloops=0
fi
# After $deadmax number of consecutive dead loops, steal lock
((deadloops>=deadmax)) && break
done
exec 77>"${lock}/owner"
echo ${$} >&77
trap "flock_close '${1}'" HUP INT TERM EXIT
}
flock_close() {
local lock=$(dirname "${1}")/.${1##*/}.lock~
exec 77>&-
rm -rf "${lock}"
trap - HUP INT TERM EXIT
}
@ryran
Copy link
Author

ryran commented Apr 25, 2017

I had a use-case for mandatory file locking and flock just didn't cut it. I have a small number (less than 20) of parallel processes that aren't started at exactly the same time and all read & append (at some point) from/to the same file. I wanted to make sure none of them clobber each other (the programs in use do NOT pay attention to advisory locks in Linux) and I wanted to be able to steal locks from a dead process (if one dies without cleaning up).

To use the above, anything that wants to modify /SOME/FILE will need to wrap its operations inside calls to flock_open and flock_close, e.g.:

flock_open /SOME/FILE

INSERT ARBITRARY ACTIONS HERE
MOST-LIKELY INCLUDING THINGS THAT READ FROM AND/OR WRITE TO FILE

flock_close /SOME/FILE

Caveats:

  • Not for use with NFS. (mkdir is not guaranteed atomic on NFS.)
  • If there's a chance one of your processes could get hung forever, well, this is a blocking request, so ... that would be bad.

For simpler use-cases, take a look at man 1 flock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment