Browse Source

Merge branch 'mh/lockfile-retry'

Instead of dying immediately upon failing to obtain a lock, retry
after a short while with backoff.

* mh/lockfile-retry:
  lock_packed_refs(): allow retries when acquiring the packed-refs lock
  lockfile: allow file locking to be retried with a timeout
maint
Junio C Hamano 10 years ago
parent
commit
fb257bfa17
  1. 6
      Documentation/config.txt
  2. 79
      lockfile.c
  3. 16
      lockfile.h
  4. 12
      refs.c
  5. 17
      t/t3210-pack-refs.sh

6
Documentation/config.txt

@ -624,6 +624,12 @@ core.commentChar:: @@ -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.

79
lockfile.c

@ -157,6 +157,80 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) @@ -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) @@ -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;

16
lockfile.h

@ -74,8 +74,20 @@ struct lock_file { @@ -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);

12
refs.c

@ -2505,9 +2505,19 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data) @@ -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

17
t/t3210-pack-refs.sh

@ -187,4 +187,21 @@ test_expect_success 'notice d/f conflict with existing ref' ' @@ -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

Loading…
Cancel
Save