Coverage Report

Created: 2026-05-30 06:18

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
37
{
268
37
    PyObject *pyname = PyUnicode_FromString(name);
269
37
    if (pyname == NULL) {
270
0
        return NULL;
271
0
    }
272
37
    PyContextVar *var = contextvar_new(pyname, def);
273
37
    Py_DECREF(pyname);
274
37
    return (PyObject *)var;
275
37
}
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
66
{
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
66
    Py_hash_t name_hash = PyObject_Hash(name);
859
66
    if (name_hash == -1) {
860
0
        return -1;
861
0
    }
862
863
66
    Py_hash_t res = Py_HashPointer(addr) ^ name_hash;
864
66
    return res == -1 ? -2 : res;
865
66
}
866
867
static PyContextVar *
868
contextvar_new(PyObject *name, PyObject *def)
869
66
{
870
66
    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
66
    PyContextVar *var = PyObject_GC_New(PyContextVar, &PyContextVar_Type);
877
66
    if (var == NULL) {
878
0
        return NULL;
879
0
    }
880
881
66
    var->var_name = Py_NewRef(name);
882
66
    var->var_default = Py_XNewRef(def);
883
884
66
#ifndef Py_GIL_DISABLED
885
66
    var->var_cached = NULL;
886
66
    var->var_cached_tsid = 0;
887
66
    var->var_cached_tsver = 0;
888
66
#endif
889
890
66
    var->var_hash = contextvar_generate_hash(var, name);
891
66
    if (var->var_hash == -1) {
892
0
        Py_DECREF(var);
893
0
        return NULL;
894
0
    }
895
896
66
    if (_PyObject_GC_MAY_BE_TRACKED(name) ||
897
66
            (def != NULL && _PyObject_GC_MAY_BE_TRACKED(def)))
898
0
    {
899
0
        PyObject_GC_Track(var);
900
0
    }
901
66
    return var;
902
66
}
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,       PyDoc_STR("See PEP 585")},
1104
    {NULL, NULL}
1105
};
1106
1107
PyTypeObject PyContextVar_Type = {
1108
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1109
    "_contextvars.ContextVar",
1110
    sizeof(PyContextVar),
1111
    .tp_methods = PyContextVar_methods,
1112
    .tp_members = PyContextVar_members,
1113
    .tp_dealloc = contextvar_tp_dealloc,
1114
    .tp_getattro = PyObject_GenericGetAttr,
1115
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1116
    .tp_traverse = contextvar_tp_traverse,
1117
    .tp_clear = contextvar_tp_clear,
1118
    .tp_new = contextvar_tp_new,
1119
    .tp_free = PyObject_GC_Del,
1120
    .tp_hash = contextvar_tp_hash,
1121
    .tp_repr = contextvar_tp_repr,
1122
};
1123
1124
1125
/////////////////////////// Token
1126
1127
static PyObject * get_token_missing(void);
1128
1129
1130
/*[clinic input]
1131
class _contextvars.Token "PyContextToken *" "&PyContextToken_Type"
1132
[clinic start generated code]*/
1133
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=338a5e2db13d3f5b]*/
1134
1135
1136
0
#define _PyContextToken_CAST(op)    ((PyContextToken *)(op))
1137
1138
1139
static PyObject *
1140
token_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1141
0
{
1142
0
    PyErr_SetString(PyExc_RuntimeError,
1143
0
                    "Tokens can only be created by ContextVars");
1144
0
    return NULL;
1145
0
}
1146
1147
static int
1148
token_tp_clear(PyObject *op)
1149
0
{
1150
0
    PyContextToken *self = _PyContextToken_CAST(op);
1151
0
    Py_CLEAR(self->tok_ctx);
1152
0
    Py_CLEAR(self->tok_var);
1153
0
    Py_CLEAR(self->tok_oldval);
1154
0
    return 0;
1155
0
}
1156
1157
static int
1158
token_tp_traverse(PyObject *op, visitproc visit, void *arg)
1159
0
{
1160
0
    PyContextToken *self = _PyContextToken_CAST(op);
1161
0
    Py_VISIT(self->tok_ctx);
1162
0
    Py_VISIT(self->tok_var);
1163
0
    Py_VISIT(self->tok_oldval);
1164
0
    return 0;
1165
0
}
1166
1167
static void
1168
token_tp_dealloc(PyObject *self)
1169
0
{
1170
0
    PyObject_GC_UnTrack(self);
1171
0
    (void)token_tp_clear(self);
1172
0
    Py_TYPE(self)->tp_free(self);
1173
0
}
1174
1175
static PyObject *
1176
token_tp_repr(PyObject *op)
1177
0
{
1178
0
    PyContextToken *self = _PyContextToken_CAST(op);
1179
0
    PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
1180
0
    if (writer == NULL) {
1181
0
        return NULL;
1182
0
    }
1183
0
    if (PyUnicodeWriter_WriteASCII(writer, "<Token", 6) < 0) {
1184
0
        goto error;
1185
0
    }
1186
0
    if (self->tok_used) {
1187
0
        if (PyUnicodeWriter_WriteASCII(writer, " used", 5) < 0) {
1188
0
            goto error;
1189
0
        }
1190
0
    }
1191
0
    if (PyUnicodeWriter_WriteASCII(writer, " var=", 5) < 0) {
1192
0
        goto error;
1193
0
    }
1194
0
    if (PyUnicodeWriter_WriteRepr(writer, (PyObject *)self->tok_var) < 0) {
1195
0
        goto error;
1196
0
    }
1197
0
    if (PyUnicodeWriter_Format(writer, " at %p>", self) < 0) {
1198
0
        goto error;
1199
0
    }
1200
0
    return PyUnicodeWriter_Finish(writer);
1201
1202
0
error:
1203
0
    PyUnicodeWriter_Discard(writer);
1204
0
    return NULL;
1205
0
}
1206
1207
static PyObject *
1208
token_get_var(PyObject *op, void *Py_UNUSED(ignored))
1209
0
{
1210
0
    PyContextToken *self = _PyContextToken_CAST(op);
1211
0
    return Py_NewRef(self->tok_var);;
1212
0
}
1213
1214
static PyObject *
1215
token_get_old_value(PyObject *op, void *Py_UNUSED(ignored))
1216
0
{
1217
0
    PyContextToken *self = _PyContextToken_CAST(op);
1218
0
    if (self->tok_oldval == NULL) {
1219
0
        return get_token_missing();
1220
0
    }
1221
1222
0
    return Py_NewRef(self->tok_oldval);
1223
0
}
1224
1225
static PyGetSetDef PyContextTokenType_getsetlist[] = {
1226
    {"var", token_get_var, NULL, NULL},
1227
    {"old_value", token_get_old_value, NULL, NULL},
1228
    {NULL}
1229
};
1230
1231
/*[clinic input]
1232
_contextvars.Token.__enter__ as token_enter
1233
1234
Enter into Token context manager.
1235
[clinic start generated code]*/
1236
1237
static PyObject *
1238
token_enter_impl(PyContextToken *self)
1239
/*[clinic end generated code: output=9af4d2054e93fb75 input=41a3d6c4195fd47a]*/
1240
0
{
1241
0
    return Py_NewRef(self);
1242
0
}
1243
1244
/*[clinic input]
1245
_contextvars.Token.__exit__ as token_exit
1246
1247
    type: object
1248
    val: object
1249
    tb: object
1250
    /
1251
1252
Exit from Token context manager, restore the linked ContextVar.
1253
[clinic start generated code]*/
1254
1255
static PyObject *
1256
token_exit_impl(PyContextToken *self, PyObject *type, PyObject *val,
1257
                PyObject *tb)
1258
/*[clinic end generated code: output=3e6a1c95d3da703a input=7f117445f0ccd92e]*/
1259
0
{
1260
0
    int ret = PyContextVar_Reset((PyObject *)self->tok_var, (PyObject *)self);
1261
0
    if (ret < 0) {
1262
0
        return NULL;
1263
0
    }
1264
0
    Py_RETURN_NONE;
1265
0
}
1266
1267
static PyMethodDef PyContextTokenType_methods[] = {
1268
    {"__class_getitem__",    Py_GenericAlias,
1269
    METH_O|METH_CLASS,       PyDoc_STR("See PEP 585")},
1270
    TOKEN_ENTER_METHODDEF
1271
    TOKEN_EXIT_METHODDEF
1272
    {NULL}
1273
};
1274
1275
PyTypeObject PyContextToken_Type = {
1276
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1277
    "_contextvars.Token",
1278
    sizeof(PyContextToken),
1279
    .tp_methods = PyContextTokenType_methods,
1280
    .tp_getset = PyContextTokenType_getsetlist,
1281
    .tp_dealloc = token_tp_dealloc,
1282
    .tp_getattro = PyObject_GenericGetAttr,
1283
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1284
    .tp_traverse = token_tp_traverse,
1285
    .tp_clear = token_tp_clear,
1286
    .tp_new = token_tp_new,
1287
    .tp_free = PyObject_GC_Del,
1288
    .tp_hash = PyObject_HashNotImplemented,
1289
    .tp_repr = token_tp_repr,
1290
};
1291
1292
static PyContextToken *
1293
token_new(PyContext *ctx, PyContextVar *var, PyObject *val)
1294
0
{
1295
0
    PyContextToken *tok = PyObject_GC_New(PyContextToken, &PyContextToken_Type);
1296
0
    if (tok == NULL) {
1297
0
        return NULL;
1298
0
    }
1299
1300
0
    tok->tok_ctx = (PyContext*)Py_NewRef(ctx);
1301
1302
0
    tok->tok_var = (PyContextVar*)Py_NewRef(var);
1303
1304
0
    tok->tok_oldval = Py_XNewRef(val);
1305
1306
0
    tok->tok_used = 0;
1307
1308
0
    PyObject_GC_Track(tok);
1309
0
    return tok;
1310
0
}
1311
1312
1313
/////////////////////////// Token.MISSING
1314
1315
1316
static PyObject *
1317
context_token_missing_tp_repr(PyObject *self)
1318
0
{
1319
0
    return PyUnicode_FromString("<Token.MISSING>");
1320
0
}
1321
1322
static void
1323
context_token_missing_tp_dealloc(PyObject *Py_UNUSED(self))
1324
0
{
1325
#ifdef Py_DEBUG
1326
    /* The singleton is statically allocated. */
1327
    _Py_FatalRefcountError("deallocating the token missing singleton");
1328
#else
1329
0
    return;
1330
0
#endif
1331
0
}
1332
1333
1334
PyTypeObject _PyContextTokenMissing_Type = {
1335
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1336
    "Token.MISSING",
1337
    sizeof(_PyContextTokenMissing),
1338
    .tp_dealloc = context_token_missing_tp_dealloc,
1339
    .tp_getattro = PyObject_GenericGetAttr,
1340
    .tp_flags = Py_TPFLAGS_DEFAULT,
1341
    .tp_repr = context_token_missing_tp_repr,
1342
};
1343
1344
1345
static PyObject *
1346
get_token_missing(void)
1347
37
{
1348
37
    return (PyObject *)&_Py_SINGLETON(context_token_missing);
1349
37
}
1350
1351
1352
///////////////////////////
1353
1354
1355
PyStatus
1356
_PyContext_Init(PyInterpreterState *interp)
1357
37
{
1358
37
    PyObject *missing = get_token_missing();
1359
37
    assert(PyUnstable_IsImmortal(missing));
1360
37
    if (PyDict_SetItemString(
1361
37
        _PyType_GetDict(&PyContextToken_Type), "MISSING", missing))
1362
0
    {
1363
0
        Py_DECREF(missing);
1364
0
        return _PyStatus_ERR("can't init context types");
1365
0
    }
1366
37
    Py_DECREF(missing);
1367
1368
37
    return _PyStatus_OK();
1369
37
}