Skip to content

Commit b1fc9da

Browse files
matheustavaresgitster
authored andcommitted
replace-object: make replace operations thread-safe
replace-object functions are very close to being thread-safe: the only current racy section is the lazy initialization at prepare_replace_object(). The following patches will protect some object reading operations to be called threaded, but before that, replace functions must be protected. To do so, add a mutex to struct raw_object_store and acquire it before lazy initializing the replace_map. This won't cause any noticeable performance drop as the mutex will no longer be used after the replace_map is initialized. Later, when the replace functions are called in parallel, thread debuggers might point our use of the added replace_map_initialized flag as a data race. However, as this boolean variable is initialized as false and it's only updated once, there's no real harm. It's perfectly fine if the value is updated right after a thread read it in replace-map.h:lookup_replace_object() (there'll only be a performance penalty for the affected threads at that moment). We could cease the debugger warning protecting the variable reading at the said function. However, this would negatively affect performance for all threads calling it, at any time, so it's not really worthy since the warning doesn't represent a real problem. Instead, to make sure we don't get false positives (at ThreadSanitizer, at least) an entry for the respective function is added to .tsan-suppressions. Signed-off-by: Matheus Tavares <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d5b0bac commit b1fc9da

5 files changed

+26
-2
lines changed

.tsan-suppressions

+6
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@
88
# in practice it (hopefully!) doesn't matter.
99
race:^want_color$
1010
race:^transfer_debug$
11+
12+
# A boolean value, which tells whether the replace_map has been initialized or
13+
# not, is read racily with an update. As this variable is written to only once,
14+
# and it's OK if the value change right after reading it, this shouldn't be a
15+
# problem.
16+
race:^lookup_replace_object$

object-store.h

+2
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ struct raw_object_store {
125125
* (see git-replace(1)).
126126
*/
127127
struct oidmap *replace_map;
128+
unsigned replace_map_initialized : 1;
129+
pthread_mutex_t replace_mutex; /* protect object replace functions */
128130

129131
struct commit_graph *commit_graph;
130132
unsigned commit_graph_attempted : 1; /* if loading has been attempted */

object.c

+2
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ struct raw_object_store *raw_object_store_new(void)
480480
memset(o, 0, sizeof(*o));
481481
INIT_LIST_HEAD(&o->packed_git_mru);
482482
hashmap_init(&o->pack_map, pack_map_entry_cmp, NULL, 0);
483+
pthread_mutex_init(&o->replace_mutex, NULL);
483484
return o;
484485
}
485486

@@ -507,6 +508,7 @@ void raw_object_store_clear(struct raw_object_store *o)
507508

508509
oidmap_free(o->replace_map, 1);
509510
FREE_AND_NULL(o->replace_map);
511+
pthread_mutex_destroy(&o->replace_mutex);
510512

511513
free_commit_graph(o->commit_graph);
512514
o->commit_graph = NULL;

replace-object.c

+10-1
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,23 @@ static int register_replace_ref(struct repository *r,
3434

3535
void prepare_replace_object(struct repository *r)
3636
{
37-
if (r->objects->replace_map)
37+
if (r->objects->replace_map_initialized)
3838
return;
3939

40+
pthread_mutex_lock(&r->objects->replace_mutex);
41+
if (r->objects->replace_map_initialized) {
42+
pthread_mutex_unlock(&r->objects->replace_mutex);
43+
return;
44+
}
45+
4046
r->objects->replace_map =
4147
xmalloc(sizeof(*r->objects->replace_map));
4248
oidmap_init(r->objects->replace_map, 0);
4349

4450
for_each_replace_ref(r, register_replace_ref, NULL);
51+
r->objects->replace_map_initialized = 1;
52+
53+
pthread_mutex_unlock(&r->objects->replace_mutex);
4554
}
4655

4756
/* We allow "recursive" replacement. Only within reason, though */

replace-object.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@ const struct object_id *do_lookup_replace_object(struct repository *r,
2424
* name (replaced recursively, if necessary). The return value is
2525
* either sha1 or a pointer to a permanently-allocated value. When
2626
* object replacement is suppressed, always return sha1.
27+
*
28+
* Note: some thread debuggers might point a data race on the
29+
* replace_map_initialized reading in this function. However, we know there's no
30+
* problem in the value being updated by one thread right after another one read
31+
* it here (and it should be written to only once, anyway).
2732
*/
2833
static inline const struct object_id *lookup_replace_object(struct repository *r,
2934
const struct object_id *oid)
3035
{
3136
if (!read_replace_refs ||
32-
(r->objects->replace_map &&
37+
(r->objects->replace_map_initialized &&
3338
r->objects->replace_map->map.tablesize == 0))
3439
return oid;
3540
return do_lookup_replace_object(r, oid);

0 commit comments

Comments
 (0)