From: "Alex Bennée" <alex.bennee@linaro.org>
To: qemu-devel@nongnu.org
Cc: "Richard Henderson" <richard.henderson@linaro.org>,
"Alexandre Iooss" <erdnaxe@crans.org>,
"Beraldo Leal" <bleal@redhat.com>,
"Thomas Huth" <thuth@redhat.com>, "John Snow" <jsnow@redhat.com>,
"Eduardo Habkost" <eduardo@habkost.net>,
"Elena Ufimtseva" <elena.ufimtseva@oracle.com>,
"Ed Maste" <emaste@freebsd.org>,
"Yanan Wang" <wangyanan55@huawei.com>,
"Cleber Rosa" <crosa@redhat.com>,
"Marc-André Lureau" <marcandre.lureau@redhat.com>,
"Li-Wen Hsu" <lwhsu@freebsd.org>,
"Markus Armbruster" <armbru@redhat.com>,
"Jagannathan Raman" <jag.raman@oracle.com>,
"Daniel P. Berrangé" <berrange@redhat.com>,
"Philippe Mathieu-Daudé" <philmd@linaro.org>,
"Michael Roth" <michael.roth@amd.com>,
"Wainer dos Santos Moschetta" <wainersm@redhat.com>,
"Alex Bennée" <alex.bennee@linaro.org>,
qemu-arm@nongnu.org,
"Marcel Apfelbaum" <marcel.apfelbaum@gmail.com>,
"Peter Maydell" <peter.maydell@linaro.org>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Mahmoud Mandour" <ma.mandourr@gmail.com>,
"John G Johnson" <john.g.johnson@oracle.com>,
"Emilio Cota" <cota@braap.org>
Subject: [PATCH 21/26] util/qht: use striped locks under TSAN
Date: Tue, 10 Jan 2023 17:39:17 +0000 [thread overview]
Message-ID: <20230110173922.265055-22-alex.bennee@linaro.org> (raw)
In-Reply-To: <20230110173922.265055-1-alex.bennee@linaro.org>
From: Emilio Cota <cota@braap.org>
Fixes this tsan crash, easy to reproduce with any large enough program:
$ tests/unit/test-qht
1..2
ThreadSanitizer: CHECK failed: sanitizer_deadlock_detector.h:67 "((n_all_locks_)) < (((sizeof(all_locks_with_contexts_)/sizeof((all_locks_with_contexts_)[0]))))" (0x40, 0x40) (tid=1821568)
#0 __tsan::CheckUnwind() ../../../../src/libsanitizer/tsan/tsan_rtl.cpp:353 (libtsan.so.2+0x90034)
#1 __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cpp:86 (libtsan.so.2+0xca555)
#2 __sanitizer::DeadlockDetectorTLS<__sanitizer::TwoLevelBitVector<1ul, __sanitizer::BasicBitVector<unsigned long> > >::addLock(unsigned long, unsigned long, unsigned int) ../../../../src/libsanitizer/sanitizer_common/sanitizer_deadlock_detector.h:67 (libtsan.so.2+0xb3616)
#3 __sanitizer::DeadlockDetectorTLS<__sanitizer::TwoLevelBitVector<1ul, __sanitizer::BasicBitVector<unsigned long> > >::addLock(unsigned long, unsigned long, unsigned int) ../../../../src/libsanitizer/sanitizer_common/sanitizer_deadlock_detector.h:59 (libtsan.so.2+0xb3616)
#4 __sanitizer::DeadlockDetector<__sanitizer::TwoLevelBitVector<1ul, __sanitizer::BasicBitVector<unsigned long> > >::onLockAfter(__sanitizer::DeadlockDetectorTLS<__sanitizer::TwoLevelBitVector<1ul, __sanitizer::BasicBitVector<unsigned long> > >*, unsigned long, unsigned int) ../../../../src/libsanitizer/sanitizer_common/sanitizer_deadlock_detector.h:216 (libtsan.so.2+0xb3616)
#5 __sanitizer::DD::MutexAfterLock(__sanitizer::DDCallback*, __sanitizer::DDMutex*, bool, bool) ../../../../src/libsanitizer/sanitizer_common/sanitizer_deadlock_detector1.cpp:169 (libtsan.so.2+0xb3616)
#6 __tsan::MutexPostLock(__tsan::ThreadState*, unsigned long, unsigned long, unsigned int, int) ../../../../src/libsanitizer/tsan/tsan_rtl_mutex.cpp:200 (libtsan.so.2+0xa3382)
#7 __tsan_mutex_post_lock ../../../../src/libsanitizer/tsan/tsan_interface_ann.cpp:384 (libtsan.so.2+0x76bc3)
#8 qemu_spin_lock /home/cota/src/qemu/include/qemu/thread.h:259 (test-qht+0x44a97)
#9 qht_map_lock_buckets ../util/qht.c:253 (test-qht+0x44a97)
#10 do_qht_iter ../util/qht.c:809 (test-qht+0x45f33)
#11 qht_iter ../util/qht.c:821 (test-qht+0x45f33)
#12 iter_check ../tests/unit/test-qht.c:121 (test-qht+0xe473)
#13 qht_do_test ../tests/unit/test-qht.c:202 (test-qht+0xe473)
#14 qht_test ../tests/unit/test-qht.c:240 (test-qht+0xe7c1)
#15 test_default ../tests/unit/test-qht.c:246 (test-qht+0xe828)
#16 <null> <null> (libglib-2.0.so.0+0x7daed)
#17 <null> <null> (libglib-2.0.so.0+0x7d80a)
#18 <null> <null> (libglib-2.0.so.0+0x7d80a)
#19 g_test_run_suite <null> (libglib-2.0.so.0+0x7dfe9)
#20 g_test_run <null> (libglib-2.0.so.0+0x7e055)
#21 main ../tests/unit/test-qht.c:259 (test-qht+0xd2c6)
#22 __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 (libc.so.6+0x29d8f)
#23 __libc_start_main_impl ../csu/libc-start.c:392 (libc.so.6+0x29e3f)
#24 _start <null> (test-qht+0xdb44)
Signed-off-by: Emilio Cota <cota@braap.org>
Message-Id: <20230109224954.161672-5-cota@braap.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
---
util/qht.c | 101 +++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 87 insertions(+), 14 deletions(-)
diff --git a/util/qht.c b/util/qht.c
index 15866299e6..70cc733d5d 100644
--- a/util/qht.c
+++ b/util/qht.c
@@ -151,6 +151,22 @@ struct qht_bucket {
QEMU_BUILD_BUG_ON(sizeof(struct qht_bucket) > QHT_BUCKET_ALIGN);
+/*
+ * Under TSAN, we use striped locks instead of one lock per bucket chain.
+ * This avoids crashing under TSAN, since TSAN aborts the program if more than
+ * 64 locks are held (this is a hardcoded limit in TSAN).
+ * When resizing a QHT we grab all the buckets' locks, which can easily
+ * go over TSAN's limit. By using striped locks, we avoid this problem.
+ *
+ * Note: this number must be a power of two for easy index computation.
+ */
+#define QHT_TSAN_BUCKET_LOCKS_BITS 4
+#define QHT_TSAN_BUCKET_LOCKS (1 << QHT_TSAN_BUCKET_LOCKS_BITS)
+
+struct qht_tsan_lock {
+ QemuSpin lock;
+} QEMU_ALIGNED(QHT_BUCKET_ALIGN);
+
/**
* struct qht_map - structure to track an array of buckets
* @rcu: used by RCU. Keep it as the top field in the struct to help valgrind
@@ -160,6 +176,7 @@ QEMU_BUILD_BUG_ON(sizeof(struct qht_bucket) > QHT_BUCKET_ALIGN);
* @n_added_buckets: number of added (i.e. "non-head") buckets
* @n_added_buckets_threshold: threshold to trigger an upward resize once the
* number of added buckets surpasses it.
+ * @tsan_bucket_locks: Array of striped locks to be used only under TSAN.
*
* Buckets are tracked in what we call a "map", i.e. this structure.
*/
@@ -169,6 +186,9 @@ struct qht_map {
size_t n_buckets;
size_t n_added_buckets;
size_t n_added_buckets_threshold;
+#ifdef CONFIG_TSAN
+ struct qht_tsan_lock tsan_bucket_locks[QHT_TSAN_BUCKET_LOCKS];
+#endif
};
/* trigger a resize when n_added_buckets > n_buckets / div */
@@ -229,10 +249,62 @@ static inline size_t qht_elems_to_buckets(size_t n_elems)
return pow2ceil(n_elems / QHT_BUCKET_ENTRIES);
}
-static inline void qht_head_init(struct qht_bucket *b)
+/*
+ * When using striped locks (i.e. under TSAN), we have to be careful not
+ * to operate on the same lock twice (e.g. when iterating through all buckets).
+ * We achieve this by operating only on each stripe's first matching lock.
+ */
+static inline void qht_do_if_first_in_stripe(const struct qht_map *map,
+ struct qht_bucket *b,
+ void (*func)(QemuSpin *spin))
+{
+#ifdef CONFIG_TSAN
+ unsigned long bucket_idx = b - map->buckets;
+ bool is_first_in_stripe = (bucket_idx >> QHT_TSAN_BUCKET_LOCKS_BITS) == 0;
+ if (is_first_in_stripe) {
+ unsigned long lock_idx = bucket_idx & (QHT_TSAN_BUCKET_LOCKS - 1);
+ func(&map->tsan_bucket_locks[lock_idx]);
+ }
+#else
+ func(&b->lock);
+#endif
+}
+
+static inline void qht_bucket_lock_destroy(const struct qht_map *map,
+ struct qht_bucket *b)
+{
+ qht_do_if_first_in_stripe(map, b, qemu_spin_destroy);
+}
+
+static inline void qht_bucket_lock_do(const struct qht_map *map,
+ struct qht_bucket *b,
+ void (*func)(QemuSpin *lock))
+{
+#ifdef CONFIG_TSAN
+ unsigned long bucket_idx = b - map->buckets;
+ unsigned long lock_idx = bucket_idx & (QHT_TSAN_BUCKET_LOCKS - 1);
+ func(&map->tsan_bucket_locks[lock_idx]);
+#else
+ func(&b->lock);
+#endif
+}
+
+static inline void qht_bucket_lock(const struct qht_map *map,
+ struct qht_bucket *b)
+{
+ qht_bucket_lock_do(map, b, qemu_spin_lock);
+}
+
+static inline void qht_bucket_unlock(const struct qht_map *map,
+ struct qht_bucket *b)
+{
+ qht_bucket_lock_do(map, b, qemu_spin_unlock);
+}
+
+static inline void qht_head_init(struct qht_map *map, struct qht_bucket *b)
{
memset(b, 0, sizeof(*b));
- qemu_spin_init(&b->lock);
+ qht_do_if_first_in_stripe(map, b, qemu_spin_init);
seqlock_init(&b->sequence);
}
@@ -250,7 +322,7 @@ static void qht_map_lock_buckets(struct qht_map *map)
for (i = 0; i < map->n_buckets; i++) {
struct qht_bucket *b = &map->buckets[i];
- qemu_spin_lock(&b->lock);
+ qht_do_if_first_in_stripe(map, b, qemu_spin_lock);
}
}
@@ -261,7 +333,7 @@ static void qht_map_unlock_buckets(struct qht_map *map)
for (i = 0; i < map->n_buckets; i++) {
struct qht_bucket *b = &map->buckets[i];
- qemu_spin_unlock(&b->lock);
+ qht_do_if_first_in_stripe(map, b, qemu_spin_unlock);
}
}
@@ -308,7 +380,7 @@ void qht_map_lock_buckets__no_stale(struct qht *ht, struct qht_map **pmap)
* Get a head bucket and lock it, making sure its parent map is not stale.
* @pmap is filled with a pointer to the bucket's parent map.
*
- * Unlock with qemu_spin_unlock(&b->lock).
+ * Unlock with qht_bucket_unlock.
*
* Note: callers cannot have ht->lock held.
*/
@@ -322,18 +394,18 @@ struct qht_bucket *qht_bucket_lock__no_stale(struct qht *ht, uint32_t hash,
map = qatomic_rcu_read(&ht->map);
b = qht_map_to_bucket(map, hash);
- qemu_spin_lock(&b->lock);
+ qht_bucket_lock(map, b);
if (likely(!qht_map_is_stale__locked(ht, map))) {
*pmap = map;
return b;
}
- qemu_spin_unlock(&b->lock);
+ qht_bucket_unlock(map, b);
/* we raced with a resize; acquire ht->lock to see the updated ht->map */
qht_lock(ht);
map = ht->map;
b = qht_map_to_bucket(map, hash);
- qemu_spin_lock(&b->lock);
+ qht_bucket_lock(map, b);
qht_unlock(ht);
*pmap = map;
return b;
@@ -345,12 +417,13 @@ static inline bool qht_map_needs_resize(const struct qht_map *map)
map->n_added_buckets_threshold;
}
-static inline void qht_chain_destroy(const struct qht_bucket *head)
+static inline void qht_chain_destroy(const struct qht_map *map,
+ struct qht_bucket *head)
{
struct qht_bucket *curr = head->next;
struct qht_bucket *prev;
- qemu_spin_destroy(&head->lock);
+ qht_do_if_first_in_stripe(map, head, qemu_spin_destroy);
while (curr) {
prev = curr;
curr = curr->next;
@@ -364,7 +437,7 @@ static void qht_map_destroy(struct qht_map *map)
size_t i;
for (i = 0; i < map->n_buckets; i++) {
- qht_chain_destroy(&map->buckets[i]);
+ qht_chain_destroy(map, &map->buckets[i]);
}
qemu_vfree(map->buckets);
g_free(map);
@@ -390,7 +463,7 @@ static struct qht_map *qht_map_create(size_t n_buckets)
map->buckets = qemu_memalign(QHT_BUCKET_ALIGN,
sizeof(*map->buckets) * n_buckets);
for (i = 0; i < n_buckets; i++) {
- qht_head_init(&map->buckets[i]);
+ qht_head_init(map, &map->buckets[i]);
}
return map;
}
@@ -638,7 +711,7 @@ bool qht_insert(struct qht *ht, void *p, uint32_t hash, void **existing)
b = qht_bucket_lock__no_stale(ht, hash, &map);
prev = qht_insert__locked(ht, map, b, p, hash, &needs_resize);
qht_bucket_debug__locked(b);
- qemu_spin_unlock(&b->lock);
+ qht_bucket_unlock(map, b);
if (unlikely(needs_resize) && ht->mode & QHT_MODE_AUTO_RESIZE) {
qht_grow_maybe(ht);
@@ -749,7 +822,7 @@ bool qht_remove(struct qht *ht, const void *p, uint32_t hash)
b = qht_bucket_lock__no_stale(ht, hash, &map);
ret = qht_remove__locked(b, p, hash);
qht_bucket_debug__locked(b);
- qemu_spin_unlock(&b->lock);
+ qht_bucket_unlock(map, b);
return ret;
}
--
2.34.1
next prev parent reply other threads:[~2023-01-10 18:00 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-01-10 17:38 [PATCH 00/26] current maintainer trees (testing/semihosting/plugins) Alex Bennée
2023-01-10 17:38 ` [PATCH 01/26] scripts/ci: update gitlab-runner playbook to use latest runner Alex Bennée
2023-01-10 17:38 ` [PATCH 02/26] gitlab: add FF_SCRIPT_SECTIONS for timings Alex Bennée
2023-01-12 14:26 ` Thomas Huth
2023-01-10 17:38 ` [PATCH 03/26] gitlab: just use plain --cc=clang for custom runner build Alex Bennée
2023-01-11 18:50 ` Richard Henderson
2023-01-10 17:39 ` [PATCH 04/26] tests/unit: drop hacky race avoidance in test-io-channel-command Alex Bennée
2023-01-12 12:21 ` Thomas Huth
2023-01-13 16:10 ` Marc-André Lureau
2023-01-10 17:39 ` [PATCH 05/26] build-sys: fix crlf-ending C code Alex Bennée
2023-01-10 17:39 ` [PATCH 06/26] .gitlab-ci.d/windows: do not disable opengl Alex Bennée
2023-01-10 17:39 ` [PATCH 07/26] configure: replace Perl usage with sed Alex Bennée
2023-01-13 8:29 ` Paolo Bonzini
2023-01-10 17:39 ` [PATCH 08/26] meson: replace Perl usage with Python Alex Bennée
2023-01-10 17:39 ` [PATCH 09/26] docs: drop texinfo options Alex Bennée
2023-01-10 17:39 ` [PATCH 10/26] Update lcitool and fedora to 37 Alex Bennée
2023-01-10 17:39 ` [PATCH 11/26] lcitool: drop perl from QEMU project/dependencies Alex Bennée
2023-01-10 17:39 ` [PATCH 12/26] lcitool: drop texinfo " Alex Bennée
2023-01-10 17:39 ` [PATCH 13/26] semihosting: Write back semihosting data before completion callback Alex Bennée
2023-01-10 17:39 ` [PATCH 14/26] semihosting: add O_BINARY flag in host_open for NT compatibility Alex Bennée
2023-01-10 17:39 ` [PATCH 15/26] docs: add a proper feature overview in "About QEMU" Alex Bennée
2023-01-10 17:39 ` [PATCH 16/26] semihosting: add semihosting section to the docs Alex Bennée
2023-01-11 19:06 ` Richard Henderson
2023-01-10 17:39 ` [PATCH 17/26] tests/tcg: add memory-sve test for aarch64 Alex Bennée
2023-01-11 18:54 ` Richard Henderson
2023-01-10 17:39 ` [PATCH 18/26] cpu: free cpu->tb_jmp_cache with RCU Alex Bennée
2023-01-11 19:08 ` Richard Henderson
2023-01-10 17:39 ` [PATCH 19/26] util/qht: add missing atomic_set(hashes[i]) Alex Bennée
2023-01-10 17:39 ` [PATCH 20/26] thread: de-const qemu_spin_destroy Alex Bennée
2023-01-11 19:09 ` Richard Henderson
2023-01-10 17:39 ` Alex Bennée [this message]
2023-01-11 19:10 ` [PATCH 21/26] util/qht: use striped locks under TSAN Richard Henderson
2023-01-10 17:39 ` [PATCH 22/26] plugins: make qemu_plugin_user_exit's locking order consistent with fork_start's Alex Bennée
2023-01-10 17:39 ` [PATCH 23/26] plugins: fix optimization in plugin_gen_disable_mem_helpers Alex Bennée
2023-01-10 17:39 ` [PATCH 24/26] translator: always pair plugin_gen_insn_{start, end} calls Alex Bennée
2023-01-11 19:11 ` Richard Henderson
2023-01-10 17:39 ` [PATCH 25/26] tcg: exclude lookup_tb_ptr from helper instrumentation Alex Bennée
2023-01-11 19:15 ` Richard Henderson
2023-01-12 9:52 ` Alex Bennée
2023-01-12 11:59 ` Alex Bennée
2023-01-10 17:39 ` [PATCH 26/26] cpu-exec: assert that plugin_mem_cbs is NULL after execution Alex Bennée
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230110173922.265055-22-alex.bennee@linaro.org \
--to=alex.bennee@linaro.org \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=bleal@redhat.com \
--cc=cota@braap.org \
--cc=crosa@redhat.com \
--cc=eduardo@habkost.net \
--cc=elena.ufimtseva@oracle.com \
--cc=emaste@freebsd.org \
--cc=erdnaxe@crans.org \
--cc=jag.raman@oracle.com \
--cc=john.g.johnson@oracle.com \
--cc=jsnow@redhat.com \
--cc=lwhsu@freebsd.org \
--cc=ma.mandourr@gmail.com \
--cc=marcandre.lureau@redhat.com \
--cc=marcel.apfelbaum@gmail.com \
--cc=michael.roth@amd.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=richard.henderson@linaro.org \
--cc=thuth@redhat.com \
--cc=wainersm@redhat.com \
--cc=wangyanan55@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).