Coverage Report

Created: 2026-01-13 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython3/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
277k
    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
22
{
268
22
    PyObject *pyname = PyUnicode_FromString(name);
269
22
    if (pyname == NULL) {
270
0
        return NULL;
271
0
    }
272
22
    PyContextVar *var = contextvar_new(pyname, def);
273
22
    Py_DECREF(pyname);
274
22
    return (PyObject *)var;
275
22
}
276
277
278
int
279
PyContextVar_Get(PyObject *ovar, PyObject *def, PyObject **val)
280
277k
{
281
277k
    ENSURE_ContextVar(ovar, -1)
282
277k
    PyContextVar *var = (PyContextVar *)ovar;
283
284
277k
    PyThreadState *ts = _PyThreadState_GET();
285
277k
    assert(ts != NULL);
286
277k
    if (ts->context == NULL) {
287
56.2k
        goto not_found;
288
56.2k
    }
289
290
220k
#ifndef Py_GIL_DISABLED
291
220k
    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
220k
#endif
299
300
220k
    assert(PyContext_CheckExact(ts->context));
301
220k
    PyHamtObject *vars = ((PyContext *)ts->context)->ctx_vars;
302
303
220k
    PyObject *found = NULL;
304
220k
    int res = _PyHamt_Find(vars, (PyObject*)var, &found);
305
220k
    if (res < 0) {
306
0
        goto error;
307
0
    }
308
220k
    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
277k
not_found:
321
277k
    if (def == NULL) {
322
277k
        if (var->var_default != NULL) {
323
0
            *val = var->var_default;
324
0
            goto found;
325
0
        }
326
327
277k
        *val = NULL;
328
277k
        goto found;
329
277k
    }
330
0
    else {
331
0
        *val = def;
332
0
        goto found;
333
0
   }
334
335
277k
found:
336
277k
    Py_XINCREF(*val);
337
277k
    return 0;
338
339
0
error:
340
0
    *val = NULL;
341
0
    return -1;
342
277k
}
343
344
345
PyObject *
346
PyContextVar_Set(PyObject *ovar, PyObject *val)
347
4
{
348
4
    ENSURE_ContextVar(ovar, NULL)
349
4
    PyContextVar *var = (PyContextVar *)ovar;
350
351
4
    PyContext *ctx = context_get();
352
4
    if (ctx == NULL) {
353
0
        return NULL;
354
0
    }
355
356
4
    PyObject *old_val = NULL;
357
4
    int found = _PyHamt_Find(ctx->ctx_vars, (PyObject *)var, &old_val);
358
4
    if (found < 0) {
359
0
        return NULL;
360
0
    }
361
362
4
    Py_XINCREF(old_val);
363
4
    PyContextToken *tok = token_new(ctx, var, old_val);
364
4
    Py_XDECREF(old_val);
365
366
4
    if (contextvar_set(var, val)) {
367
0
        Py_DECREF(tok);
368
0
        return NULL;
369
0
    }
370
371
4
    return (PyObject *)tok;
372
4
}
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
8.64k
#define _PyContext_CAST(op)     ((PyContext *)(op))
422
423
424
static inline PyContext *
425
_context_alloc(void)
426
4
{
427
4
    PyContext *ctx = _Py_FREELIST_POP(PyContext, contexts);
428
4
    if (ctx == NULL) {
429
4
        ctx = PyObject_GC_New(PyContext, &PyContext_Type);
430
4
        if (ctx == NULL) {
431
0
            return NULL;
432
0
        }
433
4
    }
434
435
4
    ctx->ctx_vars = NULL;
436
4
    ctx->ctx_prev = NULL;
437
4
    ctx->ctx_entered = 0;
438
4
    ctx->ctx_weakreflist = NULL;
439
440
4
    return ctx;
441
4
}
442
443
444
static PyContext *
445
context_new_empty(void)
446
4
{
447
4
    PyContext *ctx = _context_alloc();
448
4
    if (ctx == NULL) {
449
0
        return NULL;
450
0
    }
451
452
4
    ctx->ctx_vars = _PyHamt_New();
453
4
    if (ctx->ctx_vars == NULL) {
454
0
        Py_DECREF(ctx);
455
0
        return NULL;
456
0
    }
457
458
4
    _PyObject_GC_TRACK(ctx);
459
4
    return ctx;
460
4
}
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
8
{
481
8
    PyThreadState *ts = _PyThreadState_GET();
482
8
    assert(ts != NULL);
483
8
    PyContext *current_ctx = (PyContext *)ts->context;
484
8
    if (current_ctx == NULL) {
485
4
        current_ctx = context_new_empty();
486
4
        if (current_ctx == NULL) {
487
0
            return NULL;
488
0
        }
489
4
        ts->context = (PyObject *)current_ctx;
490
4
    }
491
8
    return current_ctx;
492
8
}
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
8.64k
{
529
8.64k
    PyContext *self = _PyContext_CAST(op);
530
8.64k
    Py_VISIT(self->ctx_prev);
531
8.64k
    Py_VISIT(self->ctx_vars);
532
8.64k
    return 0;
533
8.64k
}
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
_contextvars.Context.get
622
    key: object
623
    default: object = None
624
    /
625
626
Return the value for `key` if `key` has the value in the context object.
627
628
If `key` does not exist, return `default`. If `default` is not given,
629
return None.
630
[clinic start generated code]*/
631
632
static PyObject *
633
_contextvars_Context_get_impl(PyContext *self, PyObject *key,
634
                              PyObject *default_value)
635
/*[clinic end generated code: output=0c54aa7664268189 input=c8eeb81505023995]*/
636
0
{
637
0
    if (context_check_key_type(key)) {
638
0
        return NULL;
639
0
    }
640
641
0
    PyObject *val = NULL;
642
0
    int found = _PyHamt_Find(self->ctx_vars, key, &val);
643
0
    if (found < 0) {
644
0
        return NULL;
645
0
    }
646
0
    if (found == 0) {
647
0
        return Py_NewRef(default_value);
648
0
    }
649
0
    return Py_NewRef(val);
650
0
}
651
652
653
/*[clinic input]
654
_contextvars.Context.items
655
656
Return all variables and their values in the context object.
657
658
The result is returned as a list of 2-tuples (variable, value).
659
[clinic start generated code]*/
660
661
static PyObject *
662
_contextvars_Context_items_impl(PyContext *self)
663
/*[clinic end generated code: output=fa1655c8a08502af input=00db64ae379f9f42]*/
664
0
{
665
0
    return _PyHamt_NewIterItems(self->ctx_vars);
666
0
}
667
668
669
/*[clinic input]
670
_contextvars.Context.keys
671
672
Return a list of all variables in the context object.
673
[clinic start generated code]*/
674
675
static PyObject *
676
_contextvars_Context_keys_impl(PyContext *self)
677
/*[clinic end generated code: output=177227c6b63ec0e2 input=114b53aebca3449c]*/
678
0
{
679
0
    return _PyHamt_NewIterKeys(self->ctx_vars);
680
0
}
681
682
683
/*[clinic input]
684
_contextvars.Context.values
685
686
Return a list of all variables' values in the context object.
687
[clinic start generated code]*/
688
689
static PyObject *
690
_contextvars_Context_values_impl(PyContext *self)
691
/*[clinic end generated code: output=d286dabfc8db6dde input=ce8075d04a6ea526]*/
692
0
{
693
0
    return _PyHamt_NewIterValues(self->ctx_vars);
694
0
}
695
696
697
/*[clinic input]
698
_contextvars.Context.copy
699
700
Return a shallow copy of the context object.
701
[clinic start generated code]*/
702
703
static PyObject *
704
_contextvars_Context_copy_impl(PyContext *self)
705
/*[clinic end generated code: output=30ba8896c4707a15 input=ebafdbdd9c72d592]*/
706
0
{
707
0
    return (PyObject *)context_new_from_vars(self->ctx_vars);
708
0
}
709
710
711
static PyObject *
712
context_run(PyObject *self, PyObject *const *args,
713
            Py_ssize_t nargs, PyObject *kwnames)
714
0
{
715
0
    PyThreadState *ts = _PyThreadState_GET();
716
717
0
    if (nargs < 1) {
718
0
        _PyErr_SetString(ts, PyExc_TypeError,
719
0
                         "run() missing 1 required positional argument");
720
0
        return NULL;
721
0
    }
722
723
0
    if (_PyContext_Enter(ts, self)) {
724
0
        return NULL;
725
0
    }
726
727
0
    PyObject *call_result = _PyObject_VectorcallTstate(
728
0
        ts, args[0], args + 1, nargs - 1, kwnames);
729
730
0
    if (_PyContext_Exit(ts, self)) {
731
0
        Py_XDECREF(call_result);
732
0
        return NULL;
733
0
    }
734
735
0
    return call_result;
736
0
}
737
738
739
static PyMethodDef PyContext_methods[] = {
740
    _CONTEXTVARS_CONTEXT_GET_METHODDEF
741
    _CONTEXTVARS_CONTEXT_ITEMS_METHODDEF
742
    _CONTEXTVARS_CONTEXT_KEYS_METHODDEF
743
    _CONTEXTVARS_CONTEXT_VALUES_METHODDEF
744
    _CONTEXTVARS_CONTEXT_COPY_METHODDEF
745
    {"run", _PyCFunction_CAST(context_run), METH_FASTCALL | METH_KEYWORDS, NULL},
746
    {NULL, NULL}
747
};
748
749
static PySequenceMethods PyContext_as_sequence = {
750
    .sq_contains = context_tp_contains
751
};
752
753
static PyMappingMethods PyContext_as_mapping = {
754
    .mp_length = context_tp_len,
755
    .mp_subscript = context_tp_subscript
756
};
757
758
PyTypeObject PyContext_Type = {
759
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
760
    "_contextvars.Context",
761
    sizeof(PyContext),
762
    .tp_methods = PyContext_methods,
763
    .tp_as_mapping = &PyContext_as_mapping,
764
    .tp_as_sequence = &PyContext_as_sequence,
765
    .tp_iter = context_tp_iter,
766
    .tp_dealloc = context_tp_dealloc,
767
    .tp_getattro = PyObject_GenericGetAttr,
768
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
769
    .tp_richcompare = context_tp_richcompare,
770
    .tp_traverse = context_tp_traverse,
771
    .tp_clear = context_tp_clear,
772
    .tp_new = context_tp_new,
773
    .tp_weaklistoffset = offsetof(PyContext, ctx_weakreflist),
774
    .tp_hash = PyObject_HashNotImplemented,
775
};
776
777
778
/////////////////////////// ContextVar
779
780
781
static int
782
contextvar_set(PyContextVar *var, PyObject *val)
783
4
{
784
4
#ifndef Py_GIL_DISABLED
785
4
    var->var_cached = NULL;
786
4
    PyThreadState *ts = _PyThreadState_GET();
787
4
#endif
788
789
4
    PyContext *ctx = context_get();
790
4
    if (ctx == NULL) {
791
0
        return -1;
792
0
    }
793
794
4
    PyHamtObject *new_vars = _PyHamt_Assoc(
795
4
        ctx->ctx_vars, (PyObject *)var, val);
796
4
    if (new_vars == NULL) {
797
0
        return -1;
798
0
    }
799
800
4
    Py_SETREF(ctx->ctx_vars, new_vars);
801
802
4
#ifndef Py_GIL_DISABLED
803
4
    var->var_cached = val;  /* borrow */
804
4
    var->var_cached_tsid = ts->id;
805
4
    var->var_cached_tsver = ts->context_ver;
806
4
#endif
807
4
    return 0;
808
4
}
809
810
static int
811
contextvar_del(PyContextVar *var)
812
0
{
813
0
#ifndef Py_GIL_DISABLED
814
0
    var->var_cached = NULL;
815
0
#endif
816
817
0
    PyContext *ctx = context_get();
818
0
    if (ctx == NULL) {
819
0
        return -1;
820
0
    }
821
822
0
    PyHamtObject *vars = ctx->ctx_vars;
823
0
    PyHamtObject *new_vars = _PyHamt_Without(vars, (PyObject *)var);
824
0
    if (new_vars == NULL) {
825
0
        return -1;
826
0
    }
827
828
0
    if (vars == new_vars) {
829
0
        Py_DECREF(new_vars);
830
0
        PyErr_SetObject(PyExc_LookupError, (PyObject *)var);
831
0
        return -1;
832
0
    }
833
834
0
    Py_SETREF(ctx->ctx_vars, new_vars);
835
0
    return 0;
836
0
}
837
838
static Py_hash_t
839
contextvar_generate_hash(void *addr, PyObject *name)
840
29
{
841
    /* Take hash of `name` and XOR it with the object's addr.
842
843
       The structure of the tree is encoded in objects' hashes, which
844
       means that sufficiently similar hashes would result in tall trees
845
       with many Collision nodes.  Which would, in turn, result in slower
846
       get and set operations.
847
848
       The XORing helps to ensure that:
849
850
       (1) sequentially allocated ContextVar objects have
851
           different hashes;
852
853
       (2) context variables with equal names have
854
           different hashes.
855
    */
856
857
29
    Py_hash_t name_hash = PyObject_Hash(name);
858
29
    if (name_hash == -1) {
859
0
        return -1;
860
0
    }
861
862
29
    Py_hash_t res = Py_HashPointer(addr) ^ name_hash;
863
29
    return res == -1 ? -2 : res;
864
29
}
865
866
static PyContextVar *
867
contextvar_new(PyObject *name, PyObject *def)
868
29
{
869
29
    if (!PyUnicode_Check(name)) {
870
0
        PyErr_SetString(PyExc_TypeError,
871
0
                        "context variable name must be a str");
872
0
        return NULL;
873
0
    }
874
875
29
    PyContextVar *var = PyObject_GC_New(PyContextVar, &PyContextVar_Type);
876
29
    if (var == NULL) {
877
0
        return NULL;
878
0
    }
879
880
29
    var->var_name = Py_NewRef(name);
881
29
    var->var_default = Py_XNewRef(def);
882
883
29
#ifndef Py_GIL_DISABLED
884
29
    var->var_cached = NULL;
885
29
    var->var_cached_tsid = 0;
886
29
    var->var_cached_tsver = 0;
887
29
#endif
888
889
29
    var->var_hash = contextvar_generate_hash(var, name);
890
29
    if (var->var_hash == -1) {
891
0
        Py_DECREF(var);
892
0
        return NULL;
893
0
    }
894
895
29
    if (_PyObject_GC_MAY_BE_TRACKED(name) ||
896
29
            (def != NULL && _PyObject_GC_MAY_BE_TRACKED(def)))
897
0
    {
898
0
        PyObject_GC_Track(var);
899
0
    }
900
29
    return var;
901
29
}
902
903
904
/*[clinic input]
905
class _contextvars.ContextVar "PyContextVar *" "&PyContextVar_Type"
906
[clinic start generated code]*/
907
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=445da935fa8883c3]*/
908
909
910
220k
#define _PyContextVar_CAST(op)  ((PyContextVar *)(op))
911
912
913
static PyObject *
914
contextvar_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
915
7
{
916
7
    static char *kwlist[] = {"", "default", NULL};
917
7
    PyObject *name;
918
7
    PyObject *def = NULL;
919
920
7
    if (!PyArg_ParseTupleAndKeywords(
921
7
            args, kwds, "O|$O:ContextVar", kwlist, &name, &def))
922
0
    {
923
0
        return NULL;
924
0
    }
925
926
7
    return (PyObject *)contextvar_new(name, def);
927
7
}
928
929
static int
930
contextvar_tp_clear(PyObject *op)
931
0
{
932
0
    PyContextVar *self = _PyContextVar_CAST(op);
933
0
    Py_CLEAR(self->var_name);
934
0
    Py_CLEAR(self->var_default);
935
0
#ifndef Py_GIL_DISABLED
936
0
    self->var_cached = NULL;
937
0
    self->var_cached_tsid = 0;
938
0
    self->var_cached_tsver = 0;
939
0
#endif
940
0
    return 0;
941
0
}
942
943
static int
944
contextvar_tp_traverse(PyObject *op, visitproc visit, void *arg)
945
0
{
946
0
    PyContextVar *self = _PyContextVar_CAST(op);
947
0
    Py_VISIT(self->var_name);
948
0
    Py_VISIT(self->var_default);
949
0
    return 0;
950
0
}
951
952
static void
953
contextvar_tp_dealloc(PyObject *self)
954
0
{
955
0
    PyObject_GC_UnTrack(self);
956
0
    (void)contextvar_tp_clear(self);
957
0
    Py_TYPE(self)->tp_free(self);
958
0
}
959
960
static Py_hash_t
961
contextvar_tp_hash(PyObject *op)
962
220k
{
963
220k
    PyContextVar *self = _PyContextVar_CAST(op);
964
220k
    return self->var_hash;
965
220k
}
966
967
static PyObject *
968
contextvar_tp_repr(PyObject *op)
969
0
{
970
0
    PyContextVar *self = _PyContextVar_CAST(op);
971
    // Estimation based on the shortest name and default value,
972
    // but maximize the pointer size.
973
    // "<ContextVar name='a' at 0x1234567812345678>"
974
    // "<ContextVar name='a' default=1 at 0x1234567812345678>"
975
0
    Py_ssize_t estimate = self->var_default ? 53 : 43;
976
0
    PyUnicodeWriter *writer = PyUnicodeWriter_Create(estimate);
977
0
    if (writer == NULL) {
978
0
        return NULL;
979
0
    }
980
981
0
    if (PyUnicodeWriter_WriteASCII(writer, "<ContextVar name=", 17) < 0) {
982
0
        goto error;
983
0
    }
984
0
    if (PyUnicodeWriter_WriteRepr(writer, self->var_name) < 0) {
985
0
        goto error;
986
0
    }
987
988
0
    if (self->var_default != NULL) {
989
0
        if (PyUnicodeWriter_WriteASCII(writer, " default=", 9) < 0) {
990
0
            goto error;
991
0
        }
992
0
        if (PyUnicodeWriter_WriteRepr(writer, self->var_default) < 0) {
993
0
            goto error;
994
0
        }
995
0
    }
996
997
0
    if (PyUnicodeWriter_Format(writer, " at %p>", self) < 0) {
998
0
        goto error;
999
0
    }
1000
0
    return PyUnicodeWriter_Finish(writer);
1001
1002
0
error:
1003
0
    PyUnicodeWriter_Discard(writer);
1004
0
    return NULL;
1005
0
}
1006
1007
1008
/*[clinic input]
1009
@permit_long_docstring_body
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 method will:
1017
 * return the value of the default argument of the method, if provided; or
1018
 * return the default value for the context variable, if it was created
1019
   with one; or
1020
 * raise a LookupError.
1021
[clinic start generated code]*/
1022
1023
static PyObject *
1024
_contextvars_ContextVar_get_impl(PyContextVar *self, PyObject *default_value)
1025
/*[clinic end generated code: output=0746bd0aa2ced7bf input=da66664d5d0af4ad]*/
1026
4
{
1027
4
    PyObject *val;
1028
4
    if (PyContextVar_Get((PyObject *)self, default_value, &val) < 0) {
1029
0
        return NULL;
1030
0
    }
1031
1032
4
    if (val == NULL) {
1033
4
        PyErr_SetObject(PyExc_LookupError, (PyObject *)self);
1034
4
        return NULL;
1035
4
    }
1036
1037
0
    return val;
1038
4
}
1039
1040
/*[clinic input]
1041
@permit_long_docstring_body
1042
_contextvars.ContextVar.set
1043
    value: object
1044
    /
1045
1046
Call to set a new value for the context variable in the current context.
1047
1048
The required value argument is the new value for the context variable.
1049
1050
Returns a Token object that can be used to restore the variable to its previous
1051
value via the `ContextVar.reset()` method.
1052
[clinic start generated code]*/
1053
1054
static PyObject *
1055
_contextvars_ContextVar_set_impl(PyContextVar *self, PyObject *value)
1056
/*[clinic end generated code: output=1b562d35cc79c806 input=73ebbbfc7c98f6cd]*/
1057
4
{
1058
4
    return PyContextVar_Set((PyObject *)self, value);
1059
4
}
1060
1061
/*[clinic input]
1062
@permit_long_docstring_body
1063
_contextvars.ContextVar.reset
1064
    token: object
1065
    /
1066
1067
Reset the context variable.
1068
1069
The variable is reset to the value it had before the `ContextVar.set()` that
1070
created the token was used.
1071
[clinic start generated code]*/
1072
1073
static PyObject *
1074
_contextvars_ContextVar_reset_impl(PyContextVar *self, PyObject *token)
1075
/*[clinic end generated code: output=3205d2bdff568521 input=b8bc514a9245242a]*/
1076
0
{
1077
0
    if (!PyContextToken_CheckExact(token)) {
1078
0
        PyErr_Format(PyExc_TypeError,
1079
0
                     "expected an instance of Token, got %R", token);
1080
0
        return NULL;
1081
0
    }
1082
1083
0
    if (PyContextVar_Reset((PyObject *)self, token)) {
1084
0
        return NULL;
1085
0
    }
1086
1087
0
    Py_RETURN_NONE;
1088
0
}
1089
1090
1091
static PyMemberDef PyContextVar_members[] = {
1092
    {"name", _Py_T_OBJECT, offsetof(PyContextVar, var_name), Py_READONLY},
1093
    {NULL}
1094
};
1095
1096
static PyMethodDef PyContextVar_methods[] = {
1097
    _CONTEXTVARS_CONTEXTVAR_GET_METHODDEF
1098
    _CONTEXTVARS_CONTEXTVAR_SET_METHODDEF
1099
    _CONTEXTVARS_CONTEXTVAR_RESET_METHODDEF
1100
    {"__class_getitem__", Py_GenericAlias,
1101
    METH_O|METH_CLASS,       PyDoc_STR("See PEP 585")},
1102
    {NULL, NULL}
1103
};
1104
1105
PyTypeObject PyContextVar_Type = {
1106
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1107
    "_contextvars.ContextVar",
1108
    sizeof(PyContextVar),
1109
    .tp_methods = PyContextVar_methods,
1110
    .tp_members = PyContextVar_members,
1111
    .tp_dealloc = contextvar_tp_dealloc,
1112
    .tp_getattro = PyObject_GenericGetAttr,
1113
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1114
    .tp_traverse = contextvar_tp_traverse,
1115
    .tp_clear = contextvar_tp_clear,
1116
    .tp_new = contextvar_tp_new,
1117
    .tp_free = PyObject_GC_Del,
1118
    .tp_hash = contextvar_tp_hash,
1119
    .tp_repr = contextvar_tp_repr,
1120
};
1121
1122
1123
/////////////////////////// Token
1124
1125
static PyObject * get_token_missing(void);
1126
1127
1128
/*[clinic input]
1129
class _contextvars.Token "PyContextToken *" "&PyContextToken_Type"
1130
[clinic start generated code]*/
1131
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=338a5e2db13d3f5b]*/
1132
1133
1134
4
#define _PyContextToken_CAST(op)    ((PyContextToken *)(op))
1135
1136
1137
static PyObject *
1138
token_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1139
0
{
1140
0
    PyErr_SetString(PyExc_RuntimeError,
1141
0
                    "Tokens can only be created by ContextVars");
1142
0
    return NULL;
1143
0
}
1144
1145
static int
1146
token_tp_clear(PyObject *op)
1147
4
{
1148
4
    PyContextToken *self = _PyContextToken_CAST(op);
1149
4
    Py_CLEAR(self->tok_ctx);
1150
4
    Py_CLEAR(self->tok_var);
1151
4
    Py_CLEAR(self->tok_oldval);
1152
4
    return 0;
1153
4
}
1154
1155
static int
1156
token_tp_traverse(PyObject *op, visitproc visit, void *arg)
1157
0
{
1158
0
    PyContextToken *self = _PyContextToken_CAST(op);
1159
0
    Py_VISIT(self->tok_ctx);
1160
0
    Py_VISIT(self->tok_var);
1161
0
    Py_VISIT(self->tok_oldval);
1162
0
    return 0;
1163
0
}
1164
1165
static void
1166
token_tp_dealloc(PyObject *self)
1167
4
{
1168
4
    PyObject_GC_UnTrack(self);
1169
4
    (void)token_tp_clear(self);
1170
4
    Py_TYPE(self)->tp_free(self);
1171
4
}
1172
1173
static PyObject *
1174
token_tp_repr(PyObject *op)
1175
0
{
1176
0
    PyContextToken *self = _PyContextToken_CAST(op);
1177
0
    PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
1178
0
    if (writer == NULL) {
1179
0
        return NULL;
1180
0
    }
1181
0
    if (PyUnicodeWriter_WriteASCII(writer, "<Token", 6) < 0) {
1182
0
        goto error;
1183
0
    }
1184
0
    if (self->tok_used) {
1185
0
        if (PyUnicodeWriter_WriteASCII(writer, " used", 5) < 0) {
1186
0
            goto error;
1187
0
        }
1188
0
    }
1189
0
    if (PyUnicodeWriter_WriteASCII(writer, " var=", 5) < 0) {
1190
0
        goto error;
1191
0
    }
1192
0
    if (PyUnicodeWriter_WriteRepr(writer, (PyObject *)self->tok_var) < 0) {
1193
0
        goto error;
1194
0
    }
1195
0
    if (PyUnicodeWriter_Format(writer, " at %p>", self) < 0) {
1196
0
        goto error;
1197
0
    }
1198
0
    return PyUnicodeWriter_Finish(writer);
1199
1200
0
error:
1201
0
    PyUnicodeWriter_Discard(writer);
1202
0
    return NULL;
1203
0
}
1204
1205
static PyObject *
1206
token_get_var(PyObject *op, void *Py_UNUSED(ignored))
1207
0
{
1208
0
    PyContextToken *self = _PyContextToken_CAST(op);
1209
0
    return Py_NewRef(self->tok_var);;
1210
0
}
1211
1212
static PyObject *
1213
token_get_old_value(PyObject *op, void *Py_UNUSED(ignored))
1214
0
{
1215
0
    PyContextToken *self = _PyContextToken_CAST(op);
1216
0
    if (self->tok_oldval == NULL) {
1217
0
        return get_token_missing();
1218
0
    }
1219
1220
0
    return Py_NewRef(self->tok_oldval);
1221
0
}
1222
1223
static PyGetSetDef PyContextTokenType_getsetlist[] = {
1224
    {"var", token_get_var, NULL, NULL},
1225
    {"old_value", token_get_old_value, NULL, NULL},
1226
    {NULL}
1227
};
1228
1229
/*[clinic input]
1230
_contextvars.Token.__enter__ as token_enter
1231
1232
Enter into Token context manager.
1233
[clinic start generated code]*/
1234
1235
static PyObject *
1236
token_enter_impl(PyContextToken *self)
1237
/*[clinic end generated code: output=9af4d2054e93fb75 input=41a3d6c4195fd47a]*/
1238
0
{
1239
0
    return Py_NewRef(self);
1240
0
}
1241
1242
/*[clinic input]
1243
_contextvars.Token.__exit__ as token_exit
1244
1245
    type: object
1246
    val: object
1247
    tb: object
1248
    /
1249
1250
Exit from Token context manager, restore the linked ContextVar.
1251
[clinic start generated code]*/
1252
1253
static PyObject *
1254
token_exit_impl(PyContextToken *self, PyObject *type, PyObject *val,
1255
                PyObject *tb)
1256
/*[clinic end generated code: output=3e6a1c95d3da703a input=7f117445f0ccd92e]*/
1257
0
{
1258
0
    int ret = PyContextVar_Reset((PyObject *)self->tok_var, (PyObject *)self);
1259
0
    if (ret < 0) {
1260
0
        return NULL;
1261
0
    }
1262
0
    Py_RETURN_NONE;
1263
0
}
1264
1265
static PyMethodDef PyContextTokenType_methods[] = {
1266
    {"__class_getitem__",    Py_GenericAlias,
1267
    METH_O|METH_CLASS,       PyDoc_STR("See PEP 585")},
1268
    TOKEN_ENTER_METHODDEF
1269
    TOKEN_EXIT_METHODDEF
1270
    {NULL}
1271
};
1272
1273
PyTypeObject PyContextToken_Type = {
1274
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1275
    "_contextvars.Token",
1276
    sizeof(PyContextToken),
1277
    .tp_methods = PyContextTokenType_methods,
1278
    .tp_getset = PyContextTokenType_getsetlist,
1279
    .tp_dealloc = token_tp_dealloc,
1280
    .tp_getattro = PyObject_GenericGetAttr,
1281
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1282
    .tp_traverse = token_tp_traverse,
1283
    .tp_clear = token_tp_clear,
1284
    .tp_new = token_tp_new,
1285
    .tp_free = PyObject_GC_Del,
1286
    .tp_hash = PyObject_HashNotImplemented,
1287
    .tp_repr = token_tp_repr,
1288
};
1289
1290
static PyContextToken *
1291
token_new(PyContext *ctx, PyContextVar *var, PyObject *val)
1292
4
{
1293
4
    PyContextToken *tok = PyObject_GC_New(PyContextToken, &PyContextToken_Type);
1294
4
    if (tok == NULL) {
1295
0
        return NULL;
1296
0
    }
1297
1298
4
    tok->tok_ctx = (PyContext*)Py_NewRef(ctx);
1299
1300
4
    tok->tok_var = (PyContextVar*)Py_NewRef(var);
1301
1302
4
    tok->tok_oldval = Py_XNewRef(val);
1303
1304
4
    tok->tok_used = 0;
1305
1306
4
    PyObject_GC_Track(tok);
1307
4
    return tok;
1308
4
}
1309
1310
1311
/////////////////////////// Token.MISSING
1312
1313
1314
static PyObject *
1315
context_token_missing_tp_repr(PyObject *self)
1316
0
{
1317
0
    return PyUnicode_FromString("<Token.MISSING>");
1318
0
}
1319
1320
static void
1321
context_token_missing_tp_dealloc(PyObject *Py_UNUSED(self))
1322
0
{
1323
#ifdef Py_DEBUG
1324
    /* The singleton is statically allocated. */
1325
    _Py_FatalRefcountError("deallocating the token missing singleton");
1326
#else
1327
0
    return;
1328
0
#endif
1329
0
}
1330
1331
1332
PyTypeObject _PyContextTokenMissing_Type = {
1333
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1334
    "Token.MISSING",
1335
    sizeof(_PyContextTokenMissing),
1336
    .tp_dealloc = context_token_missing_tp_dealloc,
1337
    .tp_getattro = PyObject_GenericGetAttr,
1338
    .tp_flags = Py_TPFLAGS_DEFAULT,
1339
    .tp_repr = context_token_missing_tp_repr,
1340
};
1341
1342
1343
static PyObject *
1344
get_token_missing(void)
1345
22
{
1346
22
    return (PyObject *)&_Py_SINGLETON(context_token_missing);
1347
22
}
1348
1349
1350
///////////////////////////
1351
1352
1353
PyStatus
1354
_PyContext_Init(PyInterpreterState *interp)
1355
22
{
1356
22
    PyObject *missing = get_token_missing();
1357
22
    assert(PyUnstable_IsImmortal(missing));
1358
22
    if (PyDict_SetItemString(
1359
22
        _PyType_GetDict(&PyContextToken_Type), "MISSING", missing))
1360
0
    {
1361
0
        Py_DECREF(missing);
1362
0
        return _PyStatus_ERR("can't init context types");
1363
0
    }
1364
22
    Py_DECREF(missing);
1365
1366
22
    return _PyStatus_OK();
1367
22
}