summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config.txt6
-rw-r--r--lockfile.c79
-rw-r--r--lockfile.h16
-rw-r--r--refs.c12
-rwxr-xr-xt/t3210-pack-refs.sh17
5 files changed, 125 insertions, 5 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 54cbf6b..0f668bb 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -624,6 +624,12 @@ core.commentChar::
If set to "auto", `git-commit` would select a character that is not
the beginning character of any line in existing commit messages.
+core.packedRefsTimeout::
+ The length of time, in milliseconds, to retry when trying to
+ lock the `packed-refs` file. Value 0 means not to retry at
+ all; -1 means to try indefinitely. Default is 1000 (i.e.,
+ retry for 1 second).
+
sequence.editor::
Text editor used by `git rebase -i` for editing the rebase instruction file.
The value is meant to be interpreted by the shell when it is used.
diff --git a/lockfile.c b/lockfile.c
index 9889277..30e65e9 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -157,6 +157,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 +253,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;
diff --git a/lockfile.h b/lockfile.h
index cd2ec95..b4abc61 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -74,8 +74,20 @@ struct lock_file {
extern void unable_to_lock_message(const char *path, int err,
struct strbuf *buf);
extern NORETURN void unable_to_lock_die(const char *path, int err);
-extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
-extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
+extern int hold_lock_file_for_update_timeout(
+ struct lock_file *lk, const char *path,
+ int flags, long timeout_ms);
+
+static inline int hold_lock_file_for_update(
+ struct lock_file *lk, const char *path,
+ int flags)
+{
+ return hold_lock_file_for_update_timeout(lk, path, flags, 0);
+}
+
+extern int hold_lock_file_for_append(struct lock_file *lk, const char *path,
+ int flags);
+
extern FILE *fdopen_lock_file(struct lock_file *, const char *mode);
extern char *get_locked_file_path(struct lock_file *);
extern int commit_lock_file_to(struct lock_file *, const char *path);
diff --git a/refs.c b/refs.c
index f704ee2..8480d8d 100644
--- a/refs.c
+++ b/refs.c
@@ -2505,9 +2505,19 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
/* This should return a meaningful errno on failure */
int lock_packed_refs(int flags)
{
+ static int timeout_configured = 0;
+ static int timeout_value = 1000;
+
struct packed_ref_cache *packed_ref_cache;
- if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
+ if (!timeout_configured) {
+ git_config_get_int("core.packedrefstimeout", &timeout_value);
+ timeout_configured = 1;
+ }
+
+ if (hold_lock_file_for_update_timeout(
+ &packlock, git_path("packed-refs"),
+ flags, timeout_value) < 0)
return -1;
/*
* Get the current packed-refs while holding the lock. If the
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
index aa9eb3a..8aae98d 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t3210-pack-refs.sh
@@ -187,4 +187,21 @@ test_expect_success 'notice d/f conflict with existing ref' '
test_must_fail git branch foo/bar/baz/lots/of/extra/components
'
+test_expect_success 'timeout if packed-refs.lock exists' '
+ LOCK=.git/packed-refs.lock &&
+ >"$LOCK" &&
+ test_when_finished "rm -f $LOCK" &&
+ test_must_fail git pack-refs --all --prune
+'
+
+test_expect_success 'retry acquiring packed-refs.lock' '
+ LOCK=.git/packed-refs.lock &&
+ >"$LOCK" &&
+ test_when_finished "wait; rm -f $LOCK" &&
+ {
+ ( sleep 1 ; rm -f $LOCK ) &
+ } &&
+ git -c core.packedrefstimeout=3000 pack-refs --all --prune
+'
+
test_done