Coverage Report

Created: 2026-05-30 06:18

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