lockfile: add accessor get_lock_file_path()
[gitweb.git] / lockfile.c
index 988927775169487df7677a17465b512a567488dd..5e954baf07a5266a7abe341fadf647da55a28705 100644 (file)
@@ -1,6 +1,59 @@
 /*
  * 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"
@@ -157,6 +210,80 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
        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) {
@@ -179,9 +306,10 @@ NORETURN void unable_to_lock_die(const char *path, int err)
 }
 
 /* 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;
@@ -214,7 +342,7 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags)
                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;
@@ -236,6 +364,27 @@ FILE *fdopen_lock_file(struct lock_file *lk, const char *mode)
        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)