Evaluate expression with file lock

Usage

with_flock(filename, expr, envir = parent.frame(), delay = 0.01, max_delay = 0.1, timeout = Inf, error = TRUE, verbose = FALSE)
with_flock_(filename, expr, envir = parent.frame(), delay = 0.01, max_delay = 0.1, timeout = Inf, error = TRUE, verbose = FALSE)

Arguments

filename
The name of the lockfile. If NULL, no lockfile is used and the action always runs.
expr
Expression to evaluate once the lock is acquired. This expression must not affect the file filename in any way (see warnings in the package README).
envir
Environment in which to evaluate expr. The default is usually reasonable.
delay
Initial period to poll the file for release if it is locked. Note this is a minimum time to delay. On POSIX system with fcntl I see delays around the 0.2s mark when accessing files over SMB so small values there are likely aspirational. This time is also additional to the fcntl call (i.e., the pattern is try to lock, then sleep).
max_delay
Maximum period between polls; the delay will grow from delay to max_delay over subsequent calls.
timeout
Total maximum time to wait. If a lock cannot be acquired in this period, we either error or return without evaluating expr (see Details).
error
Is failure to acquire a lock an error? If TRUE then an error is thrown or the value if expr is returned. If FALSE the return value is a list with the first element indicating success or not and the second element being either a condition or the return value. See Details.
verbose
Print information as at each lock acquisition attempt. May be useful in debugging.

Description

Evaluate an expression after acquiring, and while holding, a file lock. The with_flock_ version uses standard evaluation and is suitable for programming.

Details

The behaviour on error is controlled by the error argument. If TRUE (the default) then if a lock cannot be established then with_flock will throw an error and not return. If there is no error the return value is whatever expr evaluates to. (If expr itself throws an error the lock will always be cleaned up, though this may fail if the lockfile is removed by the code in expr or another process -- try to avoid this!)

If error=FALSE the return value is always a list of length 2. The first element is a logical scalar TRUE or FALSE indicating if the lock was acquired and the expression evaluated. The second element is the value of expr if the lock was acquired or a condition object describing why the lock was not acquired. If expr throws an error, that error will still not be caught (use tryCatch).

In either case, if a lock cannot be established the code in expr is not evaluated.

Warning

It is not safe to use the file for anything, including locking it a second time (e.g. with_flock(filename, with_flock(filename, ...))). Simply opening or closing a handle to a file will break the lock on non-Windows systems (this is a limitation of the underlying system calls).

Examples

## Demonstrating this is difficult because for a lock to fail ## another process needs to hold a lock on the file. But the ## basic approach for using it is below. ## First, we have a file that we want to modify; say path: path <- tempfile() writeLines(c("a", "b", "c"), path) ## Then we have another file that we'll use as a lock. We can't ## safely write to this file (see notes above) so it's simplest to ## have a separate file here. lock <- paste0(path, ".lock") ## Suppose we want to take the first element of the data in 'path'. ## This involves a read and a write operation so is not atomic - ## another process could read the file in the meantime and we'd ## both pull the same element out. But if we advertise that we're ## using it by using a lock the other process can wait until we ## release the lock: res <- with_flock(lock, { txt <- readLines(path) writeLines(txt[-1], path) txt[[1]] }) res
[1] "a"