/*
* Copyright (c) 2005, Junio C Hamano
*/
+
+/*
+ * State diagram and cleanup
+ * -------------------------
+ *
+ * This module keeps track of all locked files in `lock_file_list` for
+ * use at cleanup. This list and the `lock_file` objects that comprise
+ * it must be kept in self-consistent states at all time, because the
+ * program can be interrupted any time by a signal, in which case the
+ * signal handler will walk through the list attempting to clean up
+ * any open lock files.
+ *
+ * The possible states of a `lock_file` object are as follows:
+ *
+ * - Uninitialized. In this state the object's `on_list` field must be
+ * zero but the rest of its contents need not be initialized. As
+ * soon as the object is used in any way, it is irrevocably
+ * registered in `lock_file_list`, and `on_list` is set.
+ *
+ * - Locked, lockfile open (after `hold_lock_file_for_update()`,
+ * `hold_lock_file_for_append()`, or `reopen_lock_file()`). In this
+ * state:
+ *
+ * - the lockfile exists
+ * - `active` is set
+ * - `filename` holds the filename of the lockfile
+ * - `fd` holds a file descriptor open for writing to the lockfile
+ * - `fp` holds a pointer to an open `FILE` object if and only if
+ * `fdopen_lock_file()` has been called on the object
+ * - `owner` holds the PID of the process that locked the file
+ *
+ * - Locked, lockfile closed (after successful `close_lock_file()`).
+ * Same as the previous state, except that the lockfile is closed
+ * and `fd` is -1.
+ *
+ * - Unlocked (after `commit_lock_file()`, `commit_lock_file_to()`,
+ * `rollback_lock_file()`, a failed attempt to lock, or a failed
+ * `close_lock_file()`). In this state:
+ *
+ * - `active` is unset
+ * - `filename` is empty (usually, though there are transitory
+ * states in which this condition doesn't hold). Client code should
+ * *not* rely on the filename being empty in this state.
+ * - `fd` is -1
+ * - the object is left registered in the `lock_file_list`, and
+ * `on_list` is set.
+ *
+ * A lockfile is owned by the process that created it. The `lock_file`
+ * has an `owner` field that records the owner's PID. This field is
+ * used to prevent a forked process from closing a lockfile created by
+ * its parent.
+ */
+
#include "cache.h"
#include "lockfile.h"
#include "sigchain.h"
return lk->fd;
}
+static int sleep_microseconds(long us)
+{
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = us;
+ return select(0, NULL, NULL, NULL, &tv);
+}
+
+/*
+ * Constants defining the gaps between attempts to lock a file. The
+ * first backoff period is approximately INITIAL_BACKOFF_MS
+ * milliseconds. The longest backoff period is approximately
+ * (BACKOFF_MAX_MULTIPLIER * INITIAL_BACKOFF_MS) milliseconds.
+ */
+#define INITIAL_BACKOFF_MS 1L
+#define BACKOFF_MAX_MULTIPLIER 1000
+
+/*
+ * Try locking path, retrying with quadratic backoff for at least
+ * timeout_ms milliseconds. If timeout_ms is 0, try locking the file
+ * exactly once. If timeout_ms is -1, try indefinitely.
+ */
+static int lock_file_timeout(struct lock_file *lk, const char *path,
+ int flags, long timeout_ms)
+{
+ int n = 1;
+ int multiplier = 1;
+ long remaining_us = 0;
+ static int random_initialized = 0;
+
+ if (timeout_ms == 0)
+ return lock_file(lk, path, flags);
+
+ if (!random_initialized) {
+ srandom((unsigned int)getpid());
+ random_initialized = 1;
+ }
+
+ if (timeout_ms > 0) {
+ /* avoid overflow */
+ if (timeout_ms <= LONG_MAX / 1000)
+ remaining_us = timeout_ms * 1000;
+ else
+ remaining_us = LONG_MAX;
+ }
+
+ while (1) {
+ long backoff_ms, wait_us;
+ int fd;
+
+ fd = lock_file(lk, path, flags);
+
+ if (fd >= 0)
+ return fd; /* success */
+ else if (errno != EEXIST)
+ return -1; /* failure other than lock held */
+ else if (timeout_ms > 0 && remaining_us <= 0)
+ return -1; /* failure due to timeout */
+
+ backoff_ms = multiplier * INITIAL_BACKOFF_MS;
+ /* back off for between 0.75*backoff_ms and 1.25*backoff_ms */
+ wait_us = (750 + random() % 500) * backoff_ms;
+ sleep_microseconds(wait_us);
+ remaining_us -= wait_us;
+
+ /* Recursion: (n+1)^2 = n^2 + 2n + 1 */
+ multiplier += 2*n + 1;
+ if (multiplier > BACKOFF_MAX_MULTIPLIER)
+ multiplier = BACKOFF_MAX_MULTIPLIER;
+ else
+ n++;
+ }
+}
+
void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{
if (err == EEXIST) {
}
/* This should return a meaningful errno on failure */
-int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
+int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path,
+ int flags, long timeout_ms)
{
- int fd = lock_file(lk, path, flags);
+ int fd = lock_file_timeout(lk, path, flags, timeout_ms);
if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
unable_to_lock_die(path, errno);
return fd;
int save_errno = errno;
if (flags & LOCK_DIE_ON_ERROR)
- exit(128);
+ die("failed to prepare '%s' for appending", path);
close(orig_fd);
rollback_lock_file(lk);
errno = save_errno;
return lk->fp;
}
+const char *get_lock_file_path(struct lock_file *lk)
+{
+ if (!lk->active)
+ die("BUG: get_lock_file_path() called for unlocked object");
+ return lk->filename.buf;
+}
+
+int get_lock_file_fd(struct lock_file *lk)
+{
+ if (!lk->active)
+ die("BUG: get_lock_file_fd() called for unlocked object");
+ return lk->fd;
+}
+
+FILE *get_lock_file_fp(struct lock_file *lk)
+{
+ if (!lk->active)
+ die("BUG: get_lock_file_fp() called for unlocked object");
+ return lk->fp;
+}
+
char *get_locked_file_path(struct lock_file *lk)
{
if (!lk->active)