#' Lock and unlock directories
#'
#' Lock and unlock the package and version directories for thread-safe processing.
#'
#' @inheritParams touchDirectory
#' @param ... For \code{lockDirectory}, further arguments to pass to \code{\link{lock}}.
#'
#' For \code{unlockDirectory}, further arguments to pass to \code{\link{clearDirectories}}.
#' @param lock.info The list returned by \code{\link{lockDirectory}}.
#' @param clear Logical scalar indicating whether to remove expired versions via \code{\link{clearDirectories}}.
#'
#' @return
#' \code{lockDirectory} returns a list of locking information, including lock handles generated by the \pkg{filelock} package.
#'
#' \code{unlockDirectory} unlocks the handles generated by \code{lockDirectory}.
#' If \code{clear=TRUE}, versioned directories that have expired are removed by \code{\link{clearDirectories}}.
#' It returns a \code{NULL} invisibly.
#'
#' @details
#' \code{lockDirectory} actually creates two locks:
#' \itemize{
#' \item The first \dQuote{V} lock is applied to the versioned directory (i.e., \code{basename(path)}) within the package cache (i.e., \code{dirname(path)}).
#' This provides thread-safe read/write on its contents, protecting against other processes that want to write to the same versioned directory.
#' If the caller is only reading from \code{path}, they can set \code{exclusive=FALSE} in \code{...} to define a shared lock for concurrent reads across multiple processes.
#' \item The second \dQuote{P} lock is applied to the package cache and is always a shared lock, regardless of the contents of \code{...}.
#' This provides thread-safe access to the lock file used by the V lock, protecting it from deletion when the relevant directory expires in \code{\link{clearDirectories}}.
#' }
#' If \code{dirname(path)} does not exist, it will be created by \code{lockDirectory}.
#'
#' \code{\link{clearDirectories}} is called in \code{unlockDirectory} as the former needs to hold an exclusive lock on the package cache.
#' Thus, the clearing can only be performed after the P lock created by \code{lockDirectory} is released.
#'
#' @author Aaron Lun
#'
#' @examples
#' # Creating the relevant directories.
#' cache.dir <- tempfile(pattern="expired_demo")
#' version <- package_version("1.11.0")
#'
#' handle <- lockDirectory(file.path(cache.dir, version))
#' handle
#' unlockDirectory(handle)
#'
#' list.files(cache.dir)
#'
#' @export
#' @importFrom filelock lock unlock
lockDirectory <- function(path, ...) {
dir <- dirname(path)
dir.create(dir, showWarnings=FALSE, recursive=TRUE)
plock <- .plock_path(dir)
second <- lock(plock, exclusive=FALSE) # define this first to protect the other lock.
vlock <- .vlock_path(path)
tryCatch({
first <- lock(vlock, ...)
}, error=function(e) {
# Release the prior lock if acquiring this one fails.
unlock(second)
stop(e)
})
list(version=first, central=second, path=path)
}
lock.suffix <- "-00LOCK"
.unslash <- function(path) {
sub("/$", "", path)
}
.vlock_path <- function(path) {
paste0(.unslash(path), lock.suffix)
}
.plock_path <- function(dir) {
file.path(dir, paste0("central", lock.suffix))
}
#' @export
#' @importFrom filelock unlock
#' @rdname lockDirectory
unlockDirectory <- function(lock.info, clear=TRUE, ...) {
# Unlock the first one while the second lock on the 'path' directory is
# still in place to protect the lock file.
unlock(lock.info$version)
unlock(lock.info$central)
if (clear) {
path <- lock.info$path
clearDirectories(dirname(path), reference=basename(path), ...)
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.