Skip to content

Commit fb257bf

Browse files
committed
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
2 parents 29b2041 + f4ab4f3 commit fb257bf

File tree

5 files changed

+125
-5
lines changed

5 files changed

+125
-5
lines changed

Documentation/config.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,12 @@ core.commentChar::
624624
If set to "auto", `git-commit` would select a character that is not
625625
the beginning character of any line in existing commit messages.
626626

627+
core.packedRefsTimeout::
628+
The length of time, in milliseconds, to retry when trying to
629+
lock the `packed-refs` file. Value 0 means not to retry at
630+
all; -1 means to try indefinitely. Default is 1000 (i.e.,
631+
retry for 1 second).
632+
627633
sequence.editor::
628634
Text editor used by `git rebase -i` for editing the rebase instruction file.
629635
The value is meant to be interpreted by the shell when it is used.

lockfile.c

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,80 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
157157
return lk->fd;
158158
}
159159

160+
static int sleep_microseconds(long us)
161+
{
162+
struct timeval tv;
163+
tv.tv_sec = 0;
164+
tv.tv_usec = us;
165+
return select(0, NULL, NULL, NULL, &tv);
166+
}
167+
168+
/*
169+
* Constants defining the gaps between attempts to lock a file. The
170+
* first backoff period is approximately INITIAL_BACKOFF_MS
171+
* milliseconds. The longest backoff period is approximately
172+
* (BACKOFF_MAX_MULTIPLIER * INITIAL_BACKOFF_MS) milliseconds.
173+
*/
174+
#define INITIAL_BACKOFF_MS 1L
175+
#define BACKOFF_MAX_MULTIPLIER 1000
176+
177+
/*
178+
* Try locking path, retrying with quadratic backoff for at least
179+
* timeout_ms milliseconds. If timeout_ms is 0, try locking the file
180+
* exactly once. If timeout_ms is -1, try indefinitely.
181+
*/
182+
static int lock_file_timeout(struct lock_file *lk, const char *path,
183+
int flags, long timeout_ms)
184+
{
185+
int n = 1;
186+
int multiplier = 1;
187+
long remaining_us = 0;
188+
static int random_initialized = 0;
189+
190+
if (timeout_ms == 0)
191+
return lock_file(lk, path, flags);
192+
193+
if (!random_initialized) {
194+
srandom((unsigned int)getpid());
195+
random_initialized = 1;
196+
}
197+
198+
if (timeout_ms > 0) {
199+
/* avoid overflow */
200+
if (timeout_ms <= LONG_MAX / 1000)
201+
remaining_us = timeout_ms * 1000;
202+
else
203+
remaining_us = LONG_MAX;
204+
}
205+
206+
while (1) {
207+
long backoff_ms, wait_us;
208+
int fd;
209+
210+
fd = lock_file(lk, path, flags);
211+
212+
if (fd >= 0)
213+
return fd; /* success */
214+
else if (errno != EEXIST)
215+
return -1; /* failure other than lock held */
216+
else if (timeout_ms > 0 && remaining_us <= 0)
217+
return -1; /* failure due to timeout */
218+
219+
backoff_ms = multiplier * INITIAL_BACKOFF_MS;
220+
/* back off for between 0.75*backoff_ms and 1.25*backoff_ms */
221+
wait_us = (750 + random() % 500) * backoff_ms;
222+
sleep_microseconds(wait_us);
223+
remaining_us -= wait_us;
224+
225+
/* Recursion: (n+1)^2 = n^2 + 2n + 1 */
226+
multiplier += 2*n + 1;
227+
if (multiplier > BACKOFF_MAX_MULTIPLIER)
228+
multiplier = BACKOFF_MAX_MULTIPLIER;
229+
else
230+
n++;
231+
}
232+
}
233+
160234
void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
161235
{
162236
if (err == EEXIST) {
@@ -179,9 +253,10 @@ NORETURN void unable_to_lock_die(const char *path, int err)
179253
}
180254

181255
/* This should return a meaningful errno on failure */
182-
int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
256+
int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path,
257+
int flags, long timeout_ms)
183258
{
184-
int fd = lock_file(lk, path, flags);
259+
int fd = lock_file_timeout(lk, path, flags, timeout_ms);
185260
if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
186261
unable_to_lock_die(path, errno);
187262
return fd;

lockfile.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,20 @@ struct lock_file {
7474
extern void unable_to_lock_message(const char *path, int err,
7575
struct strbuf *buf);
7676
extern NORETURN void unable_to_lock_die(const char *path, int err);
77-
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
78-
extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
77+
extern int hold_lock_file_for_update_timeout(
78+
struct lock_file *lk, const char *path,
79+
int flags, long timeout_ms);
80+
81+
static inline int hold_lock_file_for_update(
82+
struct lock_file *lk, const char *path,
83+
int flags)
84+
{
85+
return hold_lock_file_for_update_timeout(lk, path, flags, 0);
86+
}
87+
88+
extern int hold_lock_file_for_append(struct lock_file *lk, const char *path,
89+
int flags);
90+
7991
extern FILE *fdopen_lock_file(struct lock_file *, const char *mode);
8092
extern char *get_locked_file_path(struct lock_file *);
8193
extern int commit_lock_file_to(struct lock_file *, const char *path);

refs.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2505,9 +2505,19 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
25052505
/* This should return a meaningful errno on failure */
25062506
int lock_packed_refs(int flags)
25072507
{
2508+
static int timeout_configured = 0;
2509+
static int timeout_value = 1000;
2510+
25082511
struct packed_ref_cache *packed_ref_cache;
25092512

2510-
if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
2513+
if (!timeout_configured) {
2514+
git_config_get_int("core.packedrefstimeout", &timeout_value);
2515+
timeout_configured = 1;
2516+
}
2517+
2518+
if (hold_lock_file_for_update_timeout(
2519+
&packlock, git_path("packed-refs"),
2520+
flags, timeout_value) < 0)
25112521
return -1;
25122522
/*
25132523
* Get the current packed-refs while holding the lock. If the

t/t3210-pack-refs.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,21 @@ test_expect_success 'notice d/f conflict with existing ref' '
187187
test_must_fail git branch foo/bar/baz/lots/of/extra/components
188188
'
189189

190+
test_expect_success 'timeout if packed-refs.lock exists' '
191+
LOCK=.git/packed-refs.lock &&
192+
>"$LOCK" &&
193+
test_when_finished "rm -f $LOCK" &&
194+
test_must_fail git pack-refs --all --prune
195+
'
196+
197+
test_expect_success 'retry acquiring packed-refs.lock' '
198+
LOCK=.git/packed-refs.lock &&
199+
>"$LOCK" &&
200+
test_when_finished "wait; rm -f $LOCK" &&
201+
{
202+
( sleep 1 ; rm -f $LOCK ) &
203+
} &&
204+
git -c core.packedrefstimeout=3000 pack-refs --all --prune
205+
'
206+
190207
test_done

0 commit comments

Comments
 (0)