Coverage Report

Created: 2026-07-16 07:04

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