Skip to content

Commit 2f77696

Browse files
committed
Fix RID_Owner synchronization
1 parent 7a1cc1e commit 2f77696

File tree

3 files changed

+264
-48
lines changed

3 files changed

+264
-48
lines changed

core/os/spin_lock.h

+40-23
Original file line numberDiff line numberDiff line change
@@ -31,42 +31,21 @@
3131
#ifndef SPIN_LOCK_H
3232
#define SPIN_LOCK_H
3333

34-
#include "core/typedefs.h"
35-
36-
#if defined(__APPLE__)
37-
38-
#include <os/lock.h>
39-
40-
class SpinLock {
41-
mutable os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT;
42-
43-
public:
44-
_ALWAYS_INLINE_ void lock() const {
45-
os_unfair_lock_lock(&_lock);
46-
}
47-
48-
_ALWAYS_INLINE_ void unlock() const {
49-
os_unfair_lock_unlock(&_lock);
50-
}
51-
};
52-
53-
#else
54-
5534
#include "core/os/thread.h"
5635

5736
#include <atomic>
5837
#include <thread>
5938

6039
static_assert(std::atomic_bool::is_always_lock_free);
6140

62-
class alignas(Thread::CACHE_LINE_BYTES) SpinLock {
41+
class alignas(Thread::CACHE_LINE_BYTES) AcqRelSpinLock {
6342
mutable std::atomic<bool> locked = ATOMIC_VAR_INIT(false);
6443

6544
public:
6645
_ALWAYS_INLINE_ void lock() const {
6746
while (true) {
6847
bool expected = false;
69-
if (locked.compare_exchange_weak(expected, true, std::memory_order_acq_rel, std::memory_order_relaxed)) {
48+
if (locked.compare_exchange_weak(expected, true, std::memory_order_acquire, std::memory_order_relaxed)) {
7049
break;
7150
}
7251
do {
@@ -78,8 +57,46 @@ class alignas(Thread::CACHE_LINE_BYTES) SpinLock {
7857
_ALWAYS_INLINE_ void unlock() const {
7958
locked.store(false, std::memory_order_release);
8059
}
60+
61+
_ALWAYS_INLINE_ void acquire() const {
62+
(void)locked.load(std::memory_order_acquire);
63+
}
64+
65+
_ALWAYS_INLINE_ void release() const {
66+
// Do as little as possible to issue a release on the atomic
67+
// without changing its value.
68+
while (true) {
69+
for (int i = 0; i < 2; i++) {
70+
bool expected = (bool)i;
71+
if (locked.compare_exchange_weak(expected, expected, std::memory_order_release, std::memory_order_relaxed)) {
72+
return;
73+
}
74+
}
75+
}
76+
}
8177
};
8278

79+
#if defined(__APPLE__)
80+
81+
#include <os/lock.h>
82+
83+
class SpinLock {
84+
mutable os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT;
85+
86+
public:
87+
_ALWAYS_INLINE_ void lock() const {
88+
os_unfair_lock_lock(&_lock);
89+
}
90+
91+
_ALWAYS_INLINE_ void unlock() const {
92+
os_unfair_lock_unlock(&_lock);
93+
}
94+
};
95+
96+
#else
97+
98+
using SpinLock = AcqRelSpinLock;
99+
83100
#endif // __APPLE__
84101

85102
#endif // SPIN_LOCK_H

core/templates/rid_owner.h

+71-25
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
#define RID_OWNER_H
3333

3434
#include "core/os/memory.h"
35-
#include "core/os/mutex.h"
35+
#include "core/os/spin_lock.h"
3636
#include "core/string/print_string.h"
3737
#include "core/templates/hash_set.h"
3838
#include "core/templates/list.h"
@@ -43,6 +43,20 @@
4343
#include <stdio.h>
4444
#include <typeinfo>
4545

46+
#ifdef SANITIZERS_ENABLED
47+
#ifdef __has_feature
48+
#if __has_feature(thread_sanitizer)
49+
#define TSAN_ENABLED
50+
#endif
51+
#elif defined(__SANITIZE_THREAD__)
52+
#define TSAN_ENABLED
53+
#endif
54+
#endif
55+
56+
#ifdef TSAN_ENABLED
57+
#include <sanitizer/tsan_interface.h>
58+
#endif
59+
4660
class RID_AllocBase {
4761
static SafeNumeric<uint64_t> base_id;
4862

@@ -83,18 +97,18 @@ class RID_Alloc : public RID_AllocBase {
8397

8498
const char *description = nullptr;
8599

86-
mutable Mutex mutex;
100+
AcqRelSpinLock spin;
87101

88102
_FORCE_INLINE_ RID _allocate_rid() {
89103
if constexpr (THREAD_SAFE) {
90-
mutex.lock();
104+
spin.lock();
91105
}
92106

93107
if (alloc_count == max_alloc) {
94108
//allocate a new chunk
95109
uint32_t chunk_count = alloc_count == 0 ? 0 : (max_alloc / elements_in_chunk);
96110
if (THREAD_SAFE && chunk_count == chunk_limit) {
97-
mutex.unlock();
111+
spin.unlock();
98112
if (description != nullptr) {
99113
ERR_FAIL_V_MSG(RID(), vformat("Element limit for RID of type '%s' reached.", String(description)));
100114
} else {
@@ -120,7 +134,8 @@ class RID_Alloc : public RID_AllocBase {
120134
free_list_chunks[chunk_count][i] = alloc_count + i;
121135
}
122136

123-
max_alloc += elements_in_chunk;
137+
// Store atomically to avoid data race with the load in get_or_null().
138+
((std::atomic<uint32_t> *)&max_alloc)->store(max_alloc + elements_in_chunk, std::memory_order_relaxed);
124139
}
125140

126141
uint32_t free_index = free_list_chunks[alloc_count / elements_in_chunk][alloc_count % elements_in_chunk];
@@ -140,7 +155,7 @@ class RID_Alloc : public RID_AllocBase {
140155
alloc_count++;
141156

142157
if constexpr (THREAD_SAFE) {
143-
mutex.unlock();
158+
spin.unlock();
144159
}
145160

146161
return _make_from_id(id);
@@ -168,9 +183,13 @@ class RID_Alloc : public RID_AllocBase {
168183
return nullptr;
169184
}
170185

186+
spin.acquire();
187+
171188
uint64_t id = p_rid.get_id();
172189
uint32_t idx = uint32_t(id & 0xFFFFFFFF);
173-
if (unlikely(idx >= max_alloc)) {
190+
// Read atomically to avoid data race with the store in _allocate_rid().
191+
uint32_t ma = ((std::atomic<uint32_t> *)&max_alloc)->load(std::memory_order_acquire);
192+
if (unlikely(idx >= ma)) {
174193
return nullptr;
175194
}
176195

@@ -180,6 +199,9 @@ class RID_Alloc : public RID_AllocBase {
180199
uint32_t validator = uint32_t(id >> 32);
181200

182201
Chunk &c = chunks[idx_chunk][idx_element];
202+
#ifdef TSAN_ENABLED
203+
__tsan_acquire(&c.validator); // We know not a race in practice.
204+
#endif
183205
if (unlikely(p_initialize)) {
184206
if (unlikely(!(c.validator & 0x80000000))) {
185207
ERR_FAIL_V_MSG(nullptr, "Initializing already initialized RID");
@@ -189,14 +211,18 @@ class RID_Alloc : public RID_AllocBase {
189211
ERR_FAIL_V_MSG(nullptr, "Attempting to initialize the wrong RID");
190212
}
191213

192-
c.validator &= 0x7FFFFFFF; //initialized
214+
// Mark initialized.
215+
c.validator &= 0x7FFFFFFF;
193216

194217
} else if (unlikely(c.validator != validator)) {
195218
if ((c.validator & 0x80000000) && c.validator != 0xFFFFFFFF) {
196219
ERR_FAIL_V_MSG(nullptr, "Attempting to use an uninitialized RID");
197220
}
198221
return nullptr;
199222
}
223+
#ifdef TSAN_ENABLED
224+
__tsan_release(&c.validator);
225+
#endif
200226

201227
T *ptr = &c.data;
202228

@@ -205,24 +231,39 @@ class RID_Alloc : public RID_AllocBase {
205231
void initialize_rid(RID p_rid) {
206232
T *mem = get_or_null(p_rid, true);
207233
ERR_FAIL_NULL(mem);
234+
#ifdef TSAN_ENABLED
235+
__tsan_acquire(mem); // We know not a race in practice.
236+
#endif
208237
memnew_placement(mem, T);
238+
#ifdef TSAN_ENABLED
239+
__tsan_release(mem);
240+
#endif
241+
spin.release();
209242
}
243+
210244
void initialize_rid(RID p_rid, const T &p_value) {
211245
T *mem = get_or_null(p_rid, true);
212246
ERR_FAIL_NULL(mem);
247+
#ifdef TSAN_ENABLED
248+
__tsan_acquire(mem); // We know not a race in practice.
249+
#endif
213250
memnew_placement(mem, T(p_value));
251+
#ifdef TSAN_ENABLED
252+
__tsan_release(mem);
253+
#endif
254+
spin.release();
214255
}
215256

216257
_FORCE_INLINE_ bool owns(const RID &p_rid) const {
217258
if constexpr (THREAD_SAFE) {
218-
mutex.lock();
259+
spin.lock();
219260
}
220261

221262
uint64_t id = p_rid.get_id();
222263
uint32_t idx = uint32_t(id & 0xFFFFFFFF);
223264
if (unlikely(idx >= max_alloc)) {
224265
if constexpr (THREAD_SAFE) {
225-
mutex.unlock();
266+
spin.unlock();
226267
}
227268
return false;
228269
}
@@ -235,22 +276,22 @@ class RID_Alloc : public RID_AllocBase {
235276
bool owned = (validator != 0x7FFFFFFF) && (chunks[idx_chunk][idx_element].validator & 0x7FFFFFFF) == validator;
236277

237278
if constexpr (THREAD_SAFE) {
238-
mutex.unlock();
279+
spin.unlock();
239280
}
240281

241282
return owned;
242283
}
243284

244285
_FORCE_INLINE_ void free(const RID &p_rid) {
245286
if constexpr (THREAD_SAFE) {
246-
mutex.lock();
287+
spin.lock();
247288
}
248289

249290
uint64_t id = p_rid.get_id();
250291
uint32_t idx = uint32_t(id & 0xFFFFFFFF);
251292
if (unlikely(idx >= max_alloc)) {
252293
if constexpr (THREAD_SAFE) {
253-
mutex.unlock();
294+
spin.unlock();
254295
}
255296
ERR_FAIL();
256297
}
@@ -261,12 +302,12 @@ class RID_Alloc : public RID_AllocBase {
261302
uint32_t validator = uint32_t(id >> 32);
262303
if (unlikely(chunks[idx_chunk][idx_element].validator & 0x80000000)) {
263304
if constexpr (THREAD_SAFE) {
264-
mutex.unlock();
305+
spin.unlock();
265306
}
266307
ERR_FAIL_MSG("Attempted to free an uninitialized or invalid RID");
267308
} else if (unlikely(chunks[idx_chunk][idx_element].validator != validator)) {
268309
if constexpr (THREAD_SAFE) {
269-
mutex.unlock();
310+
spin.unlock();
270311
}
271312
ERR_FAIL();
272313
}
@@ -278,7 +319,7 @@ class RID_Alloc : public RID_AllocBase {
278319
free_list_chunks[alloc_count / elements_in_chunk][alloc_count % elements_in_chunk] = idx;
279320

280321
if constexpr (THREAD_SAFE) {
281-
mutex.unlock();
322+
spin.unlock();
282323
}
283324
}
284325

@@ -287,35 +328,35 @@ class RID_Alloc : public RID_AllocBase {
287328
}
288329
void get_owned_list(List<RID> *p_owned) const {
289330
if constexpr (THREAD_SAFE) {
290-
mutex.lock();
331+
spin.lock();
291332
}
292333
for (size_t i = 0; i < max_alloc; i++) {
293-
uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
334+
uint32_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
294335
if (validator != 0xFFFFFFFF) {
295-
p_owned->push_back(_make_from_id((validator << 32) | i));
336+
p_owned->push_back(_make_from_id(((uint64_t)validator << 32) | i));
296337
}
297338
}
298339
if constexpr (THREAD_SAFE) {
299-
mutex.unlock();
340+
spin.unlock();
300341
}
301342
}
302343

303344
//used for fast iteration in the elements or RIDs
304345
void fill_owned_buffer(RID *p_rid_buffer) const {
305346
if constexpr (THREAD_SAFE) {
306-
mutex.lock();
347+
spin.lock();
307348
}
308349
uint32_t idx = 0;
309350
for (size_t i = 0; i < max_alloc; i++) {
310-
uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
351+
uint32_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
311352
if (validator != 0xFFFFFFFF) {
312-
p_rid_buffer[idx] = _make_from_id((validator << 32) | i);
353+
p_rid_buffer[idx] = _make_from_id(((uint64_t)validator << 32) | i);
313354
idx++;
314355
}
315356
}
316357

317358
if constexpr (THREAD_SAFE) {
318-
mutex.unlock();
359+
spin.unlock();
319360
}
320361
}
321362

@@ -329,16 +370,21 @@ class RID_Alloc : public RID_AllocBase {
329370
chunk_limit = (p_maximum_number_of_elements / elements_in_chunk) + 1;
330371
chunks = (Chunk **)memalloc(sizeof(Chunk *) * chunk_limit);
331372
free_list_chunks = (uint32_t **)memalloc(sizeof(uint32_t *) * chunk_limit);
373+
spin.release();
332374
}
333375
}
334376

335377
~RID_Alloc() {
378+
if constexpr (THREAD_SAFE) {
379+
spin.lock();
380+
}
381+
336382
if (alloc_count) {
337383
print_error(vformat("ERROR: %d RID allocations of type '%s' were leaked at exit.",
338384
alloc_count, description ? description : typeid(T).name()));
339385

340386
for (size_t i = 0; i < max_alloc; i++) {
341-
uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
387+
uint32_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
342388
if (validator & 0x80000000) {
343389
continue; //uninitialized
344390
}

0 commit comments

Comments
 (0)