Coverage Report

Created: 2025-11-24 06:11

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