refs/files: add count field to ref_lock

When refs are updated in the files-backend, a lock is obtained for the corresponding file path. This is the case even for reflogs, i.e. a lock is obtained on the reference path instead of the reflog path. This works, since generally, reflogs are updated alongside the ref. The upcoming patches will add support for reflog updates in ref transaction. This means, in a particular transaction we want to have ref updates and reflog updates. For a given ref in a given transaction there can be at most one update. But we can theoretically have multiple reflog updates for a given ref in a given transaction. A great example of this would be when migrating reflogs from one backend to another. There we would batch all the reflog updates for a given reference in a single transaction. The current flow does not support this, because currently refs & reflogs are treated as a single entity and capture the lock together. To separate this, add a count field to ref_lock. With this, multiple updates can hold onto a single ref_lock and the lock will only be released when all of them release the lock. This patch only adds the `count` field to `ref_lock` and adds the logic to increment and decrement the lock. In a follow up commit, we'll separate the reflog update logic from ref updates and utilize this functionality. Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Dec 16, 2024 at 17:44 UTC 611986f3007d591731f56ad04cf3e00f0d2f163f
1 file changed +39 -19
refs/files-backend.c
+39 -19
@@ -71,6 +71,7 @@ struct ref_lock {
71 char *ref_name;
72 struct lock_file lk;
73 struct object_id old_oid;
74 + unsigned int count; /* track users of the lock (ref update + reflog updates) */
75 };
76
77 struct files_ref_store {
@@ -638,9 +639,12 @@ int parse_loose_ref_contents(const struct git_hash_algo *algop,
639
640 static void unlock_ref(struct ref_lock *lock)
641 {
641 - rollback_lock_file(&lock->lk);
642 - free(lock->ref_name);
643 - free(lock);
642 + lock->count--;
643 + if (!lock->count) {
644 + rollback_lock_file(&lock->lk);
645 + free(lock->ref_name);
646 + free(lock);
647 + }
648 }
649
650 /*
@@ -696,6 +700,7 @@ static int lock_raw_ref(struct files_ref_store *refs,
700 *lock_p = CALLOC_ARRAY(lock, 1);
701
702 lock->ref_name = xstrdup(refname);
703 + lock->count = 1;
704 files_ref_path(refs, &ref_file, refname);
705
706 retry:
@@ -1169,6 +1174,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
1174 goto error_return;
1175
1176 lock->ref_name = xstrdup(refname);
1177 + lock->count = 1;
1178
1179 if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) {
1180 unable_to_lock_message(ref_file.buf, errno, err);
@@ -2535,6 +2541,12 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
2541 return -1;
2542 }
2543
2544 +struct files_transaction_backend_data {
2545 + struct ref_transaction *packed_transaction;
2546 + int packed_refs_locked;
2547 + struct strmap ref_locks;
2548 +};
2549 +
2550 /*
2551 * Prepare for carrying out update:
2552 * - Lock the reference referred to by update.
@@ -2557,11 +2569,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,
2569 {
2570 struct strbuf referent = STRBUF_INIT;
2571 int mustexist = ref_update_expects_existing_old_ref(update);
2572 + struct files_transaction_backend_data *backend_data;
2573 int ret = 0;
2574 struct ref_lock *lock;
2575
2576 files_assert_main_repository(refs, "lock_ref_for_update");
2577
2578 + backend_data = transaction->backend_data;
2579 +
2580 if ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))
2581 update->flags |= REF_DELETING;
2582
@@ -2572,18 +2587,25 @@ static int lock_ref_for_update(struct files_ref_store *refs,
2587 goto out;
2588 }
2589
2575 - ret = lock_raw_ref(refs, update->refname, mustexist,
2576 - affected_refnames,
2577 - &lock, &referent,
2578 - &update->type, err);
2579 - if (ret) {
2580 - char *reason;
2590 + lock = strmap_get(&backend_data->ref_locks, update->refname);
2591 + if (lock) {
2592 + lock->count++;
2593 + } else {
2594 + ret = lock_raw_ref(refs, update->refname, mustexist,
2595 + affected_refnames,
2596 + &lock, &referent,
2597 + &update->type, err);
2598 + if (ret) {
2599 + char *reason;
2600 +
2601 + reason = strbuf_detach(err, NULL);
2602 + strbuf_addf(err, "cannot lock ref '%s': %s",
2603 + ref_update_original_update_refname(update), reason);
2604 + free(reason);
2605 + goto out;
2606 + }
2607
2582 - reason = strbuf_detach(err, NULL);
2583 - strbuf_addf(err, "cannot lock ref '%s': %s",
2584 - ref_update_original_update_refname(update), reason);
2585 - free(reason);
2586 - goto out;
2608 + strmap_put(&backend_data->ref_locks, update->refname, lock);
2609 }
2610
2611 update->backend_data = lock;
@@ -2730,11 +2752,6 @@ out:
2752 return ret;
2753 }
2754
2733 -struct files_transaction_backend_data {
2734 - struct ref_transaction *packed_transaction;
2735 - int packed_refs_locked;
2736 -};
2737 -
2755 /*
2756 * Unlock any references in `transaction` that are still locked, and
2757 * mark the transaction closed.
@@ -2767,6 +2784,8 @@ static void files_transaction_cleanup(struct files_ref_store *refs,
2784 if (backend_data->packed_refs_locked)
2785 packed_refs_unlock(refs->packed_ref_store);
2786
2787 + strmap_clear(&backend_data->ref_locks, 0);
2788 +
2789 free(backend_data);
2790 }
2791
@@ -2796,6 +2815,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
2815 goto cleanup;
2816
2817 CALLOC_ARRAY(backend_data, 1);
2818 + strmap_init(&backend_data->ref_locks);
2819 transaction->backend_data = backend_data;
2820
2821 /*