Skip to content

Commit 3a60f6a

Browse files
pks-tgitster
authored andcommitted
reftable/stack: register lockfiles during compaction
We do not register any of the locks we acquire when compacting the reftable stack via our lockfiles interfaces. These locks will thus not be released when Git gets killed. Refactor the code to register locks as lockfiles. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1920d17 commit 3a60f6a

File tree

2 files changed

+123
-134
lines changed

2 files changed

+123
-134
lines changed

reftable/stack.c

Lines changed: 121 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -978,212 +978,199 @@ static int stack_compact_range(struct reftable_stack *st,
978978
size_t first, size_t last,
979979
struct reftable_log_expiry_config *expiry)
980980
{
981-
char **delete_on_success = NULL, **subtable_locks = NULL, **listp = NULL;
982-
struct strbuf temp_tab_file_name = STRBUF_INIT;
981+
struct strbuf tables_list_buf = STRBUF_INIT;
982+
struct strbuf new_table_temp_path = STRBUF_INIT;
983983
struct strbuf new_table_name = STRBUF_INIT;
984-
struct strbuf lock_file_name = STRBUF_INIT;
985-
struct strbuf ref_list_contents = STRBUF_INIT;
986984
struct strbuf new_table_path = STRBUF_INIT;
987-
size_t i, j, compact_count;
988-
int err = 0;
989-
int have_lock = 0;
990-
int lock_file_fd = -1;
991-
int is_empty_table = 0;
985+
struct strbuf table_name = STRBUF_INIT;
986+
struct lock_file tables_list_lock = LOCK_INIT;
987+
struct lock_file *table_locks = NULL;
988+
int is_empty_table = 0, err = 0;
989+
size_t i;
992990

993991
if (first > last || (!expiry && first == last)) {
994992
err = 0;
995993
goto done;
996994
}
997995

998-
compact_count = last - first + 1;
999-
REFTABLE_CALLOC_ARRAY(delete_on_success, compact_count + 1);
1000-
REFTABLE_CALLOC_ARRAY(subtable_locks, compact_count + 1);
1001-
1002996
st->stats.attempts++;
1003997

1004-
strbuf_reset(&lock_file_name);
1005-
strbuf_addstr(&lock_file_name, st->list_file);
1006-
strbuf_addstr(&lock_file_name, ".lock");
1007-
1008-
lock_file_fd =
1009-
open(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0666);
1010-
if (lock_file_fd < 0) {
1011-
if (errno == EEXIST) {
998+
/*
999+
* Hold the lock so that we can read "tables.list" and lock all tables
1000+
* which are part of the user-specified range.
1001+
*/
1002+
err = hold_lock_file_for_update(&tables_list_lock, st->list_file,
1003+
LOCK_NO_DEREF);
1004+
if (err < 0) {
1005+
if (errno == EEXIST)
10121006
err = 1;
1013-
} else {
1007+
else
10141008
err = REFTABLE_IO_ERROR;
1015-
}
10161009
goto done;
10171010
}
1018-
/* Don't want to write to the lock for now. */
1019-
close(lock_file_fd);
1020-
lock_file_fd = -1;
10211011

1022-
have_lock = 1;
10231012
err = stack_uptodate(st);
1024-
if (err != 0)
1013+
if (err)
10251014
goto done;
10261015

1027-
for (i = first, j = 0; i <= last; i++) {
1028-
struct strbuf subtab_file_name = STRBUF_INIT;
1029-
struct strbuf subtab_lock = STRBUF_INIT;
1030-
int sublock_file_fd = -1;
1031-
1032-
stack_filename(&subtab_file_name, st,
1033-
reader_name(st->readers[i]));
1034-
1035-
strbuf_reset(&subtab_lock);
1036-
strbuf_addbuf(&subtab_lock, &subtab_file_name);
1037-
strbuf_addstr(&subtab_lock, ".lock");
1016+
/*
1017+
* Lock all tables in the user-provided range. This is the slice of our
1018+
* stack which we'll compact.
1019+
*/
1020+
REFTABLE_CALLOC_ARRAY(table_locks, last - first + 1);
1021+
for (i = first; i <= last; i++) {
1022+
stack_filename(&table_name, st, reader_name(st->readers[i]));
10381023

1039-
sublock_file_fd = open(subtab_lock.buf,
1040-
O_EXCL | O_CREAT | O_WRONLY, 0666);
1041-
if (sublock_file_fd >= 0) {
1042-
close(sublock_file_fd);
1043-
} else if (sublock_file_fd < 0) {
1044-
if (errno == EEXIST) {
1024+
err = hold_lock_file_for_update(&table_locks[i - first],
1025+
table_name.buf, LOCK_NO_DEREF);
1026+
if (err < 0) {
1027+
if (errno == EEXIST)
10451028
err = 1;
1046-
} else {
1029+
else
10471030
err = REFTABLE_IO_ERROR;
1048-
}
1031+
goto done;
10491032
}
10501033

1051-
subtable_locks[j] = subtab_lock.buf;
1052-
delete_on_success[j] = subtab_file_name.buf;
1053-
j++;
1054-
1055-
if (err != 0)
1034+
/*
1035+
* We need to close the lockfiles as we might otherwise easily
1036+
* run into file descriptor exhaustion when we compress a lot
1037+
* of tables.
1038+
*/
1039+
err = close_lock_file_gently(&table_locks[i - first]);
1040+
if (err < 0) {
1041+
err = REFTABLE_IO_ERROR;
10561042
goto done;
1043+
}
10571044
}
10581045

1059-
err = unlink(lock_file_name.buf);
1060-
if (err < 0)
1046+
/*
1047+
* We have locked all tables in our range and can thus release the
1048+
* "tables.list" lock while compacting the locked tables. This allows
1049+
* concurrent updates to the stack to proceed.
1050+
*/
1051+
err = rollback_lock_file(&tables_list_lock);
1052+
if (err < 0) {
1053+
err = REFTABLE_IO_ERROR;
10611054
goto done;
1062-
have_lock = 0;
1063-
1064-
err = stack_compact_locked(st, first, last, &temp_tab_file_name,
1065-
expiry);
1066-
/* Compaction + tombstones can create an empty table out of non-empty
1067-
* tables. */
1068-
is_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);
1069-
if (is_empty_table) {
1070-
err = 0;
10711055
}
1072-
if (err < 0)
1073-
goto done;
10741056

1075-
lock_file_fd =
1076-
open(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0666);
1077-
if (lock_file_fd < 0) {
1078-
if (errno == EEXIST) {
1057+
/*
1058+
* Compact the now-locked tables into a new table. Note that compacting
1059+
* these tables may end up with an empty new table in case tombstones
1060+
* end up cancelling out all refs in that range.
1061+
*/
1062+
err = stack_compact_locked(st, first, last, &new_table_temp_path, expiry);
1063+
if (err < 0) {
1064+
if (err != REFTABLE_EMPTY_TABLE_ERROR)
1065+
goto done;
1066+
is_empty_table = 1;
1067+
}
1068+
1069+
/*
1070+
* Now that we have written the new, compacted table we need to re-lock
1071+
* "tables.list". We'll then replace the compacted range of tables with
1072+
* the new table.
1073+
*/
1074+
err = hold_lock_file_for_update(&tables_list_lock, st->list_file,
1075+
LOCK_NO_DEREF);
1076+
if (err < 0) {
1077+
if (errno == EEXIST)
10791078
err = 1;
1080-
} else {
1079+
else
10811080
err = REFTABLE_IO_ERROR;
1082-
}
10831081
goto done;
10841082
}
1085-
have_lock = 1;
1083+
10861084
if (st->config.default_permissions) {
1087-
if (chmod(lock_file_name.buf, st->config.default_permissions) < 0) {
1085+
if (chmod(get_lock_file_path(&tables_list_lock),
1086+
st->config.default_permissions) < 0) {
10881087
err = REFTABLE_IO_ERROR;
10891088
goto done;
10901089
}
10911090
}
10921091

1093-
format_name(&new_table_name, st->readers[first]->min_update_index,
1094-
st->readers[last]->max_update_index);
1095-
strbuf_addstr(&new_table_name, ".ref");
1096-
1097-
stack_filename(&new_table_path, st, new_table_name.buf);
1098-
1092+
/*
1093+
* If the resulting compacted table is not empty, then we need to move
1094+
* it into place now.
1095+
*/
10991096
if (!is_empty_table) {
1100-
/* retry? */
1101-
err = rename(temp_tab_file_name.buf, new_table_path.buf);
1097+
format_name(&new_table_name, st->readers[first]->min_update_index,
1098+
st->readers[last]->max_update_index);
1099+
strbuf_addstr(&new_table_name, ".ref");
1100+
stack_filename(&new_table_path, st, new_table_name.buf);
1101+
1102+
err = rename(new_table_temp_path.buf, new_table_path.buf);
11021103
if (err < 0) {
11031104
err = REFTABLE_IO_ERROR;
11041105
goto done;
11051106
}
11061107
}
11071108

1108-
for (i = 0; i < first; i++) {
1109-
strbuf_addstr(&ref_list_contents, st->readers[i]->name);
1110-
strbuf_addstr(&ref_list_contents, "\n");
1111-
}
1112-
if (!is_empty_table) {
1113-
strbuf_addbuf(&ref_list_contents, &new_table_name);
1114-
strbuf_addstr(&ref_list_contents, "\n");
1115-
}
1116-
for (i = last + 1; i < st->merged->stack_len; i++) {
1117-
strbuf_addstr(&ref_list_contents, st->readers[i]->name);
1118-
strbuf_addstr(&ref_list_contents, "\n");
1119-
}
1120-
1121-
err = write_in_full(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
1122-
if (err < 0) {
1123-
err = REFTABLE_IO_ERROR;
1124-
unlink(new_table_path.buf);
1125-
goto done;
1126-
}
1127-
1128-
err = fsync_component(FSYNC_COMPONENT_REFERENCE, lock_file_fd);
1109+
/*
1110+
* Write the new "tables.list" contents with the compacted table we
1111+
* have just written. In case the compacted table became empty we
1112+
* simply skip writing it.
1113+
*/
1114+
for (i = 0; i < first; i++)
1115+
strbuf_addf(&tables_list_buf, "%s\n", st->readers[i]->name);
1116+
if (!is_empty_table)
1117+
strbuf_addf(&tables_list_buf, "%s\n", new_table_name.buf);
1118+
for (i = last + 1; i < st->merged->stack_len; i++)
1119+
strbuf_addf(&tables_list_buf, "%s\n", st->readers[i]->name);
1120+
1121+
err = write_in_full(get_lock_file_fd(&tables_list_lock),
1122+
tables_list_buf.buf, tables_list_buf.len);
11291123
if (err < 0) {
11301124
err = REFTABLE_IO_ERROR;
11311125
unlink(new_table_path.buf);
11321126
goto done;
11331127
}
11341128

1135-
err = close(lock_file_fd);
1136-
lock_file_fd = -1;
1129+
err = fsync_component(FSYNC_COMPONENT_REFERENCE, get_lock_file_fd(&tables_list_lock));
11371130
if (err < 0) {
11381131
err = REFTABLE_IO_ERROR;
11391132
unlink(new_table_path.buf);
11401133
goto done;
11411134
}
11421135

1143-
err = rename(lock_file_name.buf, st->list_file);
1136+
err = commit_lock_file(&tables_list_lock);
11441137
if (err < 0) {
11451138
err = REFTABLE_IO_ERROR;
11461139
unlink(new_table_path.buf);
11471140
goto done;
11481141
}
1149-
have_lock = 0;
11501142

1151-
/* Reload the stack before deleting. On windows, we can only delete the
1152-
files after we closed them.
1153-
*/
1143+
/*
1144+
* Reload the stack before deleting the compacted tables. We can only
1145+
* delete the files after we closed them on Windows, so this needs to
1146+
* happen first.
1147+
*/
11541148
err = reftable_stack_reload_maybe_reuse(st, first < last);
1149+
if (err < 0)
1150+
goto done;
11551151

1156-
listp = delete_on_success;
1157-
while (*listp) {
1158-
if (strcmp(*listp, new_table_path.buf)) {
1159-
unlink(*listp);
1160-
}
1161-
listp++;
1152+
/*
1153+
* Delete the old tables. They may still be in use by concurrent
1154+
* readers, so it is expected that unlinking tables may fail.
1155+
*/
1156+
for (i = first; i <= last; i++) {
1157+
struct lock_file *table_lock = &table_locks[i - first];
1158+
char *table_path = get_locked_file_path(table_lock);
1159+
unlink(table_path);
1160+
free(table_path);
11621161
}
11631162

11641163
done:
1165-
free_names(delete_on_success);
1164+
rollback_lock_file(&tables_list_lock);
1165+
for (i = first; table_locks && i <= last; i++)
1166+
rollback_lock_file(&table_locks[i - first]);
1167+
reftable_free(table_locks);
11661168

1167-
if (subtable_locks) {
1168-
listp = subtable_locks;
1169-
while (*listp) {
1170-
unlink(*listp);
1171-
listp++;
1172-
}
1173-
free_names(subtable_locks);
1174-
}
1175-
if (lock_file_fd >= 0) {
1176-
close(lock_file_fd);
1177-
lock_file_fd = -1;
1178-
}
1179-
if (have_lock) {
1180-
unlink(lock_file_name.buf);
1181-
}
11821169
strbuf_release(&new_table_name);
11831170
strbuf_release(&new_table_path);
1184-
strbuf_release(&ref_list_contents);
1185-
strbuf_release(&temp_tab_file_name);
1186-
strbuf_release(&lock_file_name);
1171+
strbuf_release(&new_table_temp_path);
1172+
strbuf_release(&tables_list_buf);
1173+
strbuf_release(&table_name);
11871174
return err;
11881175
}
11891176

reftable/system.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ license that can be found in the LICENSE file or at
1212
/* This header glues the reftable library to the rest of Git */
1313

1414
#include "git-compat-util.h"
15+
#include "lockfile.h"
1516
#include "strbuf.h"
17+
#include "tempfile.h"
1618
#include "hash-ll.h" /* hash ID, sizes.*/
1719
#include "dir.h" /* remove_dir_recursively, for tests.*/
1820

0 commit comments

Comments
 (0)