blob: 6dfe4e3933d8d661914724ba6c31011290d05fff (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
# ThreadSanitizer options and suppression patterns for Zen
#
# This is a mixed-format file:
#
# key=value TSAN runtime option; parsed by xmake's sanitizer.options
# rule and set directly in TSAN_OPTIONS. When this file is also
# passed as suppressions=<path>, the TSAN runtime silently skips
# these lines (they don't match the type:pattern format).
# race:... race condition suppression pattern
# mutex:... mutex suppression pattern
# signal:... signal suppression pattern
# thread:... thread suppression pattern
# # comment line
# <blank> ignored
#
# xmake passes the file via TSAN_OPTIONS=suppressions=<path> when both the
# compiler is Clang or GCC (not MSVC/clang-cl) and the platform is Linux or
# macOS. This is handled by the sanitizer.options rule in xmake.lua.
#
# If you run a binary directly (not via xmake run / xmake test) set
# TSAN_OPTIONS manually, e.g.:
# TSAN_OPTIONS="detect_deadlocks=0:suppressions=$(pwd)/tsan.supp" ./zenserver
# Required because GC's LockState() acquires shared lock scopes on every named
# cache bucket (m_IndexLock) and every oplog (GcReferenceLocker) simultaneously.
# With enough buckets/projects/oplogs this easily exceeds TSAN's hard per-thread
# limit of 128 simultaneously-held locks, causing a CHECK abort. This is a known
# TSAN limitation, not a real deadlock risk. The long-term fix is to replace the
# N per-bucket shared-lock pattern in ZenCacheStore::LockState /
# ProjectStore::LockState with a single coarser "GC epoch" RwLock.
#
# NOTE: this line will produce a TSAN warning along the lines of:
# WARNING: failed to parse suppression 'detect_deadlocks=0'
# This is expected and harmless. xmake's sanitizer.options rule parses
# key=value lines from this file and injects them into TSAN_OPTIONS directly.
# When the file is subsequently passed to the TSAN runtime as suppressions=<path>,
# the runtime does not understand key=value syntax, prints the warning, and skips
# the line. The option is already active via TSAN_OPTIONS; nothing is lost.
detect_deadlocks=0
# EASTL's hashtable uses a global gpEmptyBucketArray[2] sentinel shared by all
# empty hash tables (mnBucketCount == 1). DoFreeNodes unconditionally writes NULL
# to each bucket slot, including this shared global. Multiple threads concurrently
# destroying empty EASTL hash_maps all write NULL to gpEmptyBucketArray[0], which
# TSAN reports as a race. This is benign: the slot is always NULL and writing NULL
# to it has no observable effect.
race:eastl::hashtable*DoFreeNodes*
# UE::Trace's GetUid() uses a racy static uint32 cache (Uid = Uid ? Uid : Initialize())
# as a fast path to avoid re-entering Initialize(). The actual initialization is done via
# a thread-safe static (Uid_ThreadSafeInit) inside Initialize(), so the worst case is
# redundant calls to Initialize() which always returns the same value.
race:*Fields::GetUid*
# TRACE_CPU_SCOPE generates a function-local `static int32 scope_id` that is lazily
# initialized without synchronization (if (0 == scope_id) scope_id = ScopeNew(...)).
# Same benign pattern as GetUid: the worst case is redundant calls to ScopeNew() which
# always returns the same value for a given scope name.
race:*$trace_scope_id*
|