Coverage Report

Created: 2026-10-17 06:15

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
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
765k
{
56
765k
    uint8_t v = _Py_atomic_load_uint8_relaxed(&m->_bits);
57
765k
    if ((v & _Py_LOCKED) == 0) {
58
765k
        if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, v|_Py_LOCKED)) {
59
765k
            return PY_LOCK_ACQUIRED;
60
765k
        }
61
765k
    }
62
8
    if (timeout == 0) {
63
8
        return PY_LOCK_FAILURE;
64
8
    }
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. thread.Lock allows other threads
128
                // to concurrently release the lock so we cannot assert that
129
                // it is locked if _PY_LOCK_PYTHONLOCK is set.
130
0
                assert(_Py_atomic_load_uint8_relaxed(&m->_bits) & _Py_LOCKED ||
131
0
                       (flags & _PY_LOCK_PYTHONLOCK) != 0);
132
0
                return PY_LOCK_ACQUIRED;
133
0
            }
134
0
        }
135
0
        else if (ret == Py_PARK_INTR && (flags & _PY_LOCK_HANDLE_SIGNALS)) {
136
0
            if (Py_MakePendingCalls() < 0) {
137
0
                return PY_LOCK_INTR;
138
0
            }
139
0
        }
140
0
        else if (ret == Py_PARK_INTR && (flags & _PY_FAIL_IF_INTERRUPTED)) {
141
0
            return PY_LOCK_INTR;
142
0
        }
143
0
        else if (ret == Py_PARK_TIMEOUT) {
144
0
            assert(timeout >= 0);
145
0
            return PY_LOCK_FAILURE;
146
0
        }
147
148
0
        if (timeout > 0) {
149
0
            timeout = _PyDeadline_Get(endtime);
150
0
            if (timeout <= 0) {
151
                // Avoid negative values because those mean block forever.
152
0
                timeout = 0;
153
0
            }
154
0
        }
155
156
0
        v = _Py_atomic_load_uint8_relaxed(&m->_bits);
157
0
    }
158
0
}
159
160
static void
161
mutex_unpark(void *arg, void *park_arg, int has_more_waiters)
162
0
{
163
0
    PyMutex *m = (PyMutex*)arg;
164
0
    struct mutex_entry *entry = (struct mutex_entry*)park_arg;
165
0
    uint8_t v = 0;
166
0
    if (entry) {
167
0
        PyTime_t now;
168
        // silently ignore error: cannot report error to the caller
169
0
        (void)PyTime_MonotonicRaw(&now);
170
0
        int should_be_fair = now > entry->time_to_be_fair;
171
172
0
        entry->handed_off = should_be_fair;
173
0
        if (should_be_fair) {
174
0
            v |= _Py_LOCKED;
175
0
        }
176
0
        if (has_more_waiters) {
177
0
            v |= _Py_HAS_PARKED;
178
0
        }
179
0
    }
180
0
    _Py_atomic_store_uint8(&m->_bits, v);
181
0
}
182
183
int
184
_PyMutex_TryUnlock(PyMutex *m)
185
406
{
186
406
    uint8_t v = _Py_atomic_load_uint8(&m->_bits);
187
406
    for (;;) {
188
406
        if ((v & _Py_LOCKED) == 0) {
189
            // error: the mutex is not locked
190
0
            return -1;
191
0
        }
192
406
        else if ((v & _Py_HAS_PARKED)) {
193
            // wake up a single thread
194
0
            _PyParkingLot_Unpark(&m->_bits, mutex_unpark, m);
195
0
            return 0;
196
0
        }
197
406
        else if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, _Py_UNLOCKED)) {
198
            // fast-path: no waiters
199
406
            return 0;
200
406
        }
201
406
    }
202
406
}
203
204
// _PyRawMutex stores a linked list of `struct raw_mutex_entry`, one for each
205
// thread waiting on the mutex, directly in the mutex itself.
206
struct raw_mutex_entry {
207
    struct raw_mutex_entry *next;
208
    _PySemaphore sema;
209
};
210
211
void
212
_PyRawMutex_LockSlow(_PyRawMutex *m)
213
0
{
214
0
    struct raw_mutex_entry waiter;
215
0
    _PySemaphore_Init(&waiter.sema);
216
217
0
    uintptr_t v = _Py_atomic_load_uintptr(&m->v);
218
0
    for (;;) {
219
0
        if ((v & _Py_LOCKED) == 0) {
220
            // Unlocked: try to grab it (even if it has a waiter).
221
0
            if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, v|_Py_LOCKED)) {
222
0
                break;
223
0
            }
224
0
            continue;
225
0
        }
226
227
        // Locked: try to add ourselves as a waiter.
228
0
        waiter.next = (struct raw_mutex_entry *)(v & ~1);
229
0
        uintptr_t desired = ((uintptr_t)&waiter)|_Py_LOCKED;
230
0
        if (!_Py_atomic_compare_exchange_uintptr(&m->v, &v, desired)) {
231
0
            continue;
232
0
        }
233
234
        // Wait for us to be woken up. Note that we still have to lock the
235
        // mutex ourselves: it is NOT handed off to us.
236
        //
237
        // Loop until we observe an actual wakeup. A return of Py_PARK_INTR
238
        // could otherwise let us exit _PySemaphore_Wait and destroy
239
        // `waiter.sema` while _PyRawMutex_UnlockSlow's matching
240
        // _PySemaphore_Wakeup is still pending, since the unlocker has
241
        // already CAS-removed us from the waiter list without any handshake.
242
0
        int res;
243
0
        do {
244
0
            res = _PySemaphore_Wait(&waiter.sema, -1);
245
0
        } while (res != Py_PARK_OK);
246
0
    }
247
248
0
    _PySemaphore_Destroy(&waiter.sema);
249
0
}
250
251
void
252
_PyRawMutex_UnlockSlow(_PyRawMutex *m)
253
0
{
254
0
    uintptr_t v = _Py_atomic_load_uintptr(&m->v);
255
0
    for (;;) {
256
0
        if ((v & _Py_LOCKED) == 0) {
257
0
            Py_FatalError("unlocking mutex that is not locked");
258
0
        }
259
260
0
        struct raw_mutex_entry *waiter = (struct raw_mutex_entry *)(v & ~1);
261
0
        if (waiter) {
262
0
            uintptr_t next_waiter = (uintptr_t)waiter->next;
263
0
            if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, next_waiter)) {
264
0
                _PySemaphore_Wakeup(&waiter->sema);
265
0
                return;
266
0
            }
267
0
        }
268
0
        else {
269
0
            if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, _Py_UNLOCKED)) {
270
0
                return;
271
0
            }
272
0
        }
273
0
    }
274
0
}
275
276
int
277
_PyEvent_IsSet(PyEvent *evt)
278
0
{
279
0
    uint8_t v = _Py_atomic_load_uint8(&evt->v);
280
0
    return v == _Py_LOCKED;
281
0
}
282
283
void
284
_PyEvent_Notify(PyEvent *evt)
285
0
{
286
0
    uintptr_t v = _Py_atomic_exchange_uint8(&evt->v, _Py_LOCKED);
287
0
    if (v == _Py_UNLOCKED) {
288
        // no waiters
289
0
        return;
290
0
    }
291
0
    else if (v == _Py_LOCKED) {
292
        // event already set
293
0
        return;
294
0
    }
295
0
    else {
296
0
        assert(v == _Py_HAS_PARKED);
297
0
        _PyParkingLot_UnparkAll(&evt->v);
298
0
    }
299
0
}
300
301
void
302
PyEvent_Wait(PyEvent *evt)
303
0
{
304
0
    while (!PyEvent_WaitTimed(evt, -1, /*detach=*/1))
305
0
        ;
306
0
}
307
308
int
309
PyEvent_WaitTimed(PyEvent *evt, PyTime_t timeout_ns, int detach)
310
0
{
311
0
    for (;;) {
312
0
        uint8_t v = _Py_atomic_load_uint8(&evt->v);
313
0
        if (v == _Py_LOCKED) {
314
            // event already set
315
0
            return 1;
316
0
        }
317
0
        if (v == _Py_UNLOCKED) {
318
0
            if (!_Py_atomic_compare_exchange_uint8(&evt->v, &v, _Py_HAS_PARKED)) {
319
0
                continue;
320
0
            }
321
0
        }
322
323
0
        uint8_t expected = _Py_HAS_PARKED;
324
0
        (void) _PyParkingLot_Park(&evt->v, &expected, sizeof(evt->v),
325
0
                                  timeout_ns, NULL, detach);
326
327
0
        return _Py_atomic_load_uint8(&evt->v) == _Py_LOCKED;
328
0
    }
329
0
}
330
331
static int
332
unlock_once(_PyOnceFlag *o, int res)
333
266
{
334
    // On success (res=0), we set the state to _Py_ONCE_INITIALIZED.
335
    // On failure (res=-1), we reset the state to _Py_UNLOCKED.
336
266
    uint8_t new_value;
337
266
    switch (res) {
338
0
        case -1: new_value = _Py_UNLOCKED; break;
339
266
        case  0: new_value = _Py_ONCE_INITIALIZED; break;
340
0
        default: {
341
0
            Py_FatalError("invalid result from _PyOnceFlag_CallOnce");
342
0
            Py_UNREACHABLE();
343
0
            break;
344
0
        }
345
266
    }
346
347
266
    uint8_t old_value = _Py_atomic_exchange_uint8(&o->v, new_value);
348
266
    if ((old_value & _Py_HAS_PARKED) != 0) {
349
        // wake up anyone waiting on the once flag
350
0
        _PyParkingLot_UnparkAll(&o->v);
351
0
    }
352
266
    return res;
353
266
}
354
355
int
356
_PyOnceFlag_CallOnceSlow(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg)
357
266
{
358
266
    uint8_t v = _Py_atomic_load_uint8(&flag->v);
359
266
    for (;;) {
360
266
        if (v == _Py_UNLOCKED) {
361
266
            if (!_Py_atomic_compare_exchange_uint8(&flag->v, &v, _Py_LOCKED)) {
362
0
                continue;
363
0
            }
364
266
            int res = fn(arg);
365
266
            return unlock_once(flag, res);
366
266
        }
367
368
0
        if (v == _Py_ONCE_INITIALIZED) {
369
0
            return 0;
370
0
        }
371
372
        // The once flag is initializing (locked).
373
0
        assert((v & _Py_LOCKED));
374
0
        if (!(v & _Py_HAS_PARKED)) {
375
            // We are the first waiter. Set the _Py_HAS_PARKED flag.
376
0
            uint8_t new_value = v | _Py_HAS_PARKED;
377
0
            if (!_Py_atomic_compare_exchange_uint8(&flag->v, &v, new_value)) {
378
0
                continue;
379
0
            }
380
0
            v = new_value;
381
0
        }
382
383
        // Wait for initialization to finish.
384
0
        _PyParkingLot_Park(&flag->v, &v, sizeof(v), -1, NULL, 1);
385
0
        v = _Py_atomic_load_uint8(&flag->v);
386
0
    }
387
266
}
388
389
static int
390
recursive_mutex_is_owned_by(_PyRecursiveMutex *m, PyThread_ident_t tid)
391
2.61M
{
392
2.61M
    return _Py_atomic_load_ullong_relaxed(&m->thread) == tid;
393
2.61M
}
394
395
int
396
_PyRecursiveMutex_IsLockedByCurrentThread(_PyRecursiveMutex *m)
397
60.8k
{
398
60.8k
    return recursive_mutex_is_owned_by(m, PyThread_get_thread_ident_ex());
399
60.8k
}
400
401
void
402
_PyRecursiveMutex_Lock(_PyRecursiveMutex *m)
403
1.09M
{
404
1.09M
    PyThread_ident_t thread = PyThread_get_thread_ident_ex();
405
1.09M
    if (recursive_mutex_is_owned_by(m, thread)) {
406
0
        m->level++;
407
0
        return;
408
0
    }
409
1.09M
    PyMutex_Lock(&m->mutex);
410
1.09M
    _Py_atomic_store_ullong_relaxed(&m->thread, thread);
411
1.09M
    assert(m->level == 0);
412
1.09M
}
413
414
PyLockStatus
415
_PyRecursiveMutex_LockTimed(_PyRecursiveMutex *m, PyTime_t timeout, _PyLockFlags flags)
416
183k
{
417
183k
    PyThread_ident_t thread = PyThread_get_thread_ident_ex();
418
183k
    if (recursive_mutex_is_owned_by(m, thread)) {
419
10
        m->level++;
420
10
        return PY_LOCK_ACQUIRED;
421
10
    }
422
183k
    PyLockStatus s = _PyMutex_LockTimed(&m->mutex, timeout, flags);
423
183k
    if (s == PY_LOCK_ACQUIRED) {
424
183k
        _Py_atomic_store_ullong_relaxed(&m->thread, thread);
425
183k
        assert(m->level == 0);
426
183k
    }
427
183k
    return s;
428
183k
}
429
430
void
431
_PyRecursiveMutex_Unlock(_PyRecursiveMutex *m)
432
60.8k
{
433
60.8k
    if (_PyRecursiveMutex_TryUnlock(m) < 0) {
434
0
        Py_FatalError("unlocking a recursive mutex that is not "
435
0
                      "owned by the current thread");
436
0
    }
437
60.8k
}
438
439
int
440
_PyRecursiveMutex_TryUnlock(_PyRecursiveMutex *m)
441
1.27M
{
442
1.27M
    PyThread_ident_t thread = PyThread_get_thread_ident_ex();
443
1.27M
    if (!recursive_mutex_is_owned_by(m, thread)) {
444
0
        return -1;
445
0
    }
446
1.27M
    if (m->level > 0) {
447
10
        m->level--;
448
10
        return 0;
449
10
    }
450
1.27M
    assert(m->level == 0);
451
1.27M
    _Py_atomic_store_ullong_relaxed(&m->thread, 0);
452
1.27M
    PyMutex_Unlock(&m->mutex);
453
1.27M
    return 0;
454
1.27M
}
455
456
#ifdef HAVE_FORK
457
// Reinitialize the mutex in the child process after fork(). The caller must
458
// record before fork() whether the forking thread owns the mutex, since the
459
// thread ident can change in the child (gh-126688). If it does, the child
460
// keeps the mutex with the same recursion level; otherwise the owner and any
461
// waiters no longer exist in the child and the mutex is reset.
462
void
463
_PyRecursiveMutex_at_fork_reinit(_PyRecursiveMutex *m, int owned)
464
0
{
465
0
    if (owned) {
466
0
        m->mutex = (PyMutex){._bits = _Py_LOCKED};
467
0
        _Py_atomic_store_ullong_relaxed(&m->thread, PyThread_get_thread_ident_ex());
468
0
    }
469
0
    else {
470
0
        memset(m, 0, sizeof(*m));
471
0
    }
472
0
}
473
#endif
474
475
0
#define _Py_WRITE_LOCKED 1
476
0
#define _PyRWMutex_READER_SHIFT 2
477
#define _Py_RWMUTEX_MAX_READERS (UINTPTR_MAX >> _PyRWMutex_READER_SHIFT)
478
479
static uintptr_t
480
rwmutex_set_parked_and_wait(_PyRWMutex *rwmutex, uintptr_t bits)
481
0
{
482
    // Set _Py_HAS_PARKED and wait until we are woken up.
483
0
    if ((bits & _Py_HAS_PARKED) == 0) {
484
0
        uintptr_t newval = bits | _Py_HAS_PARKED;
485
0
        if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
486
0
                                                 &bits, newval)) {
487
0
            return bits;
488
0
        }
489
0
        bits = newval;
490
0
    }
491
492
0
    _PyParkingLot_Park(&rwmutex->bits, &bits, sizeof(bits), -1, NULL, 1);
493
0
    return _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
494
0
}
495
496
// The number of readers holding the lock
497
static uintptr_t
498
rwmutex_reader_count(uintptr_t bits)
499
0
{
500
0
    return bits >> _PyRWMutex_READER_SHIFT;
501
0
}
502
503
void
504
_PyRWMutex_RLock(_PyRWMutex *rwmutex)
505
0
{
506
0
    uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
507
0
    for (;;) {
508
0
        if ((bits & _Py_WRITE_LOCKED)) {
509
            // A writer already holds the lock.
510
0
            bits = rwmutex_set_parked_and_wait(rwmutex, bits);
511
0
            continue;
512
0
        }
513
0
        else if ((bits & _Py_HAS_PARKED)) {
514
            // Reader(s) hold the lock (or just gave up the lock), but there is
515
            // at least one waiting writer. We can't grab the lock because we
516
            // don't want to starve the writer. Instead, we park ourselves and
517
            // wait for the writer to eventually wake us up.
518
0
            bits = rwmutex_set_parked_and_wait(rwmutex, bits);
519
0
            continue;
520
0
        }
521
0
        else {
522
            // The lock is unlocked or read-locked. Try to grab it.
523
0
            assert(rwmutex_reader_count(bits) < _Py_RWMUTEX_MAX_READERS);
524
0
            uintptr_t newval = bits + (1 << _PyRWMutex_READER_SHIFT);
525
0
            if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
526
0
                                                     &bits, newval)) {
527
0
                continue;
528
0
            }
529
0
            return;
530
0
        }
531
0
    }
532
0
}
533
534
void
535
_PyRWMutex_RUnlock(_PyRWMutex *rwmutex)
536
0
{
537
0
    uintptr_t bits = _Py_atomic_add_uintptr(&rwmutex->bits, -(1 << _PyRWMutex_READER_SHIFT));
538
0
    assert(rwmutex_reader_count(bits) > 0 && "lock was not read-locked");
539
0
    bits -= (1 << _PyRWMutex_READER_SHIFT);
540
541
0
    if (rwmutex_reader_count(bits) == 0 && (bits & _Py_HAS_PARKED)) {
542
0
        _PyParkingLot_UnparkAll(&rwmutex->bits);
543
0
        return;
544
0
    }
545
0
}
546
547
void
548
_PyRWMutex_Lock(_PyRWMutex *rwmutex)
549
0
{
550
0
    uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
551
0
    for (;;) {
552
        // If there are no active readers and it's not already write-locked,
553
        // then we can grab the lock.
554
0
        if ((bits & ~_Py_HAS_PARKED) == 0) {
555
0
            if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
556
0
                                                     &bits,
557
0
                                                     bits | _Py_WRITE_LOCKED)) {
558
0
                continue;
559
0
            }
560
0
            return;
561
0
        }
562
563
        // Otherwise, we have to wait.
564
0
        bits = rwmutex_set_parked_and_wait(rwmutex, bits);
565
0
    }
566
0
}
567
568
void
569
_PyRWMutex_Unlock(_PyRWMutex *rwmutex)
570
0
{
571
0
    uintptr_t old_bits = _Py_atomic_exchange_uintptr(&rwmutex->bits, 0);
572
573
0
    assert((old_bits & _Py_WRITE_LOCKED) && "lock was not write-locked");
574
0
    assert(rwmutex_reader_count(old_bits) == 0 && "lock was read-locked");
575
576
0
    if ((old_bits & _Py_HAS_PARKED) != 0) {
577
0
        _PyParkingLot_UnparkAll(&rwmutex->bits);
578
0
    }
579
0
}
580
581
#undef PyMutex_Lock
582
void
583
PyMutex_Lock(PyMutex *m)
584
0
{
585
0
    _PyMutex_LockTimed(m, -1, _PY_LOCK_DETACH);
586
0
}
587
588
#undef PyMutex_Unlock
589
void
590
PyMutex_Unlock(PyMutex *m)
591
0
{
592
0
    if (_PyMutex_TryUnlock(m) < 0) {
593
0
        Py_FatalError("unlocking mutex that is not locked");
594
0
    }
595
0
}
596
597
598
#undef PyMutex_IsLocked
599
int
600
PyMutex_IsLocked(PyMutex *m)
601
0
{
602
0
    return _PyMutex_IsLocked(m);
603
0
}