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 <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Patrick Steinhardt committed
Mar 7, 2024 at 14:10 UTC
3a60f6a2c472e8291894308247a8fecb0c76cb51
2 files changed
+123
-134
reftable/stack.c
+121
-134
@@ -978,212 +978,199 @@ static int stack_compact_range(struct reftable_stack *st,
978
size_t first, size_t last,
979
struct reftable_log_expiry_config *expiry)
980
{
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;
983
struct strbuf new_table_name = STRBUF_INIT;
984
- struct strbuf lock_file_name = STRBUF_INIT;
985
- struct strbuf ref_list_contents = STRBUF_INIT;
984
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;
990
991
if (first > last || (!expiry && first == last)) {
992
err = 0;
993
goto done;
994
}
995
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
-
996
st->stats.attempts++;
997
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)
1006
err = 1;
1013
- } else {
1007
+ else
1008
err = REFTABLE_IO_ERROR;
1015
- }
1009
goto done;
1010
}
1018
- /* Don't want to write to the lock for now. */
1019
- close(lock_file_fd);
1020
- lock_file_fd = -1;
1011
1022
- have_lock = 1;
1012
err = stack_uptodate(st);
1024
- if (err != 0)
1013
+ if (err)
1014
goto done;
1015
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]));
1023
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)
1028
err = 1;
1046
- } else {
1029
+ else
1030
err = REFTABLE_IO_ERROR;
1048
- }
1031
+ goto done;
1032
}
1033
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;
1042
goto done;
1043
+ }
1044
}
1045
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;
1054
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;
1055
}
1072
- if (err < 0)
1073
- goto done;
1056
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)
1078
err = 1;
1080
- } else {
1079
+ else
1080
err = REFTABLE_IO_ERROR;
1082
- }
1081
goto done;
1082
}
1085
- have_lock = 1;
1083
+
1084
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) {
1087
err = REFTABLE_IO_ERROR;
1088
goto done;
1089
}
1090
}
1091
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
+ */
1096
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);
1103
if (err < 0) {
1104
err = REFTABLE_IO_ERROR;
1105
goto done;
1106
}
1107
}
1108
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);
1123
if (err < 0) {
1124
err = REFTABLE_IO_ERROR;
1125
unlink(new_table_path.buf);
1126
goto done;
1127
}
1128
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));
1130
if (err < 0) {
1131
err = REFTABLE_IO_ERROR;
1132
unlink(new_table_path.buf);
1133
goto done;
1134
}
1135
1143
- err = rename(lock_file_name.buf, st->list_file);
1136
+ err = commit_lock_file(&tables_list_lock);
1137
if (err < 0) {
1138
err = REFTABLE_IO_ERROR;
1139
unlink(new_table_path.buf);
1140
goto done;
1141
}
1149
- have_lock = 0;
1142
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
+ */
1148
err = reftable_stack_reload_maybe_reuse(st, first < last);
1149
+ if (err < 0)
1150
+ goto done;
1151
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);
1161
}
1162
1163
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);
1168
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
- }
1169
strbuf_release(&new_table_name);
1170
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);
1174
return err;
1175
}
1176
reftable/system.h
+2
@@ -12,7 +12,9 @@ https://developers.google.com/open-source/licenses/bsd
12
/* This header glues the reftable library to the rest of Git */
13
14
#include "git-compat-util.h"
15
+#include "lockfile.h"
16
#include "strbuf.h"
17
+#include "tempfile.h"
18
#include "hash-ll.h" /* hash ID, sizes.*/
19
#include "dir.h" /* remove_dir_recursively, for tests.*/
20