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 <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Haggerty committed May 11, 2015 at 12:35 UTC 044b6a9efecf9941073b99ced4cb2881b18aee62
2 files changed +91 -4
lockfile.c
+77 -2
@@ -157,6 +157,80 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
157 return lk->fd;
158 }
159
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 +
234 void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
235 {
236 if (err == EEXIST) {
@@ -179,9 +253,10 @@ NORETURN void unable_to_lock_die(const char *path, int err)
253 }
254
255 /* 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)
258 {
184 - int fd = lock_file(lk, path, flags);
259 + int fd = lock_file_timeout(lk, path, flags, timeout_ms);
260 if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
261 unable_to_lock_die(path, errno);
262 return fd;
lockfile.h
+14 -2
@@ -74,8 +74,20 @@ struct lock_file {
74 extern void unable_to_lock_message(const char *path, int err,
75 struct strbuf *buf);
76 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 +
91 extern FILE *fdopen_lock_file(struct lock_file *, const char *mode);
92 extern char *get_locked_file_path(struct lock_file *);
93 extern int commit_lock_file_to(struct lock_file *, const char *path);