Skip to content

Commit 91c9087

Browse files
committed
Merge branch 'mh/write-refs-sooner-2.4'
Multi-ref transaction support we merged a few releases ago unnecessarily kept many file descriptors open, risking to fail with resource exhaustion. This is for 2.4.x track. * mh/write-refs-sooner-2.4: ref_transaction_commit(): fix atomicity and avoid fd exhaustion ref_transaction_commit(): remove the local flags variable ref_transaction_commit(): inline call to write_ref_sha1() rename_ref(): inline calls to write_ref_sha1() from this function commit_ref_update(): new function, extracted from write_ref_sha1() write_ref_to_lockfile(): new function, extracted from write_ref_sha1() t7004: rename ULIMIT test prerequisite to ULIMIT_STACK_SIZE update-ref: test handling large transactions properly ref_transaction_commit(): fix atomicity and avoid fd exhaustion ref_transaction_commit(): remove the local flags variable ref_transaction_commit(): inline call to write_ref_sha1() rename_ref(): inline calls to write_ref_sha1() from this function commit_ref_update(): new function, extracted from write_ref_sha1() write_ref_to_lockfile(): new function, extracted from write_ref_sha1() t7004: rename ULIMIT test prerequisite to ULIMIT_STACK_SIZE update-ref: test handling large transactions properly
2 parents fd70780 + 185ce3a commit 91c9087

File tree

3 files changed

+112
-33
lines changed

3 files changed

+112
-33
lines changed

refs.c

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

59+
/*
60+
* Used as a flag in ref_update::flags when the lockfile needs to be
61+
* committed.
62+
*/
63+
#define REF_NEEDS_COMMIT 0x20
64+
5965
/*
6066
* Try to read one refname component from the front of refname.
6167
* Return the length of the component found, or -1 if the component is
@@ -2788,8 +2794,9 @@ static int rename_ref_available(const char *oldname, const char *newname)
27882794
return ret;
27892795
}
27902796

2791-
static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1,
2792-
const char *logmsg);
2797+
static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1);
2798+
static int commit_ref_update(struct ref_lock *lock,
2799+
const unsigned char *sha1, const char *logmsg);
27932800

27942801
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
27952802
{
@@ -2847,7 +2854,9 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
28472854
goto rollback;
28482855
}
28492856
hashcpy(lock->old_sha1, orig_sha1);
2850-
if (write_ref_sha1(lock, orig_sha1, logmsg)) {
2857+
2858+
if (write_ref_to_lockfile(lock, orig_sha1) ||
2859+
commit_ref_update(lock, orig_sha1, logmsg)) {
28512860
error("unable to write current sha1 into %s", newrefname);
28522861
goto rollback;
28532862
}
@@ -2863,7 +2872,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
28632872

28642873
flag = log_all_ref_updates;
28652874
log_all_ref_updates = 0;
2866-
if (write_ref_sha1(lock, orig_sha1, NULL))
2875+
if (write_ref_to_lockfile(lock, orig_sha1) ||
2876+
commit_ref_update(lock, orig_sha1, NULL))
28672877
error("unable to write current sha1 into %s", oldrefname);
28682878
log_all_ref_updates = flag;
28692879

@@ -3052,11 +3062,11 @@ int is_branch(const char *refname)
30523062
}
30533063

30543064
/*
3055-
* Write sha1 into the ref specified by the lock. Make sure that errno
3056-
* is sane on error.
3065+
* Write sha1 into the open lockfile, then close the lockfile. On
3066+
* errors, rollback the lockfile and set errno to reflect the problem.
30573067
*/
3058-
static int write_ref_sha1(struct ref_lock *lock,
3059-
const unsigned char *sha1, const char *logmsg)
3068+
static int write_ref_to_lockfile(struct ref_lock *lock,
3069+
const unsigned char *sha1)
30603070
{
30613071
static char term = '\n';
30623072
struct object *o;
@@ -3085,6 +3095,17 @@ static int write_ref_sha1(struct ref_lock *lock,
30853095
errno = save_errno;
30863096
return -1;
30873097
}
3098+
return 0;
3099+
}
3100+
3101+
/*
3102+
* Commit a change to a loose reference that has already been written
3103+
* to the loose reference lockfile. Also update the reflogs if
3104+
* necessary, using the specified lockmsg (which can be NULL).
3105+
*/
3106+
static int commit_ref_update(struct ref_lock *lock,
3107+
const unsigned char *sha1, const char *logmsg)
3108+
{
30883109
clear_loose_ref_cache(&ref_cache);
30893110
if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
30903111
(strcmp(lock->ref_name, lock->orig_ref_name) &&
@@ -3775,19 +3796,24 @@ int ref_transaction_commit(struct ref_transaction *transaction,
37753796
goto cleanup;
37763797
}
37773798

3778-
/* Acquire all locks while verifying old values */
3799+
/*
3800+
* Acquire all locks, verify old values if provided, check
3801+
* that new values are valid, and write new values to the
3802+
* lockfiles, ready to be activated. Only keep one lockfile
3803+
* open at a time to avoid running out of file descriptors.
3804+
*/
37793805
for (i = 0; i < n; i++) {
37803806
struct ref_update *update = updates[i];
3781-
unsigned int flags = update->flags;
37823807

3783-
if ((flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1))
3784-
flags |= REF_DELETING;
3808+
if ((update->flags & REF_HAVE_NEW) &&
3809+
is_null_sha1(update->new_sha1))
3810+
update->flags |= REF_DELETING;
37853811
update->lock = lock_ref_sha1_basic(
37863812
update->refname,
37873813
((update->flags & REF_HAVE_OLD) ?
37883814
update->old_sha1 : NULL),
37893815
NULL,
3790-
flags,
3816+
update->flags,
37913817
&update->type);
37923818
if (!update->lock) {
37933819
ret = (errno == ENOTDIR)
@@ -3797,34 +3823,60 @@ int ref_transaction_commit(struct ref_transaction *transaction,
37973823
update->refname);
37983824
goto cleanup;
37993825
}
3800-
}
3801-
3802-
/* Perform updates first so live commits remain referenced */
3803-
for (i = 0; i < n; i++) {
3804-
struct ref_update *update = updates[i];
3805-
int flags = update->flags;
3806-
3807-
if ((flags & REF_HAVE_NEW) && !is_null_sha1(update->new_sha1)) {
3826+
if ((update->flags & REF_HAVE_NEW) &&
3827+
!(update->flags & REF_DELETING)) {
38083828
int overwriting_symref = ((update->type & REF_ISSYMREF) &&
38093829
(update->flags & REF_NODEREF));
38103830

3811-
if (!overwriting_symref
3812-
&& !hashcmp(update->lock->old_sha1, update->new_sha1)) {
3831+
if (!overwriting_symref &&
3832+
!hashcmp(update->lock->old_sha1, update->new_sha1)) {
38133833
/*
38143834
* The reference already has the desired
38153835
* value, so we don't need to write it.
38163836
*/
3817-
unlock_ref(update->lock);
3837+
} else if (write_ref_to_lockfile(update->lock,
3838+
update->new_sha1)) {
3839+
/*
3840+
* The lock was freed upon failure of
3841+
* write_ref_to_lockfile():
3842+
*/
3843+
update->lock = NULL;
3844+
strbuf_addf(err, "Cannot update the ref '%s'.",
3845+
update->refname);
3846+
ret = TRANSACTION_GENERIC_ERROR;
3847+
goto cleanup;
3848+
} else {
3849+
update->flags |= REF_NEEDS_COMMIT;
3850+
}
3851+
}
3852+
if (!(update->flags & REF_NEEDS_COMMIT)) {
3853+
/*
3854+
* We didn't have to write anything to the lockfile.
3855+
* Close it to free up the file descriptor:
3856+
*/
3857+
if (close_ref(update->lock)) {
3858+
strbuf_addf(err, "Couldn't close %s.lock",
3859+
update->refname);
3860+
goto cleanup;
3861+
}
3862+
}
3863+
}
3864+
3865+
/* Perform updates first so live commits remain referenced */
3866+
for (i = 0; i < n; i++) {
3867+
struct ref_update *update = updates[i];
3868+
3869+
if (update->flags & REF_NEEDS_COMMIT) {
3870+
if (commit_ref_update(update->lock,
3871+
update->new_sha1, update->msg)) {
3872+
/* freed by commit_ref_update(): */
38183873
update->lock = NULL;
3819-
} else if (write_ref_sha1(update->lock, update->new_sha1,
3820-
update->msg)) {
3821-
update->lock = NULL; /* freed by write_ref_sha1 */
38223874
strbuf_addf(err, "Cannot update the ref '%s'.",
38233875
update->refname);
38243876
ret = TRANSACTION_GENERIC_ERROR;
38253877
goto cleanup;
38263878
} else {
3827-
/* freed by write_ref_sha1(): */
3879+
/* freed by commit_ref_update(): */
38283880
update->lock = NULL;
38293881
}
38303882
}
@@ -3833,15 +3885,14 @@ int ref_transaction_commit(struct ref_transaction *transaction,
38333885
/* Perform deletes now that updates are safely completed */
38343886
for (i = 0; i < n; i++) {
38353887
struct ref_update *update = updates[i];
3836-
int flags = update->flags;
38373888

3838-
if ((flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) {
3889+
if (update->flags & REF_DELETING) {
38393890
if (delete_ref_loose(update->lock, update->type, err)) {
38403891
ret = TRANSACTION_GENERIC_ERROR;
38413892
goto cleanup;
38423893
}
38433894

3844-
if (!(flags & REF_ISPRUNING))
3895+
if (!(update->flags & REF_ISPRUNING))
38453896
string_list_append(&refs_to_delete,
38463897
update->lock->ref_name);
38473898
}

t/t1400-update-ref.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,4 +1065,32 @@ test_expect_success 'stdin -z delete refs works with packed and loose refs' '
10651065
test_must_fail git rev-parse --verify -q $c
10661066
'
10671067

1068+
run_with_limited_open_files () {
1069+
(ulimit -n 32 && "$@")
1070+
}
1071+
1072+
test_lazy_prereq ULIMIT_FILE_DESCRIPTORS 'run_with_limited_open_files true'
1073+
1074+
test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
1075+
(
1076+
for i in $(test_seq 33)
1077+
do
1078+
echo "create refs/heads/$i HEAD"
1079+
done >large_input &&
1080+
run_with_limited_open_files git update-ref --stdin <large_input &&
1081+
git rev-parse --verify -q refs/heads/33
1082+
)
1083+
'
1084+
1085+
test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches does not burst open file limit' '
1086+
(
1087+
for i in $(test_seq 33)
1088+
do
1089+
echo "delete refs/heads/$i HEAD"
1090+
done >large_input &&
1091+
run_with_limited_open_files git update-ref --stdin <large_input &&
1092+
test_must_fail git rev-parse --verify -q refs/heads/33
1093+
)
1094+
'
1095+
10681096
test_done

t/t7004-tag.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,10 +1491,10 @@ run_with_limited_stack () {
14911491
(ulimit -s 128 && "$@")
14921492
}
14931493

1494-
test_lazy_prereq ULIMIT 'run_with_limited_stack true'
1494+
test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true'
14951495

14961496
# we require ulimit, this excludes Windows
1497-
test_expect_success ULIMIT '--contains works in a deep repo' '
1497+
test_expect_success ULIMIT_STACK_SIZE '--contains works in a deep repo' '
14981498
>expect &&
14991499
i=1 &&
15001500
while test $i -lt 8000

0 commit comments

Comments
 (0)