Skip to content

Commit 6c90726

Browse files
pks-tgitster
authored andcommitted
refs/files: batch refname availability checks for normal transactions
Same as the "reftable" backend that we have adapted in the preceding commit to use batched refname availability checks we can also do so for the "files" backend. Things are a bit more intricate here though, as we call `refs_verify_refname_available()` in a set of different contexts: 1. `lock_raw_ref()` when it hits either EEXISTS or EISDIR when creating a new reference, mostly to create a nice, user-readable error message. This is nothing we have to care about too much, as we only hit this code path at most once when we hit a conflict. 2. `lock_raw_ref()` when it _could_ create the lockfile to check whether it is conflicting with any packed refs. In the general case, this code path will be hit once for every (successful) reference update. 3. `lock_ref_oid_basic()`, but it is only executed when copying or renaming references or when expiring reflogs. It will thus not be called in contexts where we have many references queued up. 4. `refs_refname_ref_available()`, but again only when copying or renaming references. It is thus not interesting due to the same reason as the previous case. 5. `files_transaction_finish_initial()`, which is only executed when creating a new repository or migrating references. So out of these, only (2) and (5) are viable candidates to use the batched checks. Adapt `lock_raw_ref()` accordingly by queueing up reference names that need to be checked for availability and then checking them after we have processed all updates. This check is done before we (optionally) lock the `packed-refs` file, which is somewhat flawed because it means that the `packed-refs` could still change after the availability check and thus create an undetected conflict. But unconditionally locking the file would change semantics that users are likely to rely on, so we keep the current locking sequence intact, even if it's suboptmial. The refactoring of `files_transaction_finish_initial()` will be done in the next commit. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 351f592 commit 6c90726

File tree

1 file changed

+31
-11
lines changed

1 file changed

+31
-11
lines changed

refs/files-backend.c

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ static void unlock_ref(struct ref_lock *lock)
678678
*/
679679
static int lock_raw_ref(struct files_ref_store *refs,
680680
const char *refname, int mustexist,
681+
struct string_list *refnames_to_check,
681682
const struct string_list *extras,
682683
struct ref_lock **lock_p,
683684
struct strbuf *referent,
@@ -855,16 +856,11 @@ static int lock_raw_ref(struct files_ref_store *refs,
855856
}
856857

857858
/*
858-
* If the ref did not exist and we are creating it,
859-
* make sure there is no existing packed ref that
860-
* conflicts with refname:
859+
* If the ref did not exist and we are creating it, we have to
860+
* make sure there is no existing packed ref that conflicts
861+
* with refname. This check is deferred so that we can batch it.
861862
*/
862-
if (refs_verify_refname_available(
863-
refs->packed_ref_store, refname,
864-
extras, NULL, 0, err)) {
865-
ret = TRANSACTION_NAME_CONFLICT;
866-
goto error_return;
867-
}
863+
string_list_append(refnames_to_check, refname);
868864
}
869865

870866
ret = 0;
@@ -2569,6 +2565,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
25692565
struct ref_update *update,
25702566
struct ref_transaction *transaction,
25712567
const char *head_ref,
2568+
struct string_list *refnames_to_check,
25722569
struct string_list *affected_refnames,
25732570
struct strbuf *err)
25742571
{
@@ -2597,7 +2594,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
25972594
lock->count++;
25982595
} else {
25992596
ret = lock_raw_ref(refs, update->refname, mustexist,
2600-
affected_refnames,
2597+
refnames_to_check, affected_refnames,
26012598
&lock, &referent,
26022599
&update->type, err);
26032600
if (ret) {
@@ -2811,6 +2808,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
28112808
size_t i;
28122809
int ret = 0;
28132810
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
2811+
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
28142812
char *head_ref = NULL;
28152813
int head_type;
28162814
struct files_transaction_backend_data *backend_data;
@@ -2898,7 +2896,8 @@ static int files_transaction_prepare(struct ref_store *ref_store,
28982896
struct ref_update *update = transaction->updates[i];
28992897

29002898
ret = lock_ref_for_update(refs, update, transaction,
2901-
head_ref, &affected_refnames, err);
2899+
head_ref, &refnames_to_check,
2900+
&affected_refnames, err);
29022901
if (ret)
29032902
goto cleanup;
29042903

@@ -2930,6 +2929,26 @@ static int files_transaction_prepare(struct ref_store *ref_store,
29302929
}
29312930
}
29322931

2932+
/*
2933+
* Verify that none of the loose reference that we're about to write
2934+
* conflict with any existing packed references. Ideally, we'd do this
2935+
* check after the packed-refs are locked so that the file cannot
2936+
* change underneath our feet. But introducing such a lock now would
2937+
* probably do more harm than good as users rely on there not being a
2938+
* global lock with the "files" backend.
2939+
*
2940+
* Another alternative would be to do the check after the (optional)
2941+
* lock, but that would extend the time we spend in the globally-locked
2942+
* state.
2943+
*
2944+
* So instead, we accept the race for now.
2945+
*/
2946+
if (refs_verify_refnames_available(refs->packed_ref_store, &refnames_to_check,
2947+
&affected_refnames, NULL, 0, err)) {
2948+
ret = TRANSACTION_NAME_CONFLICT;
2949+
goto cleanup;
2950+
}
2951+
29332952
if (packed_transaction) {
29342953
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
29352954
ret = TRANSACTION_GENERIC_ERROR;
@@ -2972,6 +2991,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
29722991
cleanup:
29732992
free(head_ref);
29742993
string_list_clear(&affected_refnames, 0);
2994+
string_list_clear(&refnames_to_check, 0);
29752995

29762996
if (ret)
29772997
files_transaction_cleanup(refs, transaction);

0 commit comments

Comments
 (0)