Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/parking_lot.c
Line
Count
Source
1
#include "Python.h"
2
3
#include "pycore_llist.h"
4
#include "pycore_lock.h"          // _PyRawMutex
5
#include "pycore_parking_lot.h"
6
#include "pycore_pyerrors.h"      // _Py_FatalErrorFormat
7
#include "pycore_pystate.h"       // _PyThreadState_GET
8
#include "pycore_semaphore.h"     // _PySemaphore
9
#include "pycore_time.h"          // _PyTime_Add()
10
11
#include <stdbool.h>
12
13
#if defined(_Py_USE_SEMAPHORES) && defined(CLOCK_MONOTONIC) && !defined(_Py_THREAD_SANITIZER)
14
#  ifdef HAVE_SEM_CLOCKWAIT
15
0
#    define _Py_SEM_CLOCKWAIT_AVAILABLE 1
16
#  elif defined(__linux__) && (defined(__GNUC__) || defined(__clang__))
17
/* Older glibc headers do not declare sem_clockwait. A weak reference allows
18
   such builds to use monotonic waits on newer runtimes. */
19
extern int sem_clockwait(sem_t *, clockid_t, const struct timespec *)
20
    __attribute__((weak));
21
#    define _Py_SEM_CLOCKWAIT_AVAILABLE (sem_clockwait != NULL)
22
#  endif
23
#endif
24
25
26
typedef struct {
27
    // The mutex protects the waiter queue and the num_waiters counter.
28
    _PyRawMutex mutex;
29
30
    // Linked list of `struct wait_entry` waiters in this bucket.
31
    struct llist_node root;
32
    size_t num_waiters;
33
} Bucket;
34
35
struct wait_entry {
36
    void *park_arg;
37
    uintptr_t addr;
38
    _PySemaphore sema;
39
    struct llist_node node;
40
    bool is_unparking;
41
};
42
43
// Prime number to avoid correlations with memory addresses.
44
// We want this to be roughly proportional to the number of CPU cores
45
// to minimize contention on the bucket locks, but not too big to avoid
46
// wasting memory. The exact choice does not matter much.
47
0
#define NUM_BUCKETS 257
48
49
#define BUCKET_INIT(b, i) [i] = { .root = LLIST_INIT(b[i].root) }
50
#define BUCKET_INIT_2(b, i)   BUCKET_INIT(b, i),     BUCKET_INIT(b, i+1)
51
#define BUCKET_INIT_4(b, i)   BUCKET_INIT_2(b, i),   BUCKET_INIT_2(b, i+2)
52
#define BUCKET_INIT_8(b, i)   BUCKET_INIT_4(b, i),   BUCKET_INIT_4(b, i+4)
53
#define BUCKET_INIT_16(b, i)  BUCKET_INIT_8(b, i),   BUCKET_INIT_8(b, i+8)
54
#define BUCKET_INIT_32(b, i)  BUCKET_INIT_16(b, i),  BUCKET_INIT_16(b, i+16)
55
#define BUCKET_INIT_64(b, i)  BUCKET_INIT_32(b, i),  BUCKET_INIT_32(b, i+32)
56
#define BUCKET_INIT_128(b, i) BUCKET_INIT_64(b, i),  BUCKET_INIT_64(b, i+64)
57
#define BUCKET_INIT_256(b, i) BUCKET_INIT_128(b, i), BUCKET_INIT_128(b, i+128)
58
59
// Table of waiters (hashed by address)
60
static Bucket buckets[NUM_BUCKETS] = {
61
    BUCKET_INIT_256(buckets, 0),
62
    BUCKET_INIT(buckets, 256),
63
};
64
65
void
66
_PySemaphore_Init(_PySemaphore *sema)
67
0
{
68
#if defined(MS_WINDOWS)
69
    sema->platform_sem = CreateSemaphore(
70
        NULL,   //  attributes
71
        0,      //  initial count
72
        10,     //  maximum count
73
        NULL    //  unnamed
74
    );
75
    if (!sema->platform_sem) {
76
        _Py_FatalErrorFormat(__func__,
77
            "parking_lot: CreateSemaphore failed (error: %u)",
78
            GetLastError());
79
    }
80
#elif defined(_Py_USE_SEMAPHORES)
81
0
    if (sem_init(&sema->platform_sem, /*pshared=*/0, /*value=*/0) < 0) {
82
0
        Py_FatalError("parking_lot: sem_init failed");
83
0
    }
84
#else
85
    if (pthread_mutex_init(&sema->mutex, NULL) != 0) {
86
        Py_FatalError("parking_lot: pthread_mutex_init failed");
87
    }
88
    if (pthread_cond_init(&sema->cond, NULL)) {
89
        Py_FatalError("parking_lot: pthread_cond_init failed");
90
    }
91
    sema->counter = 0;
92
#endif
93
0
}
94
95
void
96
_PySemaphore_Destroy(_PySemaphore *sema)
97
0
{
98
#if defined(MS_WINDOWS)
99
    CloseHandle(sema->platform_sem);
100
#elif defined(_Py_USE_SEMAPHORES)
101
    sem_destroy(&sema->platform_sem);
102
#else
103
    pthread_mutex_destroy(&sema->mutex);
104
    pthread_cond_destroy(&sema->cond);
105
#endif
106
0
}
107
108
int
109
_PySemaphore_Wait(_PySemaphore *sema, PyTime_t timeout)
110
0
{
111
0
    int res;
112
#if defined(MS_WINDOWS)
113
    DWORD wait;
114
    DWORD millis = 0;
115
    if (timeout < 0) {
116
        millis = INFINITE;
117
    }
118
    else {
119
        PyTime_t div = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
120
        // Prevent overflow with clamping the result
121
        if ((PyTime_t)PY_DWORD_MAX < div) {
122
            millis = PY_DWORD_MAX;
123
        }
124
        else {
125
            millis = (DWORD) div;
126
        }
127
    }
128
129
    HANDLE handles[2] = { sema->platform_sem, NULL };
130
    HANDLE sigint_event = NULL;
131
    DWORD count = 1;
132
    if (_Py_IsMainThread()) {
133
        // gh-135099: Wait on the SIGINT event only in the main thread. Other
134
        // threads would ignore the result anyways, and accessing
135
        // `_PyOS_SigintEvent()` from non-main threads may race with
136
        // interpreter shutdown, which closes the event handle. Note that
137
        // non-main interpreters will ignore the result.
138
        sigint_event = _PyOS_SigintEvent();
139
        if (sigint_event != NULL) {
140
            handles[1] = sigint_event;
141
            count = 2;
142
        }
143
    }
144
    wait = WaitForMultipleObjects(count, handles, FALSE, millis);
145
    if (wait == WAIT_OBJECT_0) {
146
        res = Py_PARK_OK;
147
    }
148
    else if (wait == WAIT_OBJECT_0 + 1) {
149
        assert(sigint_event != NULL);
150
        ResetEvent(sigint_event);
151
        res = Py_PARK_INTR;
152
    }
153
    else if (wait == WAIT_TIMEOUT) {
154
        res = Py_PARK_TIMEOUT;
155
    }
156
    else {
157
        _Py_FatalErrorFormat(__func__,
158
            "unexpected error from semaphore: %u (error: %u, handle: %p)",
159
            wait, GetLastError(), sema->platform_sem);
160
    }
161
#elif defined(_Py_USE_SEMAPHORES)
162
    int err;
163
0
    if (timeout >= 0) {
164
0
        struct timespec ts;
165
166
0
#ifdef _Py_SEM_CLOCKWAIT_AVAILABLE
167
0
        if (_Py_SEM_CLOCKWAIT_AVAILABLE) {
168
0
            PyTime_t now;
169
            // Silently ignore error: cannot report error to the caller.
170
0
            (void)PyTime_MonotonicRaw(&now);
171
0
            PyTime_t deadline = _PyTime_Add(now, timeout);
172
0
            _PyTime_AsTimespec_clamp(deadline, &ts);
173
174
0
            err = sem_clockwait(&sema->platform_sem, CLOCK_MONOTONIC, &ts);
175
0
        }
176
0
        else
177
0
#endif
178
0
        {
179
0
            PyTime_t now;
180
            // Silently ignore error: cannot report error to the caller.
181
0
            (void)PyTime_TimeRaw(&now);
182
0
            PyTime_t deadline = _PyTime_Add(now, timeout);
183
0
            _PyTime_AsTimespec_clamp(deadline, &ts);
184
185
0
            err = sem_timedwait(&sema->platform_sem, &ts);
186
0
        }
187
0
    }
188
0
    else {
189
0
        err = sem_wait(&sema->platform_sem);
190
0
    }
191
0
    if (err == -1) {
192
0
        err = errno;
193
0
        if (err == EINTR) {
194
0
            res = Py_PARK_INTR;
195
0
        }
196
0
        else if (err == ETIMEDOUT) {
197
0
            res = Py_PARK_TIMEOUT;
198
0
        }
199
0
        else {
200
0
            _Py_FatalErrorFormat(__func__,
201
0
                "unexpected error from semaphore: %d",
202
0
                err);
203
0
        }
204
0
    }
205
0
    else {
206
0
        res = Py_PARK_OK;
207
0
    }
208
#else
209
    pthread_mutex_lock(&sema->mutex);
210
    int err = 0;
211
    if (sema->counter == 0) {
212
        if (timeout >= 0) {
213
            struct timespec ts;
214
#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP)
215
            _PyTime_AsTimespec_clamp(timeout, &ts);
216
            err = pthread_cond_timedwait_relative_np(&sema->cond, &sema->mutex, &ts);
217
#else
218
            PyTime_t now;
219
            (void)PyTime_TimeRaw(&now);
220
            PyTime_t deadline = _PyTime_Add(now, timeout);
221
            _PyTime_AsTimespec_clamp(deadline, &ts);
222
223
            err = pthread_cond_timedwait(&sema->cond, &sema->mutex, &ts);
224
#endif // HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP
225
        }
226
        else {
227
            err = pthread_cond_wait(&sema->cond, &sema->mutex);
228
        }
229
    }
230
    if (sema->counter > 0) {
231
        sema->counter--;
232
        res = Py_PARK_OK;
233
    }
234
    else if (err) {
235
        res = Py_PARK_TIMEOUT;
236
    }
237
    else {
238
        res = Py_PARK_INTR;
239
    }
240
    pthread_mutex_unlock(&sema->mutex);
241
#endif
242
0
    return res;
243
0
}
244
245
void
246
_PySemaphore_Wakeup(_PySemaphore *sema)
247
0
{
248
#if defined(MS_WINDOWS)
249
    if (!ReleaseSemaphore(sema->platform_sem, 1, NULL)) {
250
        _Py_FatalErrorFormat(__func__,
251
            "parking_lot: ReleaseSemaphore failed (error: %u, handle: %p)",
252
            GetLastError(), sema->platform_sem);
253
    }
254
#elif defined(_Py_USE_SEMAPHORES)
255
    int err = sem_post(&sema->platform_sem);
256
0
    if (err != 0) {
257
0
        Py_FatalError("parking_lot: sem_post failed");
258
0
    }
259
#else
260
    pthread_mutex_lock(&sema->mutex);
261
    sema->counter++;
262
    pthread_cond_signal(&sema->cond);
263
    pthread_mutex_unlock(&sema->mutex);
264
#endif
265
0
}
266
267
static void
268
enqueue(Bucket *bucket, const void *address, struct wait_entry *wait)
269
0
{
270
0
    llist_insert_tail(&bucket->root, &wait->node);
271
0
    ++bucket->num_waiters;
272
0
}
273
274
static struct wait_entry *
275
dequeue(Bucket *bucket, const void *address)
276
0
{
277
    // find the first waiter that is waiting on `address`
278
0
    struct llist_node *root = &bucket->root;
279
0
    struct llist_node *node;
280
0
    llist_for_each(node, root) {
281
0
        struct wait_entry *wait = llist_data(node, struct wait_entry, node);
282
0
        if (wait->addr == (uintptr_t)address) {
283
0
            llist_remove(node);
284
0
            --bucket->num_waiters;
285
0
            wait->is_unparking = true;
286
0
            return wait;
287
0
        }
288
0
    }
289
0
    return NULL;
290
0
}
291
292
static void
293
dequeue_all(Bucket *bucket, const void *address, struct llist_node *dst)
294
0
{
295
    // remove and append all matching waiters to dst
296
0
    struct llist_node *root = &bucket->root;
297
0
    struct llist_node *node;
298
0
    llist_for_each_safe(node, root) {
299
0
        struct wait_entry *wait = llist_data(node, struct wait_entry, node);
300
0
        if (wait->addr == (uintptr_t)address) {
301
0
            llist_remove(node);
302
0
            llist_insert_tail(dst, node);
303
0
            --bucket->num_waiters;
304
0
            wait->is_unparking = true;
305
0
        }
306
0
    }
307
0
}
308
309
// Checks that `*addr == *expected` (only works for 1, 2, 4, or 8 bytes)
310
static int
311
atomic_memcmp(const void *addr, const void *expected, size_t addr_size)
312
0
{
313
0
    switch (addr_size) {
314
0
    case 1: return _Py_atomic_load_uint8(addr) == *(const uint8_t *)expected;
315
0
    case 2: return _Py_atomic_load_uint16(addr) == *(const uint16_t *)expected;
316
0
    case 4: return _Py_atomic_load_uint32(addr) == *(const uint32_t *)expected;
317
0
    case 8: return _Py_atomic_load_uint64(addr) == *(const uint64_t *)expected;
318
0
    default: Py_UNREACHABLE();
319
0
    }
320
0
}
321
322
int
323
_PyParkingLot_Park(const void *addr, const void *expected, size_t size,
324
                   PyTime_t timeout_ns, void *park_arg, int detach)
325
0
{
326
0
    struct wait_entry wait = {
327
0
        .park_arg = park_arg,
328
0
        .addr = (uintptr_t)addr,
329
0
        .is_unparking = false,
330
0
    };
331
332
0
    Bucket *bucket = &buckets[((uintptr_t)addr) % NUM_BUCKETS];
333
334
0
    _PyRawMutex_Lock(&bucket->mutex);
335
0
    if (!atomic_memcmp(addr, expected, size)) {
336
0
        _PyRawMutex_Unlock(&bucket->mutex);
337
0
        return Py_PARK_AGAIN;
338
0
    }
339
0
    _PySemaphore_Init(&wait.sema);
340
0
    enqueue(bucket, addr, &wait);
341
0
    _PyRawMutex_Unlock(&bucket->mutex);
342
343
0
    PyThreadState *tstate = NULL;
344
0
    if (detach) {
345
0
        tstate = _PyThreadState_GET();
346
0
        if (tstate && _PyThreadState_IsAttached(tstate)) {
347
            // Only detach if we are attached
348
0
            PyEval_ReleaseThread(tstate);
349
0
        }
350
0
        else {
351
0
            tstate = NULL;
352
0
        }
353
0
    }
354
355
0
    int res = _PySemaphore_Wait(&wait.sema, timeout_ns);
356
0
    if (res == Py_PARK_OK) {
357
0
        goto done;
358
0
    }
359
360
    // timeout or interrupt
361
0
    _PyRawMutex_Lock(&bucket->mutex);
362
0
    if (wait.is_unparking) {
363
0
        _PyRawMutex_Unlock(&bucket->mutex);
364
        // Another thread has started to unpark us. Wait until we process the
365
        // wakeup signal.
366
0
        do {
367
0
            res = _PySemaphore_Wait(&wait.sema, -1);
368
0
        } while (res != Py_PARK_OK);
369
0
        goto done;
370
0
    }
371
0
    else {
372
0
        llist_remove(&wait.node);
373
0
        --bucket->num_waiters;
374
0
    }
375
0
    _PyRawMutex_Unlock(&bucket->mutex);
376
377
0
done:
378
0
    _PySemaphore_Destroy(&wait.sema);
379
0
    if (tstate) {
380
0
        PyEval_AcquireThread(tstate);
381
0
    }
382
0
    return res;
383
384
0
}
385
386
void
387
_PyParkingLot_Unpark(const void *addr, _Py_unpark_fn_t *fn, void *arg)
388
0
{
389
0
    Bucket *bucket = &buckets[((uintptr_t)addr) % NUM_BUCKETS];
390
391
    // Find the first waiter that is waiting on `addr`
392
0
    _PyRawMutex_Lock(&bucket->mutex);
393
0
    struct wait_entry *waiter = dequeue(bucket, addr);
394
0
    if (waiter) {
395
0
        int has_more_waiters = (bucket->num_waiters > 0);
396
0
        fn(arg, waiter->park_arg, has_more_waiters);
397
0
    }
398
0
    else {
399
0
        fn(arg, NULL, 0);
400
0
    }
401
0
    _PyRawMutex_Unlock(&bucket->mutex);
402
403
0
    if (waiter) {
404
        // Wakeup the waiter outside of the bucket lock
405
0
        _PySemaphore_Wakeup(&waiter->sema);
406
0
    }
407
0
}
408
409
void
410
_PyParkingLot_UnparkAll(const void *addr)
411
0
{
412
0
    struct llist_node head = LLIST_INIT(head);
413
0
    Bucket *bucket = &buckets[((uintptr_t)addr) % NUM_BUCKETS];
414
415
0
    _PyRawMutex_Lock(&bucket->mutex);
416
0
    dequeue_all(bucket, addr, &head);
417
0
    _PyRawMutex_Unlock(&bucket->mutex);
418
419
0
    struct llist_node *node;
420
0
    llist_for_each_safe(node, &head) {
421
0
        struct wait_entry *waiter = llist_data(node, struct wait_entry, node);
422
0
        llist_remove(node);
423
0
        _PySemaphore_Wakeup(&waiter->sema);
424
0
    }
425
0
}
426
427
void
428
_PyParkingLot_AfterFork(void)
429
0
{
430
    // After a fork only one thread remains. That thread cannot be blocked
431
    // so all entries in the parking lot are for dead threads.
432
0
    memset(buckets, 0, sizeof(buckets));
433
0
    for (Py_ssize_t i = 0; i < NUM_BUCKETS; i++) {
434
0
        llist_init(&buckets[i].root);
435
0
    }
436
0
}