Skip to content

Commit 904e1e7

Browse files
author
Edward Thomson
authored
Merge pull request libgit2#3561 from libgit2/cmn/refdb-para
Concurrency fixes for the reference db
2 parents e1c1433 + aef54a4 commit 904e1e7

File tree

6 files changed

+185
-148
lines changed

6 files changed

+185
-148
lines changed

src/fileops.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,16 @@ int git_futils_creat_locked(const char *path, const mode_t mode)
7272
O_EXCL | O_BINARY | O_CLOEXEC, mode);
7373

7474
if (fd < 0) {
75+
int error = errno;
7576
giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
76-
return errno == EEXIST ? GIT_ELOCKED : -1;
77+
switch (error) {
78+
case EEXIST:
79+
return GIT_ELOCKED;
80+
case ENOENT:
81+
return GIT_ENOTFOUND;
82+
default:
83+
return -1;
84+
}
7785
}
7886

7987
return fd;

src/path.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,10 @@ int git_path_set_error(int errno_value, const char *path, const char *action)
644644
giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path);
645645
return GIT_EEXISTS;
646646

647+
case EACCES:
648+
giterr_set(GITERR_OS, "Failed %s - '%s' is locked", action, path);
649+
return GIT_ELOCKED;
650+
647651
default:
648652
giterr_set(GITERR_OS, "Could not %s '%s'", action, path);
649653
return -1;

src/refdb.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,15 @@ int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
125125

126126
int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob)
127127
{
128+
int error;
129+
128130
if (!db->backend || !db->backend->iterator) {
129131
giterr_set(GITERR_REFERENCE, "This backend doesn't support iterators");
130132
return -1;
131133
}
132134

133-
if (db->backend->iterator(out, db->backend, glob) < 0)
134-
return -1;
135+
if ((error = db->backend->iterator(out, db->backend, glob)) < 0)
136+
return error;
135137

136138
GIT_REFCOUNT_INC(db);
137139
(*out)->db = db;

src/refdb_fs.c

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -326,12 +326,13 @@ static int refdb_fs_backend__exists(
326326
{
327327
refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
328328
git_buf ref_path = GIT_BUF_INIT;
329+
int error;
329330

330331
assert(backend);
331332

332-
if (packed_reload(backend) < 0 ||
333-
git_buf_joinpath(&ref_path, backend->path, ref_name) < 0)
334-
return -1;
333+
if ((error = packed_reload(backend)) < 0 ||
334+
(error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0)
335+
return error;
335336

336337
*exists = git_path_isfile(ref_path.ptr) ||
337338
(git_sortedcache_lookup(backend->refcache, ref_name) != NULL);
@@ -409,8 +410,8 @@ static int packed_lookup(
409410
int error = 0;
410411
struct packref *entry;
411412

412-
if (packed_reload(backend) < 0)
413-
return -1;
413+
if ((error = packed_reload(backend)) < 0)
414+
return error;
414415

415416
if (git_sortedcache_rlock(backend->refcache) < 0)
416417
return -1;
@@ -615,13 +616,14 @@ static int refdb_fs_backend__iterator_next_name(
615616
static int refdb_fs_backend__iterator(
616617
git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
617618
{
619+
int error;
618620
refdb_fs_iter *iter;
619621
refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
620622

621623
assert(backend);
622624

623-
if (packed_reload(backend) < 0)
624-
return -1;
625+
if ((error = packed_reload(backend)) < 0)
626+
return error;
625627

626628
iter = git__calloc(1, sizeof(refdb_fs_iter));
627629
GITERR_CHECK_ALLOC(iter);
@@ -674,16 +676,18 @@ static int reference_path_available(
674676
int force)
675677
{
676678
size_t i;
679+
int error;
677680

678-
if (packed_reload(backend) < 0)
679-
return -1;
681+
if ((error = packed_reload(backend)) < 0)
682+
return error;
680683

681684
if (!force) {
682685
int exists;
683686

684-
if (refdb_fs_backend__exists(
685-
&exists, (git_refdb_backend *)backend, new_ref) < 0)
686-
return -1;
687+
if ((error = refdb_fs_backend__exists(
688+
&exists, (git_refdb_backend *)backend, new_ref)) < 0) {
689+
return error;
690+
}
687691

688692
if (exists) {
689693
giterr_set(GITERR_REFERENCE,
@@ -901,40 +905,60 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file)
901905
static int packed_remove_loose(refdb_fs_backend *backend)
902906
{
903907
size_t i;
904-
git_buf full_path = GIT_BUF_INIT;
905-
int failed = 0;
908+
git_filebuf lock = GIT_FILEBUF_INIT;
909+
git_buf ref_content = GIT_BUF_INIT;
910+
int error = 0;
906911

907912
/* backend->refcache is already locked when this is called */
908913

909914
for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
910915
struct packref *ref = git_sortedcache_entry(backend->refcache, i);
916+
git_oid current_id;
911917

912918
if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
913919
continue;
914920

915-
if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
916-
return -1; /* critical; do not try to recover on oom */
921+
git_filebuf_cleanup(&lock);
917922

918-
if (git_path_exists(full_path.ptr) && p_unlink(full_path.ptr) < 0) {
919-
if (failed)
920-
continue;
923+
/* We need to stop anybody from updating the ref while we try to do a safe delete */
924+
error = loose_lock(&lock, backend, ref->name);
925+
/* If someone else is updating it, let them do it */
926+
if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
927+
continue;
921928

922-
giterr_set(GITERR_REFERENCE,
923-
"Failed to remove loose reference '%s' after packing: %s",
924-
full_path.ptr, strerror(errno));
925-
failed = 1;
929+
if (error < 0) {
930+
giterr_set(GITERR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
931+
return error;
926932
}
927933

934+
error = git_futils_readbuffer(&ref_content, lock.path_original);
935+
/* Someone else beat us to cleaning up the ref, let's simply continue */
936+
if (error == GIT_ENOTFOUND)
937+
continue;
938+
939+
/* This became a symref between us packing and trying to delete it, so ignore it */
940+
if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF))
941+
continue;
942+
943+
/* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */
944+
if (loose_parse_oid(&current_id, lock.path_original, &ref_content) < 0)
945+
continue;
946+
947+
/* If the ref moved since we packed it, we must not delete it */
948+
if (!git_oid_equal(&current_id, &ref->oid))
949+
continue;
950+
928951
/*
929952
* if we fail to remove a single file, this is *not* good,
930953
* but we should keep going and remove as many as possible.
931-
* After we've removed as many files as possible, we return
932-
* the error code anyway.
954+
* If we fail to remove, the ref is still in the old state, so
955+
* we haven't lost information.
933956
*/
957+
p_unlink(lock.path_original);
934958
}
935959

936-
git_buf_free(&full_path);
937-
return failed ? -1 : 0;
960+
git_filebuf_cleanup(&lock);
961+
return 0;
938962
}
939963

940964
/*
@@ -944,41 +968,42 @@ static int packed_write(refdb_fs_backend *backend)
944968
{
945969
git_sortedcache *refcache = backend->refcache;
946970
git_filebuf pack_file = GIT_FILEBUF_INIT;
971+
int error;
947972
size_t i;
948973

949974
/* lock the cache to updates while we do this */
950-
if (git_sortedcache_wlock(refcache) < 0)
951-
return -1;
975+
if ((error = git_sortedcache_wlock(refcache)) < 0)
976+
return error;
952977

953978
/* Open the file! */
954-
if (git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE) < 0)
979+
if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE)) < 0)
955980
goto fail;
956981

957982
/* Packfiles have a header... apparently
958983
* This is in fact not required, but we might as well print it
959984
* just for kicks */
960-
if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
985+
if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0)
961986
goto fail;
962987

963988
for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
964989
struct packref *ref = git_sortedcache_entry(refcache, i);
965990
assert(ref);
966991

967-
if (packed_find_peel(backend, ref) < 0)
992+
if ((error = packed_find_peel(backend, ref)) < 0)
968993
goto fail;
969994

970-
if (packed_write_ref(ref, &pack_file) < 0)
995+
if ((error = packed_write_ref(ref, &pack_file)) < 0)
971996
goto fail;
972997
}
973998

974999
/* if we've written all the references properly, we can commit
9751000
* the packfile to make the changes effective */
976-
if (git_filebuf_commit(&pack_file) < 0)
1001+
if ((error = git_filebuf_commit(&pack_file)) < 0)
9771002
goto fail;
9781003

9791004
/* when and only when the packfile has been properly written,
9801005
* we can go ahead and remove the loose refs */
981-
if (packed_remove_loose(backend) < 0)
1006+
if ((error = packed_remove_loose(backend)) < 0)
9821007
goto fail;
9831008

9841009
git_sortedcache_updated(refcache);
@@ -991,7 +1016,7 @@ static int packed_write(refdb_fs_backend *backend)
9911016
git_filebuf_cleanup(&pack_file);
9921017
git_sortedcache_wunlock(refcache);
9931018

994-
return -1;
1019+
return error;
9951020
}
9961021

9971022
static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message);
@@ -1143,8 +1168,7 @@ static int refdb_fs_backend__write(
11431168

11441169
assert(backend);
11451170

1146-
error = reference_path_available(backend, ref->name, NULL, force);
1147-
if (error < 0)
1171+
if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
11481172
return error;
11491173

11501174
/* We need to perform the reflog append and old value check under the ref's lock */
@@ -1260,15 +1284,14 @@ static int refdb_fs_backend__delete_tail(
12601284
if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
12611285
return -1;
12621286

1263-
if (git_path_isfile(loose_path.ptr)) {
1264-
error = p_unlink(loose_path.ptr);
1265-
loose_deleted = 1;
1266-
}
1267-
1268-
git_buf_free(&loose_path);
12691287

1270-
if (error != 0)
1288+
error = p_unlink(loose_path.ptr);
1289+
if (error < 0 && errno == ENOENT)
1290+
error = 0;
1291+
else if (error < 0)
12711292
goto cleanup;
1293+
else if (error == 0)
1294+
loose_deleted = 1;
12721295

12731296
if ((error = packed_reload(backend)) < 0)
12741297
goto cleanup;
@@ -1291,6 +1314,7 @@ static int refdb_fs_backend__delete_tail(
12911314
error = packed_write(backend);
12921315

12931316
cleanup:
1317+
git_buf_free(&loose_path);
12941318
git_filebuf_cleanup(file);
12951319

12961320
return error;
@@ -1362,14 +1386,15 @@ static int refdb_fs_backend__rename(
13621386

13631387
static int refdb_fs_backend__compress(git_refdb_backend *_backend)
13641388
{
1389+
int error;
13651390
refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
13661391

13671392
assert(backend);
13681393

1369-
if (packed_reload(backend) < 0 || /* load the existing packfile */
1370-
packed_loadloose(backend) < 0 || /* add all the loose refs */
1371-
packed_write(backend) < 0) /* write back to disk */
1372-
return -1;
1394+
if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */
1395+
(error = packed_loadloose(backend)) < 0 || /* add all the loose refs */
1396+
(error = packed_write(backend)) < 0) /* write back to disk */
1397+
return error;
13731398

13741399
return 0;
13751400
}
@@ -1789,9 +1814,10 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co
17891814
* there maybe an obsolete/unused directory (or directory hierarchy) in the way.
17901815
*/
17911816
if (git_path_isdir(git_buf_cstr(&path))) {
1792-
if ((git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0))
1793-
error = -1;
1794-
else if (git_path_isdir(git_buf_cstr(&path))) {
1817+
if ((error = git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) {
1818+
if (error == GIT_ENOTFOUND)
1819+
error = 0;
1820+
} else if (git_path_isdir(git_buf_cstr(&path))) {
17951821
giterr_set(GITERR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
17961822
ref->name);
17971823
error = GIT_EDIRECTORY;

src/sortedcache.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,26 +200,34 @@ void git_sortedcache_runlock(git_sortedcache *sc)
200200
int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf)
201201
{
202202
int error, fd;
203+
struct stat st;
203204

204205
if ((error = git_sortedcache_wlock(sc)) < 0)
205206
return error;
206207

207208
if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0)
208209
goto unlock;
209210

210-
if (!git__is_sizet(sc->stamp.size)) {
211-
giterr_set(GITERR_INVALID, "Unable to load file larger than size_t");
211+
if ((fd = git_futils_open_ro(sc->path)) < 0) {
212+
error = fd;
213+
goto unlock;
214+
}
215+
216+
if (p_fstat(fd, &st) < 0) {
217+
giterr_set(GITERR_OS, "failed to stat file");
212218
error = -1;
213219
goto unlock;
214220
}
215221

216-
if ((fd = git_futils_open_ro(sc->path)) < 0) {
217-
error = fd;
222+
if (!git__is_sizet(st.st_size)) {
223+
giterr_set(GITERR_INVALID, "Unable to load file larger than size_t");
224+
error = -1;
225+
(void)p_close(fd);
218226
goto unlock;
219227
}
220228

221229
if (buf)
222-
error = git_futils_readbuffer_fd(buf, fd, (size_t)sc->stamp.size);
230+
error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size);
223231

224232
(void)p_close(fd);
225233

0 commit comments

Comments
 (0)