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:
commit
fb257bfa17
@ -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.
|
||||||
|
79
lockfile.c
79
lockfile.c
@ -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;
|
||||||
|
16
lockfile.h
16
lockfile.h
@ -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
12
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 */
|
/* 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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user