Skip to content

Commit 044b6a9

Browse files
mhaggergitster
authored andcommitted
lockfile: allow file locking to be retried with a timeout
Currently, there is only one attempt to lock a file. If it fails, the whole operation fails. But it might sometimes be advantageous to try acquiring a file lock a few times before giving up. So add a new function, hold_lock_file_for_update_timeout(), that allows a timeout to be specified. Make hold_lock_file_for_update() a thin wrapper around the new function. If timeout_ms is positive, then retry for at least that many milliseconds to acquire the lock. On each failed attempt, use select() to wait for a backoff time that increases quadratically (capped at 1 second) and has a random component to prevent two processes from getting synchronized. If timeout_ms is negative, retry indefinitely. In a moment we will switch to using the new function when locking packed-refs. Signed-off-by: Michael Haggerty <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1ea28e1 commit 044b6a9

File tree

2 files changed

+91
-4
lines changed

2 files changed

+91
-4
lines changed

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);

0 commit comments

Comments
 (0)