Skip to content

Commit cf018ee

Browse files
mhaggergitster
authored andcommitted
ref_transaction_commit(): fix atomicity and avoid fd exhaustion
The old code was roughly for update in updates: acquire locks and check old_sha for update in updates: if changing value: write_ref_to_lockfile() commit_ref_update() for update in updates: if deleting value: unlink() rewrite packed-refs file for update in updates: if reference still locked: unlock_ref() This has two problems. Non-atomic updates ================== The atomicity of the reference transaction depends on all pre-checks being done in the first loop, before any changes have started being committed in the second loop. The problem is that write_ref_to_lockfile() (previously part of write_ref_sha1()), which is called from the second loop, contains two more checks: * It verifies that new_sha1 is a valid object * If the reference being updated is a branch, it verifies that new_sha1 points at a commit object (as opposed to a tag, tree, or blob). If either of these checks fails, the "transaction" is aborted during the second loop. But this might happen after some reference updates have already been permanently committed. In other words, the all-or-nothing promise of "git update-ref --stdin" could be violated. So these checks have to be moved to the first loop. File descriptor exhaustion ========================== The old code locked all of the references in the first loop, leaving all of the lockfiles open until later loops. Since we might be updating a lot of references, this could result in file descriptor exhaustion. The solution ============ After this patch, the code looks like for update in updates: acquire locks and check old_sha if changing value: write_ref_to_lockfile() else: close_ref() for update in updates: if changing value: commit_ref_update() for update in updates: if deleting value: unlink() rewrite packed-refs file for update in updates: if reference still locked: unlock_ref() This fixes both problems: 1. The pre-checks in write_ref_to_lockfile() are now done in the first loop, before any changes have been committed. If any of the checks fails, the whole transaction can now be rolled back correctly. 2. All lockfiles are closed in the first loop immediately after they are created (either by write_ref_to_lockfile() or by close_ref()). This means that there is never more than one open lockfile at a time, preventing file descriptor exhaustion. To simplify the bookkeeping across loops, add a new REF_NEEDS_COMMIT bit to update->flags, which keeps track of whether the corresponding lockfile needs to be committed, as opposed to just unlocked. (Since "struct ref_update" is internal to the refs module, this change is not visible to external callers.) This change fixes two tests in t1400. Signed-off-by: Michael Haggerty <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent cbf50f9 commit cf018ee

File tree

2 files changed

+55
-23
lines changed

2 files changed

+55
-23
lines changed

refs.c

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ static unsigned char refname_disposition[256] = {
5757
*/
5858
#define REF_HAVE_OLD 0x10
5959

60+
/*
61+
* Used as a flag in ref_update::flags when the lockfile needs to be
62+
* committed.
63+
*/
64+
#define REF_NEEDS_COMMIT 0x20
65+
6066
/*
6167
* Try to read one refname component from the front of refname.
6268
* Return the length of the component found, or -1 if the component is
@@ -3760,7 +3766,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,
37603766
goto cleanup;
37613767
}
37623768

3763-
/* Acquire all locks while verifying old values */
3769+
/*
3770+
* Acquire all locks, verify old values if provided, check
3771+
* that new values are valid, and write new values to the
3772+
* lockfiles, ready to be activated. Only keep one lockfile
3773+
* open at a time to avoid running out of file descriptors.
3774+
*/
37643775
for (i = 0; i < n; i++) {
37653776
struct ref_update *update = updates[i];
37663777

@@ -3782,38 +3793,60 @@ int ref_transaction_commit(struct ref_transaction *transaction,
37823793
update->refname);
37833794
goto cleanup;
37843795
}
3785-
}
3786-
3787-
/* Perform updates first so live commits remain referenced */
3788-
for (i = 0; i < n; i++) {
3789-
struct ref_update *update = updates[i];
3790-
3791-
if ((update->flags & REF_HAVE_NEW)
3792-
&& !is_null_sha1(update->new_sha1)) {
3796+
if ((update->flags & REF_HAVE_NEW) &&
3797+
!(update->flags & REF_DELETING)) {
37933798
int overwriting_symref = ((update->type & REF_ISSYMREF) &&
37943799
(update->flags & REF_NODEREF));
37953800

3796-
if (!overwriting_symref
3797-
&& !hashcmp(update->lock->old_sha1, update->new_sha1)) {
3801+
if (!overwriting_symref &&
3802+
!hashcmp(update->lock->old_sha1, update->new_sha1)) {
37983803
/*
37993804
* The reference already has the desired
38003805
* value, so we don't need to write it.
38013806
*/
3802-
unlock_ref(update->lock);
3803-
update->lock = NULL;
38043807
} else if (write_ref_to_lockfile(update->lock,
3805-
update->new_sha1) ||
3806-
commit_ref_update(update->lock,
3807-
update->new_sha1,
3808-
update->msg)) {
3809-
/* freed by one of the above calls: */
3808+
update->new_sha1)) {
3809+
/*
3810+
* The lock was freed upon failure of
3811+
* write_ref_to_lockfile():
3812+
*/
3813+
update->lock = NULL;
3814+
strbuf_addf(err, "Cannot update the ref '%s'.",
3815+
update->refname);
3816+
ret = TRANSACTION_GENERIC_ERROR;
3817+
goto cleanup;
3818+
} else {
3819+
update->flags |= REF_NEEDS_COMMIT;
3820+
}
3821+
}
3822+
if (!(update->flags & REF_NEEDS_COMMIT)) {
3823+
/*
3824+
* We didn't have to write anything to the lockfile.
3825+
* Close it to free up the file descriptor:
3826+
*/
3827+
if (close_ref(update->lock)) {
3828+
strbuf_addf(err, "Couldn't close %s.lock",
3829+
update->refname);
3830+
goto cleanup;
3831+
}
3832+
}
3833+
}
3834+
3835+
/* Perform updates first so live commits remain referenced */
3836+
for (i = 0; i < n; i++) {
3837+
struct ref_update *update = updates[i];
3838+
3839+
if (update->flags & REF_NEEDS_COMMIT) {
3840+
if (commit_ref_update(update->lock,
3841+
update->new_sha1, update->msg)) {
3842+
/* freed by commit_ref_update(): */
38103843
update->lock = NULL;
38113844
strbuf_addf(err, "Cannot update the ref '%s'.",
38123845
update->refname);
38133846
ret = TRANSACTION_GENERIC_ERROR;
38143847
goto cleanup;
38153848
} else {
3816-
/* freed by write_ref_sha1(): */
3849+
/* freed by commit_ref_update(): */
38173850
update->lock = NULL;
38183851
}
38193852
}
@@ -3823,8 +3856,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
38233856
for (i = 0; i < n; i++) {
38243857
struct ref_update *update = updates[i];
38253858

3826-
if ((update->flags & REF_HAVE_NEW)
3827-
&& is_null_sha1(update->new_sha1)) {
3859+
if (update->flags & REF_DELETING) {
38283860
if (delete_ref_loose(update->lock, update->type, err)) {
38293861
ret = TRANSACTION_GENERIC_ERROR;
38303862
goto cleanup;

t/t1400-update-ref.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,7 @@ run_with_limited_open_files () {
10711071

10721072
test_lazy_prereq ULIMIT_FILE_DESCRIPTORS 'run_with_limited_open_files true'
10731073

1074-
test_expect_failure ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
1074+
test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
10751075
(
10761076
for i in $(test_seq 33)
10771077
do
@@ -1082,7 +1082,7 @@ test_expect_failure ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches
10821082
)
10831083
'
10841084

1085-
test_expect_failure ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches does not burst open file limit' '
1085+
test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches does not burst open file limit' '
10861086
(
10871087
for i in $(test_seq 33)
10881088
do

0 commit comments

Comments
 (0)