]> git.ipfire.org Git - thirdparty/git.git/commitdiff
replace-object: make replace operations thread-safe
authorMatheus Tavares <matheus.bernardino@usp.br>
Thu, 16 Jan 2020 02:39:52 +0000 (23:39 -0300)
committerJunio C Hamano <gitster@pobox.com>
Fri, 17 Jan 2020 21:52:14 +0000 (13:52 -0800)
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 <matheus.bernardino@usp.br>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
.tsan-suppressions
object-store.h
object.c
replace-object.c
replace-object.h

index 8c85014a0a936892f6832c68e3db646b6f9d2ea2..5ba86d68459e61f87dae1332c7f2402860b4280c 100644 (file)
@@ -8,3 +8,9 @@
 # in practice it (hopefully!) doesn't matter.
 race:^want_color$
 race:^transfer_debug$
+
+# A boolean value, which tells whether the replace_map has been initialized or
+# not, is read racily with an update. As this variable is written to only once,
+# and it's OK if the value change right after reading it, this shouldn't be a
+# problem.
+race:^lookup_replace_object$
index 55ee63935073e2b2790d82e52399aaa1638f9e8f..33739c9deeb5f18ae878c75b88e7e452f30e38a9 100644 (file)
@@ -125,6 +125,8 @@ struct raw_object_store {
         * (see git-replace(1)).
         */
        struct oidmap *replace_map;
+       unsigned replace_map_initialized : 1;
+       pthread_mutex_t replace_mutex; /* protect object replace functions */
 
        struct commit_graph *commit_graph;
        unsigned commit_graph_attempted : 1; /* if loading has been attempted */
index 142ef69399a2fd81c36d81291c573295715b48b8..b4e1d3db3c73097fff3a9accd515a5b45887eada 100644 (file)
--- a/object.c
+++ b/object.c
@@ -480,6 +480,7 @@ struct raw_object_store *raw_object_store_new(void)
        memset(o, 0, sizeof(*o));
        INIT_LIST_HEAD(&o->packed_git_mru);
        hashmap_init(&o->pack_map, pack_map_entry_cmp, NULL, 0);
+       pthread_mutex_init(&o->replace_mutex, NULL);
        return o;
 }
 
@@ -507,6 +508,7 @@ void raw_object_store_clear(struct raw_object_store *o)
 
        oidmap_free(o->replace_map, 1);
        FREE_AND_NULL(o->replace_map);
+       pthread_mutex_destroy(&o->replace_mutex);
 
        free_commit_graph(o->commit_graph);
        o->commit_graph = NULL;
index e295e87943102c2a1ad903aea1a634d34bf603a0..7bd9aba6ee6c339e02c6fe25262a75a19bfc6e68 100644 (file)
@@ -34,14 +34,23 @@ static int register_replace_ref(struct repository *r,
 
 void prepare_replace_object(struct repository *r)
 {
-       if (r->objects->replace_map)
+       if (r->objects->replace_map_initialized)
                return;
 
+       pthread_mutex_lock(&r->objects->replace_mutex);
+       if (r->objects->replace_map_initialized) {
+               pthread_mutex_unlock(&r->objects->replace_mutex);
+               return;
+       }
+
        r->objects->replace_map =
                xmalloc(sizeof(*r->objects->replace_map));
        oidmap_init(r->objects->replace_map, 0);
 
        for_each_replace_ref(r, register_replace_ref, NULL);
+       r->objects->replace_map_initialized = 1;
+
+       pthread_mutex_unlock(&r->objects->replace_mutex);
 }
 
 /* We allow "recursive" replacement. Only within reason, though */
index 04ed7a85a2402d64e715c38850a41bea43d0f3b3..3fbc32eb7b7ef7bf9119f83aa7e3a4b6c20104b3 100644 (file)
@@ -24,12 +24,17 @@ const struct object_id *do_lookup_replace_object(struct repository *r,
  * name (replaced recursively, if necessary).  The return value is
  * either sha1 or a pointer to a permanently-allocated value.  When
  * object replacement is suppressed, always return sha1.
+ *
+ * Note: some thread debuggers might point a data race on the
+ * replace_map_initialized reading in this function. However, we know there's no
+ * problem in the value being updated by one thread right after another one read
+ * it here (and it should be written to only once, anyway).
  */
 static inline const struct object_id *lookup_replace_object(struct repository *r,
                                                            const struct object_id *oid)
 {
        if (!read_replace_refs ||
-           (r->objects->replace_map &&
+           (r->objects->replace_map_initialized &&
             r->objects->replace_map->map.tablesize == 0))
                return oid;
        return do_lookup_replace_object(r, oid);