Module Lock_file_blocking.Nfs
An implementation-neutral NFS lock file scheme that relies on the atomicity of link over NFS. Rather than relying on a working traditional advisory lock system over NFS, we create a hard link between the file given to the create call and a new file <filename>.nfs_lock. This link call is atomic (in that it succeeds or fails) across all systems that have the same filesystem mounted. The link file must be cleaned up on program exit (normally accomplished by an at_exit handler, but see caveats below).
There are a few caveats compared to local file locks:
- These calls require the locker to have write access to the directory containing the file being locked.
- Unlike a normal flock call the lock may not be removed when the calling program exits (in particular if it is killed with SIGKILL).
NFS lock files are non-standard and difficult to reason about. This implementation strives to strike a balance between safety and utility in the common case:
- one program per machine
- one shared user running the program
Use cases outside of this may push on/break assumptions used for easy lock cleanup/taking and may lead to double-taking the lock. If you have such an odd use case you should test it carefully/consider a different locking mechanism.
Specific known bugs:
- Safety bug: if there are two instances running on the same machine, stale lock clean-up mechanism can remove a non-stale lock so the lock ends up taken twice.
- Liveness bug: a process can write its hostname*pid information to the void upon taking the lock, so you may end up with a broken (empty) lock file, which needs manual clean-up afterwards. (it seems that for this to happen another process needs to take and release the lock in quick succession)
val create : ?message:string -> string -> unit Core.Or_error.tcreate ?message pathtries to create and lock the file atpathby creating a hard link topath.nfs_lock. The contents ofpathwill be replaced with a sexp containing the caller's hostname and pid, and the optionalmessage.Efforts will be made to release this lock when the calling program exits. But there is no guarantee that this will occur under some types of program crash. If the program crashes without removing the lock file an attempt will be made to clean up on restart by checking the hostname and pid stored in the lockfile.
val create_exn : ?message:string -> string -> unitcreate_exn ?message pathis likecreate, but throws an exception when it fails to obtain the lock.
val blocking_create : ?timeout:Core.Time.Span.t -> ?message:string -> string -> unitblocking_create ?message pathis likecreate, but sleeps for a short while between lock attempts and does not return until it succeeds ortimeoutexpires. Timeout defaults to wait indefinitely.
val critical_section : ?message:string -> string -> timeout:Core.Time.Span.t -> f:(unit -> 'a) -> 'acritical_section ?message ~timeout path ~fwraps functionf(including exceptions escaping it) by first locking (usingblocking_create) and then unlocking the given lock file.
val get_hostname_and_pid : string -> (string * Core.Pid.t) optionget_hostname_and_pid pathreads the lock file atpathand returns the hostname and path in the file. ReturnsNoneif the file cannot be read.
val get_message : string -> string optionget_message pathreads the lock file atpathand returns the message in the file. ReturnsNoneif the file cannot be read.
val unlock_exn : string -> unitunlock_exn pathunlockspathifpathwas locked from the same host and the pid in the file is either the current pid or not the pid of a running process.It will raise if for some reason the lock at the given path cannot be unlocked, for example if the lock is taken by somebody else that is still alive on the same box, or taken by a process on a different host, or if there are Unix permissions issues, etc.
This function should be used only by programs that need to release their lock before exiting. If releasing the lock can or should wait till the end of the running process, do not call this function -- this library already takes care of releasing at exit all the locks taken.
val unlock : string -> unit Core.Or_error.t