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
This commit is contained in:
Junio C Hamano 2015-05-22 12:41:55 -07:00
commit fb257bfa17
5 changed files with 125 additions and 5 deletions

View File

@ -624,6 +624,12 @@ core.commentChar::
If set to "auto", `git-commit` would select a character that is not If set to "auto", `git-commit` would select a character that is not
the beginning character of any line in existing commit messages. 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:: sequence.editor::
Text editor used by `git rebase -i` for editing the rebase instruction file. 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. The value is meant to be interpreted by the shell when it is used.

View File

@ -157,6 +157,80 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
return lk->fd; 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) void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{ {
if (err == EEXIST) { 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 */ /* 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)) if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
unable_to_lock_die(path, errno); unable_to_lock_die(path, errno);
return fd; return fd;

View File

@ -74,8 +74,20 @@ struct lock_file {
extern void unable_to_lock_message(const char *path, int err, extern void unable_to_lock_message(const char *path, int err,
struct strbuf *buf); struct strbuf *buf);
extern NORETURN void unable_to_lock_die(const char *path, int err); 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_update_timeout(
extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); 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 FILE *fdopen_lock_file(struct lock_file *, const char *mode);
extern char *get_locked_file_path(struct lock_file *); extern char *get_locked_file_path(struct lock_file *);
extern int commit_lock_file_to(struct lock_file *, const char *path); extern int commit_lock_file_to(struct lock_file *, const char *path);

12
refs.c
View File

@ -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 */ /* This should return a meaningful errno on failure */
int lock_packed_refs(int flags) int lock_packed_refs(int flags)
{ {
static int timeout_configured = 0;
static int timeout_value = 1000;
struct packed_ref_cache *packed_ref_cache; 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; return -1;
/* /*
* Get the current packed-refs while holding the lock. If the * Get the current packed-refs while holding the lock. If the

View File

@ -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_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 test_done