Coverage Report

Created: 2025-07-04 06:49

/src/cpython/Python/lock.c
Line
Count
Source (jump to first uncovered line)
1
// Lock implementation
2
3
#include "Python.h"
4
5
#include "pycore_lock.h"
6
#include "pycore_parking_lot.h"
7
#include "pycore_semaphore.h"
8
#include "pycore_time.h"          // _PyTime_Add()
9
10
#ifdef MS_WINDOWS
11
#  ifndef WIN32_LEAN_AND_MEAN
12
#    define WIN32_LEAN_AND_MEAN
13
#  endif
14
#  include <windows.h>            // SwitchToThread()
15
#elif defined(HAVE_SCHED_H)
16
#  include <sched.h>              // sched_yield()
17
#endif
18
19
// If a thread waits on a lock for longer than TIME_TO_BE_FAIR_NS (1 ms), then
20
// the unlocking thread directly hands off ownership of the lock. This avoids
21
// starvation.
22
static const PyTime_t TIME_TO_BE_FAIR_NS = 1000*1000;
23
24
// Spin for a bit before parking the thread. This is only enabled for
25
// `--disable-gil` builds because it is unlikely to be helpful if the GIL is
26
// enabled.
27
#if Py_GIL_DISABLED
28
static const int MAX_SPIN_COUNT = 40;
29
#else
30
static const int MAX_SPIN_COUNT = 0;
31
#endif
32
33
struct mutex_entry {
34
    // The time after which the unlocking thread should hand off lock ownership
35
    // directly to the waiting thread. Written by the waiting thread.
36
    PyTime_t time_to_be_fair;
37
38
    // Set to 1 if the lock was handed off. Written by the unlocking thread.
39
    int handed_off;
40
};
41
42
static void
43
_Py_yield(void)
44
0
{
45
#ifdef MS_WINDOWS
46
    SwitchToThread();
47
#elif defined(HAVE_SCHED_H)
48
    sched_yield();
49
0
#endif
50
0
}
51
52
PyLockStatus
53
_PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
54
8.67k
{
55
8.67k
    uint8_t v = _Py_atomic_load_uint8_relaxed(&m->_bits);
56
8.67k
    if ((v & _Py_LOCKED) == 0) {
57
8.67k
        if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, v|_Py_LOCKED)) {
58
8.67k
            return PY_LOCK_ACQUIRED;
59
8.67k
        }
60
8.67k
    }
61
0
    if (timeout == 0) {
62
0
        return PY_LOCK_FAILURE;
63
0
    }
64
65
0
    PyTime_t now;
66
    // silently ignore error: cannot report error to the caller
67
0
    (void)PyTime_MonotonicRaw(&now);
68
0
    PyTime_t endtime = 0;
69
0
    if (timeout > 0) {
70
0
        endtime = _PyTime_Add(now, timeout);
71
0
    }
72
73
0
    struct mutex_entry entry = {
74
0
        .time_to_be_fair = now + TIME_TO_BE_FAIR_NS,
75
0
        .handed_off = 0,
76
0
    };
77
78
0
    Py_ssize_t spin_count = 0;
79
0
    for (;;) {
80
0
        if ((v & _Py_LOCKED) == 0) {
81
            // The lock is unlocked. Try to grab it.
82
0
            if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, v|_Py_LOCKED)) {
83
0
                return PY_LOCK_ACQUIRED;
84
0
            }
85
0
            continue;
86
0
        }
87
88
0
        if (!(v & _Py_HAS_PARKED) && spin_count < MAX_SPIN_COUNT) {
89
            // Spin for a bit.
90
0
            _Py_yield();
91
0
            spin_count++;
92
0
            continue;
93
0
        }
94
95
0
        if (timeout == 0) {
96
0
            return PY_LOCK_FAILURE;
97
0
        }
98
0
        if ((flags & _PY_LOCK_PYTHONLOCK) && Py_IsFinalizing()) {
99
            // At this phase of runtime shutdown, only the finalization thread
100
            // can have attached thread state; others hang if they try
101
            // attaching. And since operations on this lock requires attached
102
            // thread state (_PY_LOCK_PYTHONLOCK), the finalization thread is
103
            // running this code, and no other thread can unlock.
104
            // Raise rather than hang. (_PY_LOCK_PYTHONLOCK allows raising
105
            // exceptons.)
106
0
            PyErr_SetString(PyExc_PythonFinalizationError,
107
0
                            "cannot acquire lock at interpreter finalization");
108
0
            return PY_LOCK_FAILURE;
109
0
        }
110
111
0
        uint8_t newv = v;
112
0
        if (!(v & _Py_HAS_PARKED)) {
113
            // We are the first waiter. Set the _Py_HAS_PARKED flag.
114
0
            newv = v | _Py_HAS_PARKED;
115
0
            if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &v, newv)) {
116
0
                continue;
117
0
            }
118
0
        }
119
120
0
        int ret = _PyParkingLot_Park(&m->_bits, &newv, sizeof(newv), timeout,
121
0
                                     &entry, (flags & _PY_LOCK_DETACH) != 0);
122
0
        if (ret == Py_PARK_OK) {
123
0
            if (entry.handed_off) {
124
                // We own the lock now.
125
0
                assert(_Py_atomic_load_uint8_relaxed(&m->_bits) & _Py_LOCKED);
126
0
                return PY_LOCK_ACQUIRED;
127
0
            }
128
0
        }
129
0
        else if (ret == Py_PARK_INTR && (flags & _PY_LOCK_HANDLE_SIGNALS)) {
130
0
            if (Py_MakePendingCalls() < 0) {
131
0
                return PY_LOCK_INTR;
132
0
            }
133
0
        }
134
0
        else if (ret == Py_PARK_INTR && (flags & _PY_FAIL_IF_INTERRUPTED)) {
135
0
            return PY_LOCK_INTR;
136
0
        }
137
0
        else if (ret == Py_PARK_TIMEOUT) {
138
0
            assert(timeout >= 0);
139
0
            return PY_LOCK_FAILURE;
140
0
        }
141
142
0
        if (timeout > 0) {
143
0
            timeout = _PyDeadline_Get(endtime);
144
0
            if (timeout <= 0) {
145
                // Avoid negative values because those mean block forever.
146
0
                timeout = 0;
147
0
            }
148
0
        }
149
150
0
        v = _Py_atomic_load_uint8_relaxed(&m->_bits);
151
0
    }
152
0
}
153
154
static void
155
mutex_unpark(void *arg, void *park_arg, int has_more_waiters)
156
0
{
157
0
    PyMutex *m = (PyMutex*)arg;
158
0
    struct mutex_entry *entry = (struct mutex_entry*)park_arg;
159
0
    uint8_t v = 0;
160
0
    if (entry) {
161
0
        PyTime_t now;
162
        // silently ignore error: cannot report error to the caller
163
0
        (void)PyTime_MonotonicRaw(&now);
164
0
        int should_be_fair = now > entry->time_to_be_fair;
165
166
0
        entry->handed_off = should_be_fair;
167
0
        if (should_be_fair) {
168
0
            v |= _Py_LOCKED;
169
0
        }
170
0
        if (has_more_waiters) {
171
0
            v |= _Py_HAS_PARKED;
172
0
        }
173
0
    }
174
0
    _Py_atomic_store_uint8(&m->_bits, v);
175
0
}
176
177
int
178
_PyMutex_TryUnlock(PyMutex *m)
179
0
{
180
0
    uint8_t v = _Py_atomic_load_uint8(&m->_bits);
181
0
    for (;;) {
182
0
        if ((v & _Py_LOCKED) == 0) {
183
            // error: the mutex is not locked
184
0
            return -1;
185
0
        }
186
0
        else if ((v & _Py_HAS_PARKED)) {
187
            // wake up a single thread
188
0
            _PyParkingLot_Unpark(&m->_bits, mutex_unpark, m);
189
0
            return 0;
190
0
        }
191
0
        else if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, _Py_UNLOCKED)) {
192
            // fast-path: no waiters
193
0
            return 0;
194
0
        }
195
0
    }
196
0
}
197
198
// _PyRawMutex stores a linked list of `struct raw_mutex_entry`, one for each
199
// thread waiting on the mutex, directly in the mutex itself.
200
struct raw_mutex_entry {
201
    struct raw_mutex_entry *next;
202
    _PySemaphore sema;
203
};
204
205
void
206
_PyRawMutex_LockSlow(_PyRawMutex *m)
207
0
{
208
0
    struct raw_mutex_entry waiter;
209
0
    _PySemaphore_Init(&waiter.sema);
210
211
0
    uintptr_t v = _Py_atomic_load_uintptr(&m->v);
212
0
    for (;;) {
213
0
        if ((v & _Py_LOCKED) == 0) {
214
            // Unlocked: try to grab it (even if it has a waiter).
215
0
            if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, v|_Py_LOCKED)) {
216
0
                break;
217
0
            }
218
0
            continue;
219
0
        }
220
221
        // Locked: try to add ourselves as a waiter.
222
0
        waiter.next = (struct raw_mutex_entry *)(v & ~1);
223
0
        uintptr_t desired = ((uintptr_t)&waiter)|_Py_LOCKED;
224
0
        if (!_Py_atomic_compare_exchange_uintptr(&m->v, &v, desired)) {
225
0
            continue;
226
0
        }
227
228
        // Wait for us to be woken up. Note that we still have to lock the
229
        // mutex ourselves: it is NOT handed off to us.
230
0
        _PySemaphore_Wait(&waiter.sema, -1, /*detach=*/0);
231
0
    }
232
233
0
    _PySemaphore_Destroy(&waiter.sema);
234
0
}
235
236
void
237
_PyRawMutex_UnlockSlow(_PyRawMutex *m)
238
0
{
239
0
    uintptr_t v = _Py_atomic_load_uintptr(&m->v);
240
0
    for (;;) {
241
0
        if ((v & _Py_LOCKED) == 0) {
242
0
            Py_FatalError("unlocking mutex that is not locked");
243
0
        }
244
245
0
        struct raw_mutex_entry *waiter = (struct raw_mutex_entry *)(v & ~1);
246
0
        if (waiter) {
247
0
            uintptr_t next_waiter = (uintptr_t)waiter->next;
248
0
            if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, next_waiter)) {
249
0
                _PySemaphore_Wakeup(&waiter->sema);
250
0
                return;
251
0
            }
252
0
        }
253
0
        else {
254
0
            if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, _Py_UNLOCKED)) {
255
0
                return;
256
0
            }
257
0
        }
258
0
    }
259
0
}
260
261
int
262
_PyEvent_IsSet(PyEvent *evt)
263
0
{
264
0
    uint8_t v = _Py_atomic_load_uint8(&evt->v);
265
0
    return v == _Py_LOCKED;
266
0
}
267
268
void
269
_PyEvent_Notify(PyEvent *evt)
270
0
{
271
0
    uintptr_t v = _Py_atomic_exchange_uint8(&evt->v, _Py_LOCKED);
272
0
    if (v == _Py_UNLOCKED) {
273
        // no waiters
274
0
        return;
275
0
    }
276
0
    else if (v == _Py_LOCKED) {
277
        // event already set
278
0
        return;
279
0
    }
280
0
    else {
281
0
        assert(v == _Py_HAS_PARKED);
282
0
        _PyParkingLot_UnparkAll(&evt->v);
283
0
    }
284
0
}
285
286
void
287
PyEvent_Wait(PyEvent *evt)
288
0
{
289
0
    while (!PyEvent_WaitTimed(evt, -1, /*detach=*/1))
290
0
        ;
291
0
}
292
293
int
294
PyEvent_WaitTimed(PyEvent *evt, PyTime_t timeout_ns, int detach)
295
0
{
296
0
    for (;;) {
297
0
        uint8_t v = _Py_atomic_load_uint8(&evt->v);
298
0
        if (v == _Py_LOCKED) {
299
            // event already set
300
0
            return 1;
301
0
        }
302
0
        if (v == _Py_UNLOCKED) {
303
0
            if (!_Py_atomic_compare_exchange_uint8(&evt->v, &v, _Py_HAS_PARKED)) {
304
0
                continue;
305
0
            }
306
0
        }
307
308
0
        uint8_t expected = _Py_HAS_PARKED;
309
0
        (void) _PyParkingLot_Park(&evt->v, &expected, sizeof(evt->v),
310
0
                                  timeout_ns, NULL, detach);
311
312
0
        return _Py_atomic_load_uint8(&evt->v) == _Py_LOCKED;
313
0
    }
314
0
}
315
316
static int
317
unlock_once(_PyOnceFlag *o, int res)
318
94
{
319
    // On success (res=0), we set the state to _Py_ONCE_INITIALIZED.
320
    // On failure (res=-1), we reset the state to _Py_UNLOCKED.
321
94
    uint8_t new_value;
322
94
    switch (res) {
323
0
        case -1: new_value = _Py_UNLOCKED; break;
324
94
        case  0: new_value = _Py_ONCE_INITIALIZED; break;
325
0
        default: {
326
0
            Py_FatalError("invalid result from _PyOnceFlag_CallOnce");
327
0
            Py_UNREACHABLE();
328
0
            break;
329
0
        }
330
94
    }
331
332
94
    uint8_t old_value = _Py_atomic_exchange_uint8(&o->v, new_value);
333
94
    if ((old_value & _Py_HAS_PARKED) != 0) {
334
        // wake up anyone waiting on the once flag
335
0
        _PyParkingLot_UnparkAll(&o->v);
336
0
    }
337
94
    return res;
338
94
}
339
340
int
341
_PyOnceFlag_CallOnceSlow(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg)
342
94
{
343
94
    uint8_t v = _Py_atomic_load_uint8(&flag->v);
344
94
    for (;;) {
345
94
        if (v == _Py_UNLOCKED) {
346
94
            if (!_Py_atomic_compare_exchange_uint8(&flag->v, &v, _Py_LOCKED)) {
347
0
                continue;
348
0
            }
349
94
            int res = fn(arg);
350
94
            return unlock_once(flag, res);
351
94
        }
352
353
0
        if (v == _Py_ONCE_INITIALIZED) {
354
0
            return 0;
355
0
        }
356
357
        // The once flag is initializing (locked).
358
0
        assert((v & _Py_LOCKED));
359
0
        if (!(v & _Py_HAS_PARKED)) {
360
            // We are the first waiter. Set the _Py_HAS_PARKED flag.
361
0
            uint8_t new_value = v | _Py_HAS_PARKED;
362
0
            if (!_Py_atomic_compare_exchange_uint8(&flag->v, &v, new_value)) {
363
0
                continue;
364
0
            }
365
0
            v = new_value;
366
0
        }
367
368
        // Wait for initialization to finish.
369
0
        _PyParkingLot_Park(&flag->v, &v, sizeof(v), -1, NULL, 1);
370
0
        v = _Py_atomic_load_uint8(&flag->v);
371
0
    }
372
94
}
373
374
static int
375
recursive_mutex_is_owned_by(_PyRecursiveMutex *m, PyThread_ident_t tid)
376
85.7k
{
377
85.7k
    return _Py_atomic_load_ullong_relaxed(&m->thread) == tid;
378
85.7k
}
379
380
int
381
_PyRecursiveMutex_IsLockedByCurrentThread(_PyRecursiveMutex *m)
382
13.2k
{
383
13.2k
    return recursive_mutex_is_owned_by(m, PyThread_get_thread_ident_ex());
384
13.2k
}
385
386
void
387
_PyRecursiveMutex_Lock(_PyRecursiveMutex *m)
388
30.4k
{
389
30.4k
    PyThread_ident_t thread = PyThread_get_thread_ident_ex();
390
30.4k
    if (recursive_mutex_is_owned_by(m, thread)) {
391
0
        m->level++;
392
0
        return;
393
0
    }
394
30.4k
    PyMutex_Lock(&m->mutex);
395
30.4k
    _Py_atomic_store_ullong_relaxed(&m->thread, thread);
396
30.4k
    assert(m->level == 0);
397
30.4k
}
398
399
PyLockStatus
400
_PyRecursiveMutex_LockTimed(_PyRecursiveMutex *m, PyTime_t timeout, _PyLockFlags flags)
401
5.83k
{
402
5.83k
    PyThread_ident_t thread = PyThread_get_thread_ident_ex();
403
5.83k
    if (recursive_mutex_is_owned_by(m, thread)) {
404
0
        m->level++;
405
0
        return PY_LOCK_ACQUIRED;
406
0
    }
407
5.83k
    PyLockStatus s = _PyMutex_LockTimed(&m->mutex, timeout, flags);
408
5.83k
    if (s == PY_LOCK_ACQUIRED) {
409
5.83k
        _Py_atomic_store_ullong_relaxed(&m->thread, thread);
410
5.83k
        assert(m->level == 0);
411
5.83k
    }
412
5.83k
    return s;
413
5.83k
}
414
415
void
416
_PyRecursiveMutex_Unlock(_PyRecursiveMutex *m)
417
13.2k
{
418
13.2k
    if (_PyRecursiveMutex_TryUnlock(m) < 0) {
419
0
        Py_FatalError("unlocking a recursive mutex that is not "
420
0
                      "owned by the current thread");
421
0
    }
422
13.2k
}
423
424
int
425
_PyRecursiveMutex_TryUnlock(_PyRecursiveMutex *m)
426
36.2k
{
427
36.2k
    PyThread_ident_t thread = PyThread_get_thread_ident_ex();
428
36.2k
    if (!recursive_mutex_is_owned_by(m, thread)) {
429
0
        return -1;
430
0
    }
431
36.2k
    if (m->level > 0) {
432
0
        m->level--;
433
0
        return 0;
434
0
    }
435
36.2k
    assert(m->level == 0);
436
36.2k
    _Py_atomic_store_ullong_relaxed(&m->thread, 0);
437
36.2k
    PyMutex_Unlock(&m->mutex);
438
36.2k
    return 0;
439
36.2k
}
440
441
0
#define _Py_WRITE_LOCKED 1
442
0
#define _PyRWMutex_READER_SHIFT 2
443
#define _Py_RWMUTEX_MAX_READERS (UINTPTR_MAX >> _PyRWMutex_READER_SHIFT)
444
445
static uintptr_t
446
rwmutex_set_parked_and_wait(_PyRWMutex *rwmutex, uintptr_t bits)
447
0
{
448
    // Set _Py_HAS_PARKED and wait until we are woken up.
449
0
    if ((bits & _Py_HAS_PARKED) == 0) {
450
0
        uintptr_t newval = bits | _Py_HAS_PARKED;
451
0
        if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
452
0
                                                 &bits, newval)) {
453
0
            return bits;
454
0
        }
455
0
        bits = newval;
456
0
    }
457
458
0
    _PyParkingLot_Park(&rwmutex->bits, &bits, sizeof(bits), -1, NULL, 1);
459
0
    return _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
460
0
}
461
462
// The number of readers holding the lock
463
static uintptr_t
464
rwmutex_reader_count(uintptr_t bits)
465
0
{
466
0
    return bits >> _PyRWMutex_READER_SHIFT;
467
0
}
468
469
void
470
_PyRWMutex_RLock(_PyRWMutex *rwmutex)
471
0
{
472
0
    uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
473
0
    for (;;) {
474
0
        if ((bits & _Py_WRITE_LOCKED)) {
475
            // A writer already holds the lock.
476
0
            bits = rwmutex_set_parked_and_wait(rwmutex, bits);
477
0
            continue;
478
0
        }
479
0
        else if ((bits & _Py_HAS_PARKED)) {
480
            // Reader(s) hold the lock (or just gave up the lock), but there is
481
            // at least one waiting writer. We can't grab the lock because we
482
            // don't want to starve the writer. Instead, we park ourselves and
483
            // wait for the writer to eventually wake us up.
484
0
            bits = rwmutex_set_parked_and_wait(rwmutex, bits);
485
0
            continue;
486
0
        }
487
0
        else {
488
            // The lock is unlocked or read-locked. Try to grab it.
489
0
            assert(rwmutex_reader_count(bits) < _Py_RWMUTEX_MAX_READERS);
490
0
            uintptr_t newval = bits + (1 << _PyRWMutex_READER_SHIFT);
491
0
            if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
492
0
                                                     &bits, newval)) {
493
0
                continue;
494
0
            }
495
0
            return;
496
0
        }
497
0
    }
498
0
}
499
500
void
501
_PyRWMutex_RUnlock(_PyRWMutex *rwmutex)
502
0
{
503
0
    uintptr_t bits = _Py_atomic_add_uintptr(&rwmutex->bits, -(1 << _PyRWMutex_READER_SHIFT));
504
0
    assert(rwmutex_reader_count(bits) > 0 && "lock was not read-locked");
505
0
    bits -= (1 << _PyRWMutex_READER_SHIFT);
506
507
0
    if (rwmutex_reader_count(bits) == 0 && (bits & _Py_HAS_PARKED)) {
508
0
        _PyParkingLot_UnparkAll(&rwmutex->bits);
509
0
        return;
510
0
    }
511
0
}
512
513
void
514
_PyRWMutex_Lock(_PyRWMutex *rwmutex)
515
0
{
516
0
    uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
517
0
    for (;;) {
518
        // If there are no active readers and it's not already write-locked,
519
        // then we can grab the lock.
520
0
        if ((bits & ~_Py_HAS_PARKED) == 0) {
521
0
            if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
522
0
                                                     &bits,
523
0
                                                     bits | _Py_WRITE_LOCKED)) {
524
0
                continue;
525
0
            }
526
0
            return;
527
0
        }
528
529
        // Otherwise, we have to wait.
530
0
        bits = rwmutex_set_parked_and_wait(rwmutex, bits);
531
0
    }
532
0
}
533
534
void
535
_PyRWMutex_Unlock(_PyRWMutex *rwmutex)
536
0
{
537
0
    uintptr_t old_bits = _Py_atomic_exchange_uintptr(&rwmutex->bits, 0);
538
539
0
    assert((old_bits & _Py_WRITE_LOCKED) && "lock was not write-locked");
540
0
    assert(rwmutex_reader_count(old_bits) == 0 && "lock was read-locked");
541
542
0
    if ((old_bits & _Py_HAS_PARKED) != 0) {
543
0
        _PyParkingLot_UnparkAll(&rwmutex->bits);
544
0
    }
545
0
}
546
547
0
#define SEQLOCK_IS_UPDATING(sequence) (sequence & 0x01)
548
549
void _PySeqLock_LockWrite(_PySeqLock *seqlock)
550
0
{
551
    // lock by moving to an odd sequence number
552
0
    uint32_t prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence);
553
0
    while (1) {
554
0
        if (SEQLOCK_IS_UPDATING(prev)) {
555
            // Someone else is currently updating the cache
556
0
            _Py_yield();
557
0
            prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence);
558
0
        }
559
0
        else if (_Py_atomic_compare_exchange_uint32(&seqlock->sequence, &prev, prev + 1)) {
560
            // We've locked the cache
561
0
            _Py_atomic_fence_release();
562
0
            break;
563
0
        }
564
0
        else {
565
0
            _Py_yield();
566
0
        }
567
0
    }
568
0
}
569
570
void _PySeqLock_AbandonWrite(_PySeqLock *seqlock)
571
0
{
572
0
    uint32_t new_seq = _Py_atomic_load_uint32_relaxed(&seqlock->sequence) - 1;
573
0
    assert(!SEQLOCK_IS_UPDATING(new_seq));
574
0
    _Py_atomic_store_uint32(&seqlock->sequence, new_seq);
575
0
}
576
577
void _PySeqLock_UnlockWrite(_PySeqLock *seqlock)
578
0
{
579
0
    uint32_t new_seq = _Py_atomic_load_uint32_relaxed(&seqlock->sequence) + 1;
580
0
    assert(!SEQLOCK_IS_UPDATING(new_seq));
581
0
    _Py_atomic_store_uint32(&seqlock->sequence, new_seq);
582
0
}
583
584
uint32_t _PySeqLock_BeginRead(_PySeqLock *seqlock)
585
0
{
586
0
    uint32_t sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence);
587
0
    while (SEQLOCK_IS_UPDATING(sequence)) {
588
0
        _Py_yield();
589
0
        sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence);
590
0
    }
591
592
0
    return sequence;
593
0
}
594
595
int _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous)
596
0
{
597
    // gh-121368: We need an explicit acquire fence here to ensure that
598
    // this load of the sequence number is not reordered before any loads
599
    // within the read lock.
600
0
    _Py_atomic_fence_acquire();
601
602
0
    if (_Py_atomic_load_uint32_relaxed(&seqlock->sequence) == previous) {
603
0
        return 1;
604
0
    }
605
606
0
    _Py_yield();
607
0
    return 0;
608
0
}
609
610
int _PySeqLock_AfterFork(_PySeqLock *seqlock)
611
0
{
612
    // Synchronize again and validate that the entry hasn't been updated
613
    // while we were readying the values.
614
0
    if (SEQLOCK_IS_UPDATING(seqlock->sequence)) {
615
0
        seqlock->sequence = 0;
616
0
        return 1;
617
0
    }
618
619
0
    return 0;
620
0
}
621
622
#undef PyMutex_Lock
623
void
624
PyMutex_Lock(PyMutex *m)
625
0
{
626
0
    _PyMutex_LockTimed(m, -1, _PY_LOCK_DETACH);
627
0
}
628
629
#undef PyMutex_Unlock
630
void
631
PyMutex_Unlock(PyMutex *m)
632
0
{
633
0
    if (_PyMutex_TryUnlock(m) < 0) {
634
0
        Py_FatalError("unlocking mutex that is not locked");
635
0
    }
636
0
}
637
638
639
#undef PyMutex_IsLocked
640
int
641
PyMutex_IsLocked(PyMutex *m)
642
0
{
643
0
    return _PyMutex_IsLocked(m);
644
0
}