Coverage Report

Created: 2026-06-21 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/context.c
Line
Count
Source
1
#include "Python.h"
2
#include "pycore_call.h"          // _PyObject_VectorcallTstate()
3
#include "pycore_context.h"
4
#include "pycore_freelist.h"      // _Py_FREELIST_FREE(), _Py_FREELIST_POP()
5
#include "pycore_gc.h"            // _PyObject_GC_MAY_BE_TRACKED()
6
#include "pycore_hamt.h"
7
#include "pycore_initconfig.h"    // _PyStatus_OK()
8
#include "pycore_object.h"
9
#include "pycore_pyerrors.h"
10
#include "pycore_pystate.h"       // _PyThreadState_GET()
11
12
13
14
#include "clinic/context.c.h"
15
/*[clinic input]
16
module _contextvars
17
[clinic start generated code]*/
18
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=a0955718c8b8cea6]*/
19
20
21
#define ENSURE_Context(o, err_ret)                                  \
22
0
    if (!PyContext_CheckExact(o)) {                                 \
23
0
        PyErr_SetString(PyExc_TypeError,                            \
24
0
                        "an instance of Context was expected");     \
25
0
        return err_ret;                                             \
26
0
    }
27
28
#define ENSURE_ContextVar(o, err_ret)                               \
29
276k
    if (!PyContextVar_CheckExact(o)) {                              \
30
0
        PyErr_SetString(PyExc_TypeError,                            \
31
0
                       "an instance of ContextVar was expected");   \
32
0
        return err_ret;                                             \
33
0
    }
34
35
#define ENSURE_ContextToken(o, err_ret)                             \
36
0
    if (!PyContextToken_CheckExact(o)) {                            \
37
0
        PyErr_SetString(PyExc_TypeError,                            \
38
0
                        "an instance of Token was expected");       \
39
0
        return err_ret;                                             \
40
0
    }
41
42
43
/////////////////////////// Context API
44
45
46
static PyContext *
47
context_new_empty(void);
48
49
static PyContext *
50
context_new_from_vars(PyHamtObject *vars);
51
52
static inline PyContext *
53
context_get(void);
54
55
static PyContextToken *
56
token_new(PyContext *ctx, PyContextVar *var, PyObject *val);
57
58
static PyContextVar *
59
contextvar_new(PyObject *name, PyObject *def);
60
61
static int
62
contextvar_set(PyContextVar *var, PyObject *val);
63
64
static int
65
contextvar_del(PyContextVar *var);
66
67
68
PyObject *
69
_PyContext_NewHamtForTests(void)
70
0
{
71
0
    return (PyObject *)_PyHamt_New();
72
0
}
73
74
75
PyObject *
76
PyContext_New(void)
77
0
{
78
0
    return (PyObject *)context_new_empty();
79
0
}
80
81
82
PyObject *
83
PyContext_Copy(PyObject * octx)
84
0
{
85
0
    ENSURE_Context(octx, NULL)
86
0
    PyContext *ctx = (PyContext *)octx;
87
0
    return (PyObject *)context_new_from_vars(ctx->ctx_vars);
88
0
}
89
90
91
PyObject *
92
PyContext_CopyCurrent(void)
93
0
{
94
0
    PyContext *ctx = context_get();
95
0
    if (ctx == NULL) {
96
0
        return NULL;
97
0
    }
98
99
0
    return (PyObject *)context_new_from_vars(ctx->ctx_vars);
100
0
}
101
102
static const char *
103
0
context_event_name(PyContextEvent event) {
104
0
    switch (event) {
105
0
        case Py_CONTEXT_SWITCHED:
106
0
            return "Py_CONTEXT_SWITCHED";
107
0
        default:
108
0
            return "?";
109
0
    }
110
0
    Py_UNREACHABLE();
111
0
}
112
113
static void
114
notify_context_watchers(PyThreadState *ts, PyContextEvent event, PyObject *ctx)
115
0
{
116
0
    if (ctx == NULL) {
117
        // This will happen after exiting the last context in the stack, which
118
        // can occur if context_get was never called before entering a context
119
        // (e.g., called `contextvars.Context().run()` on a fresh thread, as
120
        // PyContext_Enter doesn't call context_get).
121
0
        ctx = Py_None;
122
0
    }
123
0
    assert(Py_REFCNT(ctx) > 0);
124
0
    PyInterpreterState *interp = ts->interp;
125
0
    assert(interp->_initialized);
126
0
    uint8_t bits = interp->active_context_watchers;
127
0
    int i = 0;
128
0
    while (bits) {
129
0
        assert(i < CONTEXT_MAX_WATCHERS);
130
0
        if (bits & 1) {
131
0
            PyContext_WatchCallback cb = interp->context_watchers[i];
132
0
            assert(cb != NULL);
133
0
            if (cb(event, ctx) < 0) {
134
0
                PyErr_FormatUnraisable(
135
0
                    "Exception ignored in %s watcher callback for %R",
136
0
                    context_event_name(event), ctx);
137
0
            }
138
0
        }
139
0
        i++;
140
0
        bits >>= 1;
141
0
    }
142
0
}
143
144
145
int
146
PyContext_AddWatcher(PyContext_WatchCallback callback)
147
0
{
148
0
    PyInterpreterState *interp = _PyInterpreterState_GET();
149
0
    assert(interp->_initialized);
150
151
0
    for (int i = 0; i < CONTEXT_MAX_WATCHERS; i++) {
152
0
        if (!interp->context_watchers[i]) {
153
0
            interp->context_watchers[i] = callback;
154
0
            interp->active_context_watchers |= (1 << i);
155
0
            return i;
156
0
        }
157
0
    }
158
159
0
    PyErr_SetString(PyExc_RuntimeError, "no more context watcher IDs available");
160
0
    return -1;
161
0
}
162
163
164
int
165
PyContext_ClearWatcher(int watcher_id)
166
0
{
167
0
    PyInterpreterState *interp = _PyInterpreterState_GET();
168
0
    assert(interp->_initialized);
169
0
    if (watcher_id < 0 || watcher_id >= CONTEXT_MAX_WATCHERS) {
170
0
        PyErr_Format(PyExc_ValueError, "Invalid context watcher ID %d", watcher_id);
171
0
        return -1;
172
0
    }
173
0
    if (!interp->context_watchers[watcher_id]) {
174
0
        PyErr_Format(PyExc_ValueError, "No context watcher set for ID %d", watcher_id);
175
0
        return -1;
176
0
    }
177
0
    interp->context_watchers[watcher_id] = NULL;
178
0
    interp->active_context_watchers &= ~(1 << watcher_id);
179
0
    return 0;
180
0
}
181
182
183
static inline void
184
context_switched(PyThreadState *ts)
185
0
{
186
0
    ts->context_ver++;
187
    // ts->context is used instead of context_get() because context_get() might
188
    // throw if ts->context is NULL.
189
0
    notify_context_watchers(ts, Py_CONTEXT_SWITCHED, ts->context);
190
0
}
191
192
193
int
194
_PyContext_Enter(PyThreadState *ts, PyObject *octx)
195
0
{
196
0
    ENSURE_Context(octx, -1)
197
0
    PyContext *ctx = (PyContext *)octx;
198
#ifdef Py_GIL_DISABLED
199
    int already_entered = _Py_atomic_exchange_int(&ctx->ctx_entered, 1);
200
#else
201
0
    int already_entered = ctx->ctx_entered;
202
0
    ctx->ctx_entered = 1;
203
0
#endif
204
205
0
    if (already_entered) {
206
0
        _PyErr_Format(ts, PyExc_RuntimeError,
207
0
                      "cannot enter context: %R is already entered", ctx);
208
0
        return -1;
209
0
    }
210
211
0
    ctx->ctx_prev = (PyContext *)ts->context;  /* borrow */
212
0
    ts->context = Py_NewRef(ctx);
213
0
    context_switched(ts);
214
0
    return 0;
215
0
}
216
217
218
int
219
PyContext_Enter(PyObject *octx)
220
0
{
221
0
    PyThreadState *ts = _PyThreadState_GET();
222
0
    assert(ts != NULL);
223
0
    return _PyContext_Enter(ts, octx);
224
0
}
225
226
227
int
228
_PyContext_Exit(PyThreadState *ts, PyObject *octx)
229
0
{
230
0
    ENSURE_Context(octx, -1)
231
0
    PyContext *ctx = (PyContext *)octx;
232
0
    int already_entered = FT_ATOMIC_LOAD_INT_RELAXED(ctx->ctx_entered);
233
234
0
    if (!already_entered) {
235
0
        PyErr_Format(PyExc_RuntimeError,
236
0
                     "cannot exit context: %R has not been entered", ctx);
237
0
        return -1;
238
0
    }
239
240
0
    if (ts->context != (PyObject *)ctx) {
241
        /* Can only happen if someone misuses the C API */
242
0
        PyErr_SetString(PyExc_RuntimeError,
243
0
                        "cannot exit context: thread state references "
244
0
                        "a different context object");
245
0
        return -1;
246
0
    }
247
248
0
    Py_SETREF(ts->context, (PyObject *)ctx->ctx_prev);
249
250
0
    ctx->ctx_prev = NULL;
251
0
    FT_ATOMIC_STORE_INT(ctx->ctx_entered, 0);
252
0
    context_switched(ts);
253
0
    return 0;
254
0
}
255
256
int
257
PyContext_Exit(PyObject *octx)
258
0
{
259
0
    PyThreadState *ts = _PyThreadState_GET();
260
0
    assert(ts != NULL);
261
0
    return _PyContext_Exit(ts, octx);
262
0
}
263
264
265
PyObject *
266
PyContextVar_New(const char *name, PyObject *def)
267
36
{
268
36
    PyObject *pyname = PyUnicode_FromString(name);
269
36
    if (pyname == NULL) {
270
0
        return NULL;
271
0
    }
272
36
    PyContextVar *var = contextvar_new(pyname, def);
273
36
    Py_DECREF(pyname);
274
36
    return (PyObject *)var;
275
36
}
276
277
278
int
279
PyContextVar_Get(PyObject *ovar, PyObject *def, PyObject **val)
280
276k
{
281
276k
    ENSURE_ContextVar(ovar, -1)
282
276k
    PyContextVar *var = (PyContextVar *)ovar;
283
284
276k
    PyThreadState *ts = _PyThreadState_GET();
285
276k
    assert(ts != NULL);
286
276k
    if (ts->context == NULL) {
287
276k
        goto not_found;
288
276k
    }
289
290
0
#ifndef Py_GIL_DISABLED
291
0
    if (var->var_cached != NULL &&
292
0
            var->var_cached_tsid == ts->id &&
293
0
            var->var_cached_tsver == ts->context_ver)
294
0
    {
295
0
        *val = var->var_cached;
296
0
        goto found;
297
0
    }
298
0
#endif
299
300
0
    assert(PyContext_CheckExact(ts->context));
301
0
    PyHamtObject *vars = ((PyContext *)ts->context)->ctx_vars;
302
303
0
    PyObject *found = NULL;
304
0
    int res = _PyHamt_Find(vars, (PyObject*)var, &found);
305
0
    if (res < 0) {
306
0
        goto error;
307
0
    }
308
0
    if (res == 1) {
309
0
        assert(found != NULL);
310
0
#ifndef Py_GIL_DISABLED
311
0
        var->var_cached = found;  /* borrow */
312
0
        var->var_cached_tsid = ts->id;
313
0
        var->var_cached_tsver = ts->context_ver;
314
0
#endif
315
316
0
        *val = found;
317
0
        goto found;
318
0
    }
319
320
276k
not_found:
321
276k
    if (def == NULL) {
322
276k
        if (var->var_default != NULL) {
323
0
            *val = var->var_default;
324
0
            goto found;
325
0
        }
326
327
276k
        *val = NULL;
328
276k
        goto found;
329
276k
    }
330
0
    else {
331
0
        *val = def;
332
0
        goto found;
333
0
   }
334
335
276k
found:
336
276k
    Py_XINCREF(*val);
337
276k
    return 0;
338
339
0
error:
340
0
    *val = NULL;
341
0
    return -1;
342
276k
}
343
344
345
PyObject *
346
PyContextVar_Set(PyObject *ovar, PyObject *val)
347
0
{
348
0
    ENSURE_ContextVar(ovar, NULL)
349
0
    PyContextVar *var = (PyContextVar *)ovar;
350
351
0
    PyContext *ctx = context_get();
352
0
    if (ctx == NULL) {
353
0
        return NULL;
354
0
    }
355
356
0
    PyObject *old_val = NULL;
357
0
    int found = _PyHamt_Find(ctx->ctx_vars, (PyObject *)var, &old_val);
358
0
    if (found < 0) {
359
0
        return NULL;
360
0
    }
361
362
0
    Py_XINCREF(old_val);
363
0
    PyContextToken *tok = token_new(ctx, var, old_val);
364
0
    Py_XDECREF(old_val);
365
366
0
    if (contextvar_set(var, val)) {
367
0
        Py_DECREF(tok);
368
0
        return NULL;
369
0
    }
370
371
0
    return (PyObject *)tok;
372
0
}
373
374
375
int
376
PyContextVar_Reset(PyObject *ovar, PyObject *otok)
377
0
{
378
0
    ENSURE_ContextVar(ovar, -1)
379
0
    ENSURE_ContextToken(otok, -1)
380
0
    PyContextVar *var = (PyContextVar *)ovar;
381
0
    PyContextToken *tok = (PyContextToken *)otok;
382
383
0
    if (tok->tok_used) {
384
0
        PyErr_Format(PyExc_RuntimeError,
385
0
                     "%R has already been used once", tok);
386
0
        return -1;
387
0
    }
388
389
0
    if (var != tok->tok_var) {
390
0
        PyErr_Format(PyExc_ValueError,
391
0
                     "%R was created by a different ContextVar", tok);
392
0
        return -1;
393
0
    }
394
395
0
    PyContext *ctx = context_get();
396
0
    if (ctx != tok->tok_ctx) {
397
0
        PyErr_Format(PyExc_ValueError,
398
0
                     "%R was created in a different Context", tok);
399
0
        return -1;
400
0
    }
401
402
0
    tok->tok_used = 1;
403
404
0
    if (tok->tok_oldval == NULL) {
405
0
        return contextvar_del(var);
406
0
    }
407
0
    else {
408
0
        return contextvar_set(var, tok->tok_oldval);
409
0
    }
410
0
}
411
412
413
/////////////////////////// PyContext
414
415
/*[clinic input]
416
class _contextvars.Context "PyContext *" "&PyContext_Type"
417
[clinic start generated code]*/
418
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=bdf87f8e0cb580e8]*/
419
420
421
0
#define _PyContext_CAST(op)     ((PyContext *)(op))
422
423
424
static inline PyContext *
425
_context_alloc(void)
426
0
{
427
0
    PyContext *ctx = _Py_FREELIST_POP(PyContext, contexts);
428
0
    if (ctx == NULL) {
429
0
        ctx = PyObject_GC_New(PyContext, &PyContext_Type);
430
0
        if (ctx == NULL) {
431
0
            return NULL;
432
0
        }
433
0
    }
434
435
0
    ctx->ctx_vars = NULL;
436
0
    ctx->ctx_prev = NULL;
437
0
    ctx->ctx_entered = 0;
438
0
    ctx->ctx_weakreflist = NULL;
439
440
0
    return ctx;
441
0
}
442
443
444
static PyContext *
445
context_new_empty(void)
446
0
{
447
0
    PyContext *ctx = _context_alloc();
448
0
    if (ctx == NULL) {
449
0
        return NULL;
450
0
    }
451
452
0
    ctx->ctx_vars = _PyHamt_New();
453
0
    if (ctx->ctx_vars == NULL) {
454
0
        Py_DECREF(ctx);
455
0
        return NULL;
456
0
    }
457
458
0
    _PyObject_GC_TRACK(ctx);
459
0
    return ctx;
460
0
}
461
462
463
static PyContext *
464
context_new_from_vars(PyHamtObject *vars)
465
0
{
466
0
    PyContext *ctx = _context_alloc();
467
0
    if (ctx == NULL) {
468
0
        return NULL;
469
0
    }
470
471
0
    ctx->ctx_vars = (PyHamtObject*)Py_NewRef(vars);
472
473
0
    _PyObject_GC_TRACK(ctx);
474
0
    return ctx;
475
0
}
476
477
478
static inline PyContext *
479
context_get(void)
480
0
{
481
0
    PyThreadState *ts = _PyThreadState_GET();
482
0
    assert(ts != NULL);
483
0
    PyContext *current_ctx = (PyContext *)ts->context;
484
0
    if (current_ctx == NULL) {
485
0
        current_ctx = context_new_empty();
486
0
        if (current_ctx == NULL) {
487
0
            return NULL;
488
0
        }
489
0
        ts->context = (PyObject *)current_ctx;
490
0
    }
491
0
    return current_ctx;
492
0
}
493
494
static int
495
context_check_key_type(PyObject *key)
496
0
{
497
0
    if (!PyContextVar_CheckExact(key)) {
498
        // abort();
499
0
        PyErr_Format(PyExc_TypeError,
500
0
                     "a ContextVar key was expected, got %R", key);
501
0
        return -1;
502
0
    }
503
0
    return 0;
504
0
}
505
506
static PyObject *
507
context_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
508
0
{
509
0
    if (PyTuple_Size(args) || (kwds != NULL && PyDict_Size(kwds))) {
510
0
        PyErr_SetString(
511
0
            PyExc_TypeError, "Context() does not accept any arguments");
512
0
        return NULL;
513
0
    }
514
0
    return PyContext_New();
515
0
}
516
517
static int
518
context_tp_clear(PyObject *op)
519
0
{
520
0
    PyContext *self = _PyContext_CAST(op);
521
0
    Py_CLEAR(self->ctx_prev);
522
0
    Py_CLEAR(self->ctx_vars);
523
0
    return 0;
524
0
}
525
526
static int
527
context_tp_traverse(PyObject *op, visitproc visit, void *arg)
528
0
{
529
0
    PyContext *self = _PyContext_CAST(op);
530
0
    Py_VISIT(self->ctx_prev);
531
0
    Py_VISIT(self->ctx_vars);
532
0
    return 0;
533
0
}
534
535
static void
536
context_tp_dealloc(PyObject *self)
537
0
{
538
0
    _PyObject_GC_UNTRACK(self);
539
0
    PyContext *ctx = _PyContext_CAST(self);
540
0
    if (ctx->ctx_weakreflist != NULL) {
541
0
        PyObject_ClearWeakRefs(self);
542
0
    }
543
0
    (void)context_tp_clear(self);
544
545
0
    _Py_FREELIST_FREE(contexts, self, Py_TYPE(self)->tp_free);
546
0
}
547
548
static PyObject *
549
context_tp_iter(PyObject *op)
550
0
{
551
0
    PyContext *self = _PyContext_CAST(op);
552
0
    return _PyHamt_NewIterKeys(self->ctx_vars);
553
0
}
554
555
static PyObject *
556
context_tp_richcompare(PyObject *v, PyObject *w, int op)
557
0
{
558
0
    if (!PyContext_CheckExact(v) || !PyContext_CheckExact(w) ||
559
0
            (op != Py_EQ && op != Py_NE))
560
0
    {
561
0
        Py_RETURN_NOTIMPLEMENTED;
562
0
    }
563
564
0
    int res = _PyHamt_Eq(
565
0
        ((PyContext *)v)->ctx_vars, ((PyContext *)w)->ctx_vars);
566
0
    if (res < 0) {
567
0
        return NULL;
568
0
    }
569
570
0
    if (op == Py_NE) {
571
0
        res = !res;
572
0
    }
573
574
0
    if (res) {
575
0
        Py_RETURN_TRUE;
576
0
    }
577
0
    else {
578
0
        Py_RETURN_FALSE;
579
0
    }
580
0
}
581
582
static Py_ssize_t
583
context_tp_len(PyObject *op)
584
0
{
585
0
    PyContext *self = _PyContext_CAST(op);
586
0
    return _PyHamt_Len(self->ctx_vars);
587
0
}
588
589
static PyObject *
590
context_tp_subscript(PyObject *op, PyObject *key)
591
0
{
592
0
    if (context_check_key_type(key)) {
593
0
        return NULL;
594
0
    }
595
0
    PyObject *val = NULL;
596
0
    PyContext *self = _PyContext_CAST(op);
597
0
    int found = _PyHamt_Find(self->ctx_vars, key, &val);
598
0
    if (found < 0) {
599
0
        return NULL;
600
0
    }
601
0
    if (found == 0) {
602
0
        PyErr_SetObject(PyExc_KeyError, key);
603
0
        return NULL;
604
0
    }
605
0
    return Py_NewRef(val);
606
0
}
607
608
static int
609
context_tp_contains(PyObject *op, PyObject *key)
610
0
{
611
0
    if (context_check_key_type(key)) {
612
0
        return -1;
613
0
    }
614
0
    PyObject *val = NULL;
615
0
    PyContext *self = _PyContext_CAST(op);
616
0
    return _PyHamt_Find(self->ctx_vars, key, &val);
617
0
}
618
619
620
/*[clinic input]
621
@permit_long_summary
622
_contextvars.Context.get
623
    key: object
624
    default: object = None
625
    /
626
627
Return the value for `key` if `key` has the value in the context object.
628
629
If `key` does not exist, return `default`.  If `default` is not
630
given, return None.
631
[clinic start generated code]*/
632
633
static PyObject *
634
_contextvars_Context_get_impl(PyContext *self, PyObject *key,
635
                              PyObject *default_value)
636
/*[clinic end generated code: output=0c54aa7664268189 input=d669a0d56fabb0a5]*/
637
0
{
638
0
    if (context_check_key_type(key)) {
639
0
        return NULL;
640
0
    }
641
642
0
    PyObject *val = NULL;
643
0
    int found = _PyHamt_Find(self->ctx_vars, key, &val);
644
0
    if (found < 0) {
645
0
        return NULL;
646
0
    }
647
0
    if (found == 0) {
648
0
        return Py_NewRef(default_value);
649
0
    }
650
0
    return Py_NewRef(val);
651
0
}
652
653
654
/*[clinic input]
655
_contextvars.Context.items
656
657
Return all variables and their values in the context object.
658
659
The result is returned as a list of 2-tuples (variable, value).
660
[clinic start generated code]*/
661
662
static PyObject *
663
_contextvars_Context_items_impl(PyContext *self)
664
/*[clinic end generated code: output=fa1655c8a08502af input=00db64ae379f9f42]*/
665
0
{
666
0
    return _PyHamt_NewIterItems(self->ctx_vars);
667
0
}
668
669
670
/*[clinic input]
671
_contextvars.Context.keys
672
673
Return a list of all variables in the context object.
674
[clinic start generated code]*/
675
676
static PyObject *
677
_contextvars_Context_keys_impl(PyContext *self)
678
/*[clinic end generated code: output=177227c6b63ec0e2 input=114b53aebca3449c]*/
679
0
{
680
0
    return _PyHamt_NewIterKeys(self->ctx_vars);
681
0
}
682
683
684
/*[clinic input]
685
_contextvars.Context.values
686
687
Return a list of all variables' values in the context object.
688
[clinic start generated code]*/
689
690
static PyObject *
691
_contextvars_Context_values_impl(PyContext *self)
692
/*[clinic end generated code: output=d286dabfc8db6dde input=ce8075d04a6ea526]*/
693
0
{
694
0
    return _PyHamt_NewIterValues(self->ctx_vars);
695
0
}
696
697
698
/*[clinic input]
699
_contextvars.Context.copy
700
701
Return a shallow copy of the context object.
702
[clinic start generated code]*/
703
704
static PyObject *
705
_contextvars_Context_copy_impl(PyContext *self)
706
/*[clinic end generated code: output=30ba8896c4707a15 input=ebafdbdd9c72d592]*/
707
0
{
708
0
    return (PyObject *)context_new_from_vars(self->ctx_vars);
709
0
}
710
711
712
static PyObject *
713
context_run(PyObject *self, PyObject *const *args,
714
            Py_ssize_t nargs, PyObject *kwnames)
715
0
{
716
0
    PyThreadState *ts = _PyThreadState_GET();
717
718
0
    if (nargs < 1) {
719
0
        _PyErr_SetString(ts, PyExc_TypeError,
720
0
                         "run() missing 1 required positional argument");
721
0
        return NULL;
722
0
    }
723
724
0
    if (_PyContext_Enter(ts, self)) {
725
0
        return NULL;
726
0
    }
727
728
0
    PyObject *call_result = _PyObject_VectorcallTstate(
729
0
        ts, args[0], args + 1, nargs - 1, kwnames);
730
731
0
    if (_PyContext_Exit(ts, self)) {
732
0
        Py_XDECREF(call_result);
733
0
        return NULL;
734
0
    }
735
736
0
    return call_result;
737
0
}
738
739
740
static PyMethodDef PyContext_methods[] = {
741
    _CONTEXTVARS_CONTEXT_GET_METHODDEF
742
    _CONTEXTVARS_CONTEXT_ITEMS_METHODDEF
743
    _CONTEXTVARS_CONTEXT_KEYS_METHODDEF
744
    _CONTEXTVARS_CONTEXT_VALUES_METHODDEF
745
    _CONTEXTVARS_CONTEXT_COPY_METHODDEF
746
    {"run", _PyCFunction_CAST(context_run), METH_FASTCALL | METH_KEYWORDS, NULL},
747
    {NULL, NULL}
748
};
749
750
static PySequenceMethods PyContext_as_sequence = {
751
    .sq_contains = context_tp_contains
752
};
753
754
static PyMappingMethods PyContext_as_mapping = {
755
    .mp_length = context_tp_len,
756
    .mp_subscript = context_tp_subscript
757
};
758
759
PyTypeObject PyContext_Type = {
760
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
761
    "_contextvars.Context",
762
    sizeof(PyContext),
763
    .tp_methods = PyContext_methods,
764
    .tp_as_mapping = &PyContext_as_mapping,
765
    .tp_as_sequence = &PyContext_as_sequence,
766
    .tp_iter = context_tp_iter,
767
    .tp_dealloc = context_tp_dealloc,
768
    .tp_getattro = PyObject_GenericGetAttr,
769
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
770
    .tp_richcompare = context_tp_richcompare,
771
    .tp_traverse = context_tp_traverse,
772
    .tp_clear = context_tp_clear,
773
    .tp_new = context_tp_new,
774
    .tp_weaklistoffset = offsetof(PyContext, ctx_weakreflist),
775
    .tp_hash = PyObject_HashNotImplemented,
776
};
777
778
779
/////////////////////////// ContextVar
780
781
782
static int
783
contextvar_set(PyContextVar *var, PyObject *val)
784
0
{
785
0
#ifndef Py_GIL_DISABLED
786
0
    var->var_cached = NULL;
787
0
    PyThreadState *ts = _PyThreadState_GET();
788
0
#endif
789
790
0
    PyContext *ctx = context_get();
791
0
    if (ctx == NULL) {
792
0
        return -1;
793
0
    }
794
795
0
    PyHamtObject *new_vars = _PyHamt_Assoc(
796
0
        ctx->ctx_vars, (PyObject *)var, val);
797
0
    if (new_vars == NULL) {
798
0
        return -1;
799
0
    }
800
801
0
    Py_SETREF(ctx->ctx_vars, new_vars);
802
803
0
#ifndef Py_GIL_DISABLED
804
0
    var->var_cached = val;  /* borrow */
805
0
    var->var_cached_tsid = ts->id;
806
0
    var->var_cached_tsver = ts->context_ver;
807
0
#endif
808
0
    return 0;
809
0
}
810
811
static int
812
contextvar_del(PyContextVar *var)
813
0
{
814
0
#ifndef Py_GIL_DISABLED
815
0
    var->var_cached = NULL;
816
0
#endif
817
818
0
    PyContext *ctx = context_get();
819
0
    if (ctx == NULL) {
820
0
        return -1;
821
0
    }
822
823
0
    PyHamtObject *vars = ctx->ctx_vars;
824
0
    PyHamtObject *new_vars = _PyHamt_Without(vars, (PyObject *)var);
825
0
    if (new_vars == NULL) {
826
0
        return -1;
827
0
    }
828
829
0
    if (vars == new_vars) {
830
0
        Py_DECREF(new_vars);
831
0
        PyErr_SetObject(PyExc_LookupError, (PyObject *)var);
832
0
        return -1;
833
0
    }
834
835
0
    Py_SETREF(ctx->ctx_vars, new_vars);
836
0
    return 0;
837
0
}
838
839
static Py_hash_t
840
contextvar_generate_hash(void *addr, PyObject *name)
841
65
{
842
    /* Take hash of `name` and XOR it with the object's addr.
843
844
       The structure of the tree is encoded in objects' hashes, which
845
       means that sufficiently similar hashes would result in tall trees
846
       with many Collision nodes.  Which would, in turn, result in slower
847
       get and set operations.
848
849
       The XORing helps to ensure that:
850
851
       (1) sequentially allocated ContextVar objects have
852
           different hashes;
853
854
       (2) context variables with equal names have
855
           different hashes.
856
    */
857
858
65
    Py_hash_t name_hash = PyObject_Hash(name);
859
65
    if (name_hash == -1) {
860
0
        return -1;
861
0
    }
862
863
65
    Py_hash_t res = Py_HashPointer(addr) ^ name_hash;
864
65
    return res == -1 ? -2 : res;
865
65
}
866
867
static PyContextVar *
868
contextvar_new(PyObject *name, PyObject *def)
869
65
{
870
65
    if (!PyUnicode_Check(name)) {
871
0
        PyErr_SetString(PyExc_TypeError,
872
0
                        "context variable name must be a str");
873
0
        return NULL;
874
0
    }
875
876
65
    PyContextVar *var = PyObject_GC_New(PyContextVar, &PyContextVar_Type);
877
65
    if (var == NULL) {
878
0
        return NULL;
879
0
    }
880
881
65
    var->var_name = Py_NewRef(name);
882
65
    var->var_default = Py_XNewRef(def);
883
884
65
#ifndef Py_GIL_DISABLED
885
65
    var->var_cached = NULL;
886
65
    var->var_cached_tsid = 0;
887
65
    var->var_cached_tsver = 0;
888
65
#endif
889
890
65
    var->var_hash = contextvar_generate_hash(var, name);
891
65
    if (var->var_hash == -1) {
892
0
        Py_DECREF(var);
893
0
        return NULL;
894
0
    }
895
896
65
    if (_PyObject_GC_MAY_BE_TRACKED(name) ||
897
65
            (def != NULL && _PyObject_GC_MAY_BE_TRACKED(def)))
898
0
    {
899
0
        PyObject_GC_Track(var);
900
0
    }
901
65
    return var;
902
65
}
903
904
905
/*[clinic input]
906
class _contextvars.ContextVar "PyContextVar *" "&PyContextVar_Type"
907
[clinic start generated code]*/
908
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=445da935fa8883c3]*/
909
910
911
0
#define _PyContextVar_CAST(op)  ((PyContextVar *)(op))
912
913
914
static PyObject *
915
contextvar_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
916
29
{
917
29
    static char *kwlist[] = {"", "default", NULL};
918
29
    PyObject *name;
919
29
    PyObject *def = NULL;
920
921
29
    if (!PyArg_ParseTupleAndKeywords(
922
29
            args, kwds, "O|$O:ContextVar", kwlist, &name, &def))
923
0
    {
924
0
        return NULL;
925
0
    }
926
927
29
    return (PyObject *)contextvar_new(name, def);
928
29
}
929
930
static int
931
contextvar_tp_clear(PyObject *op)
932
0
{
933
0
    PyContextVar *self = _PyContextVar_CAST(op);
934
0
    Py_CLEAR(self->var_name);
935
0
    Py_CLEAR(self->var_default);
936
0
#ifndef Py_GIL_DISABLED
937
0
    self->var_cached = NULL;
938
0
    self->var_cached_tsid = 0;
939
0
    self->var_cached_tsver = 0;
940
0
#endif
941
0
    return 0;
942
0
}
943
944
static int
945
contextvar_tp_traverse(PyObject *op, visitproc visit, void *arg)
946
0
{
947
0
    PyContextVar *self = _PyContextVar_CAST(op);
948
0
    Py_VISIT(self->var_name);
949
0
    Py_VISIT(self->var_default);
950
0
    return 0;
951
0
}
952
953
static void
954
contextvar_tp_dealloc(PyObject *self)
955
0
{
956
0
    PyObject_GC_UnTrack(self);
957
0
    (void)contextvar_tp_clear(self);
958
0
    Py_TYPE(self)->tp_free(self);
959
0
}
960
961
static Py_hash_t
962
contextvar_tp_hash(PyObject *op)
963
0
{
964
0
    PyContextVar *self = _PyContextVar_CAST(op);
965
0
    return self->var_hash;
966
0
}
967
968
static PyObject *
969
contextvar_tp_repr(PyObject *op)
970
0
{
971
0
    PyContextVar *self = _PyContextVar_CAST(op);
972
    // Estimation based on the shortest name and default value,
973
    // but maximize the pointer size.
974
    // "<ContextVar name='a' at 0x1234567812345678>"
975
    // "<ContextVar name='a' default=1 at 0x1234567812345678>"
976
0
    Py_ssize_t estimate = self->var_default ? 53 : 43;
977
0
    PyUnicodeWriter *writer = PyUnicodeWriter_Create(estimate);
978
0
    if (writer == NULL) {
979
0
        return NULL;
980
0
    }
981
982
0
    if (PyUnicodeWriter_WriteASCII(writer, "<ContextVar name=", 17) < 0) {
983
0
        goto error;
984
0
    }
985
0
    if (PyUnicodeWriter_WriteRepr(writer, self->var_name) < 0) {
986
0
        goto error;
987
0
    }
988
989
0
    if (self->var_default != NULL) {
990
0
        if (PyUnicodeWriter_WriteASCII(writer, " default=", 9) < 0) {
991
0
            goto error;
992
0
        }
993
0
        if (PyUnicodeWriter_WriteRepr(writer, self->var_default) < 0) {
994
0
            goto error;
995
0
        }
996
0
    }
997
998
0
    if (PyUnicodeWriter_Format(writer, " at %p>", self) < 0) {
999
0
        goto error;
1000
0
    }
1001
0
    return PyUnicodeWriter_Finish(writer);
1002
1003
0
error:
1004
0
    PyUnicodeWriter_Discard(writer);
1005
0
    return NULL;
1006
0
}
1007
1008
1009
/*[clinic input]
1010
_contextvars.ContextVar.get
1011
    default: object = NULL
1012
    /
1013
1014
Return a value for the context variable for the current context.
1015
1016
If there is no value for the variable in the current context, the
1017
method will:
1018
 * return the value of the default argument of the method, if
1019
   provided; or
1020
 * return the default value for the context variable, if it was
1021
   created with one; or
1022
 * raise a LookupError.
1023
[clinic start generated code]*/
1024
1025
static PyObject *
1026
_contextvars_ContextVar_get_impl(PyContextVar *self, PyObject *default_value)
1027
/*[clinic end generated code: output=0746bd0aa2ced7bf input=83814c6aef4a9fe3]*/
1028
0
{
1029
0
    PyObject *val;
1030
0
    if (PyContextVar_Get((PyObject *)self, default_value, &val) < 0) {
1031
0
        return NULL;
1032
0
    }
1033
1034
0
    if (val == NULL) {
1035
0
        PyErr_SetObject(PyExc_LookupError, (PyObject *)self);
1036
0
        return NULL;
1037
0
    }
1038
1039
0
    return val;
1040
0
}
1041
1042
/*[clinic input]
1043
@permit_long_summary
1044
_contextvars.ContextVar.set
1045
    value: object
1046
    /
1047
1048
Call to set a new value for the context variable in the current context.
1049
1050
The required value argument is the new value for the context
1051
variable.
1052
1053
Returns a Token object that can be used to restore the variable to
1054
its previous value via the `ContextVar.reset()` method.
1055
[clinic start generated code]*/
1056
1057
static PyObject *
1058
_contextvars_ContextVar_set_impl(PyContextVar *self, PyObject *value)
1059
/*[clinic end generated code: output=1b562d35cc79c806 input=04ef8dcd810f5be6]*/
1060
0
{
1061
0
    return PyContextVar_Set((PyObject *)self, value);
1062
0
}
1063
1064
/*[clinic input]
1065
_contextvars.ContextVar.reset
1066
    token: object
1067
    /
1068
1069
Reset the context variable.
1070
1071
The variable is reset to the value it had before the
1072
`ContextVar.set()` that created the token was used.
1073
[clinic start generated code]*/
1074
1075
static PyObject *
1076
_contextvars_ContextVar_reset_impl(PyContextVar *self, PyObject *token)
1077
/*[clinic end generated code: output=3205d2bdff568521 input=dd33cfcb18c00e37]*/
1078
0
{
1079
0
    if (!PyContextToken_CheckExact(token)) {
1080
0
        PyErr_Format(PyExc_TypeError,
1081
0
                     "expected an instance of Token, got %R", token);
1082
0
        return NULL;
1083
0
    }
1084
1085
0
    if (PyContextVar_Reset((PyObject *)self, token)) {
1086
0
        return NULL;
1087
0
    }
1088
1089
0
    Py_RETURN_NONE;
1090
0
}
1091
1092
1093
static PyMemberDef PyContextVar_members[] = {
1094
    {"name", _Py_T_OBJECT, offsetof(PyContextVar, var_name), Py_READONLY},
1095
    {NULL}
1096
};
1097
1098
static PyMethodDef PyContextVar_methods[] = {
1099
    _CONTEXTVARS_CONTEXTVAR_GET_METHODDEF
1100
    _CONTEXTVARS_CONTEXTVAR_SET_METHODDEF
1101
    _CONTEXTVARS_CONTEXTVAR_RESET_METHODDEF
1102
    {"__class_getitem__", Py_GenericAlias,
1103
    METH_O|METH_CLASS,
1104
    PyDoc_STR("ContextVars are generic over the type of their contained values")},
1105
    {NULL, NULL}
1106
};
1107
1108
PyTypeObject PyContextVar_Type = {
1109
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1110
    "_contextvars.ContextVar",
1111
    sizeof(PyContextVar),
1112
    .tp_methods = PyContextVar_methods,
1113
    .tp_members = PyContextVar_members,
1114
    .tp_dealloc = contextvar_tp_dealloc,
1115
    .tp_getattro = PyObject_GenericGetAttr,
1116
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1117
    .tp_traverse = contextvar_tp_traverse,
1118
    .tp_clear = contextvar_tp_clear,
1119
    .tp_new = contextvar_tp_new,
1120
    .tp_free = PyObject_GC_Del,
1121
    .tp_hash = contextvar_tp_hash,
1122
    .tp_repr = contextvar_tp_repr,
1123
};
1124
1125
1126
/////////////////////////// Token
1127
1128
static PyObject * get_token_missing(void);
1129
1130
1131
/*[clinic input]
1132
class _contextvars.Token "PyContextToken *" "&PyContextToken_Type"
1133
[clinic start generated code]*/
1134
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=338a5e2db13d3f5b]*/
1135
1136
1137
0
#define _PyContextToken_CAST(op)    ((PyContextToken *)(op))
1138
1139
1140
static PyObject *
1141
token_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1142
0
{
1143
0
    PyErr_SetString(PyExc_RuntimeError,
1144
0
                    "Tokens can only be created by ContextVars");
1145
0
    return NULL;
1146
0
}
1147
1148
static int
1149
token_tp_clear(PyObject *op)
1150
0
{
1151
0
    PyContextToken *self = _PyContextToken_CAST(op);
1152
0
    Py_CLEAR(self->tok_ctx);
1153
0
    Py_CLEAR(self->tok_var);
1154
0
    Py_CLEAR(self->tok_oldval);
1155
0
    return 0;
1156
0
}
1157
1158
static int
1159
token_tp_traverse(PyObject *op, visitproc visit, void *arg)
1160
0
{
1161
0
    PyContextToken *self = _PyContextToken_CAST(op);
1162
0
    Py_VISIT(self->tok_ctx);
1163
0
    Py_VISIT(self->tok_var);
1164
0
    Py_VISIT(self->tok_oldval);
1165
0
    return 0;
1166
0
}
1167
1168
static void
1169
token_tp_dealloc(PyObject *self)
1170
0
{
1171
0
    PyObject_GC_UnTrack(self);
1172
0
    (void)token_tp_clear(self);
1173
0
    Py_TYPE(self)->tp_free(self);
1174
0
}
1175
1176
static PyObject *
1177
token_tp_repr(PyObject *op)
1178
0
{
1179
0
    PyContextToken *self = _PyContextToken_CAST(op);
1180
0
    PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
1181
0
    if (writer == NULL) {
1182
0
        return NULL;
1183
0
    }
1184
0
    if (PyUnicodeWriter_WriteASCII(writer, "<Token", 6) < 0) {
1185
0
        goto error;
1186
0
    }
1187
0
    if (self->tok_used) {
1188
0
        if (PyUnicodeWriter_WriteASCII(writer, " used", 5) < 0) {
1189
0
            goto error;
1190
0
        }
1191
0
    }
1192
0
    if (PyUnicodeWriter_WriteASCII(writer, " var=", 5) < 0) {
1193
0
        goto error;
1194
0
    }
1195
0
    if (PyUnicodeWriter_WriteRepr(writer, (PyObject *)self->tok_var) < 0) {
1196
0
        goto error;
1197
0
    }
1198
0
    if (PyUnicodeWriter_Format(writer, " at %p>", self) < 0) {
1199
0
        goto error;
1200
0
    }
1201
0
    return PyUnicodeWriter_Finish(writer);
1202
1203
0
error:
1204
0
    PyUnicodeWriter_Discard(writer);
1205
0
    return NULL;
1206
0
}
1207
1208
static PyObject *
1209
token_get_var(PyObject *op, void *Py_UNUSED(ignored))
1210
0
{
1211
0
    PyContextToken *self = _PyContextToken_CAST(op);
1212
0
    return Py_NewRef(self->tok_var);;
1213
0
}
1214
1215
static PyObject *
1216
token_get_old_value(PyObject *op, void *Py_UNUSED(ignored))
1217
0
{
1218
0
    PyContextToken *self = _PyContextToken_CAST(op);
1219
0
    if (self->tok_oldval == NULL) {
1220
0
        return get_token_missing();
1221
0
    }
1222
1223
0
    return Py_NewRef(self->tok_oldval);
1224
0
}
1225
1226
static PyGetSetDef PyContextTokenType_getsetlist[] = {
1227
    {"var", token_get_var, NULL, NULL},
1228
    {"old_value", token_get_old_value, NULL, NULL},
1229
    {NULL}
1230
};
1231
1232
/*[clinic input]
1233
_contextvars.Token.__enter__ as token_enter
1234
1235
Enter into Token context manager.
1236
[clinic start generated code]*/
1237
1238
static PyObject *
1239
token_enter_impl(PyContextToken *self)
1240
/*[clinic end generated code: output=9af4d2054e93fb75 input=41a3d6c4195fd47a]*/
1241
0
{
1242
0
    return Py_NewRef(self);
1243
0
}
1244
1245
/*[clinic input]
1246
_contextvars.Token.__exit__ as token_exit
1247
1248
    type: object
1249
    val: object
1250
    tb: object
1251
    /
1252
1253
Exit from Token context manager, restore the linked ContextVar.
1254
[clinic start generated code]*/
1255
1256
static PyObject *
1257
token_exit_impl(PyContextToken *self, PyObject *type, PyObject *val,
1258
                PyObject *tb)
1259
/*[clinic end generated code: output=3e6a1c95d3da703a input=7f117445f0ccd92e]*/
1260
0
{
1261
0
    int ret = PyContextVar_Reset((PyObject *)self->tok_var, (PyObject *)self);
1262
0
    if (ret < 0) {
1263
0
        return NULL;
1264
0
    }
1265
0
    Py_RETURN_NONE;
1266
0
}
1267
1268
static PyMethodDef PyContextTokenType_methods[] = {
1269
    {"__class_getitem__",    Py_GenericAlias,
1270
    METH_O|METH_CLASS,
1271
    PyDoc_STR("Tokens are generic over the same type as the ContextVar which created them.")},
1272
    TOKEN_ENTER_METHODDEF
1273
    TOKEN_EXIT_METHODDEF
1274
    {NULL}
1275
};
1276
1277
PyTypeObject PyContextToken_Type = {
1278
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1279
    "_contextvars.Token",
1280
    sizeof(PyContextToken),
1281
    .tp_methods = PyContextTokenType_methods,
1282
    .tp_getset = PyContextTokenType_getsetlist,
1283
    .tp_dealloc = token_tp_dealloc,
1284
    .tp_getattro = PyObject_GenericGetAttr,
1285
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1286
    .tp_traverse = token_tp_traverse,
1287
    .tp_clear = token_tp_clear,
1288
    .tp_new = token_tp_new,
1289
    .tp_free = PyObject_GC_Del,
1290
    .tp_hash = PyObject_HashNotImplemented,
1291
    .tp_repr = token_tp_repr,
1292
};
1293
1294
static PyContextToken *
1295
token_new(PyContext *ctx, PyContextVar *var, PyObject *val)
1296
0
{
1297
0
    PyContextToken *tok = PyObject_GC_New(PyContextToken, &PyContextToken_Type);
1298
0
    if (tok == NULL) {
1299
0
        return NULL;
1300
0
    }
1301
1302
0
    tok->tok_ctx = (PyContext*)Py_NewRef(ctx);
1303
1304
0
    tok->tok_var = (PyContextVar*)Py_NewRef(var);
1305
1306
0
    tok->tok_oldval = Py_XNewRef(val);
1307
1308
0
    tok->tok_used = 0;
1309
1310
0
    PyObject_GC_Track(tok);
1311
0
    return tok;
1312
0
}
1313
1314
1315
/////////////////////////// Token.MISSING
1316
1317
1318
static PyObject *
1319
context_token_missing_tp_repr(PyObject *self)
1320
0
{
1321
0
    return PyUnicode_FromString("<Token.MISSING>");
1322
0
}
1323
1324
static void
1325
context_token_missing_tp_dealloc(PyObject *Py_UNUSED(self))
1326
0
{
1327
#ifdef Py_DEBUG
1328
    /* The singleton is statically allocated. */
1329
    _Py_FatalRefcountError("deallocating the token missing singleton");
1330
#else
1331
0
    return;
1332
0
#endif
1333
0
}
1334
1335
1336
PyTypeObject _PyContextTokenMissing_Type = {
1337
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1338
    "Token.MISSING",
1339
    sizeof(_PyContextTokenMissing),
1340
    .tp_dealloc = context_token_missing_tp_dealloc,
1341
    .tp_getattro = PyObject_GenericGetAttr,
1342
    .tp_flags = Py_TPFLAGS_DEFAULT,
1343
    .tp_repr = context_token_missing_tp_repr,
1344
};
1345
1346
1347
static PyObject *
1348
get_token_missing(void)
1349
36
{
1350
36
    return (PyObject *)&_Py_SINGLETON(context_token_missing);
1351
36
}
1352
1353
1354
///////////////////////////
1355
1356
1357
PyStatus
1358
_PyContext_Init(PyInterpreterState *interp)
1359
36
{
1360
36
    PyObject *missing = get_token_missing();
1361
36
    assert(PyUnstable_IsImmortal(missing));
1362
36
    if (PyDict_SetItemString(
1363
36
        _PyType_GetDict(&PyContextToken_Type), "MISSING", missing))
1364
0
    {
1365
0
        Py_DECREF(missing);
1366
0
        return _PyStatus_ERR("can't init context types");
1367
0
    }
1368
36
    Py_DECREF(missing);
1369
1370
36
    return _PyStatus_OK();
1371
36
}