Coverage Report

Created: 2025-08-26 06:26

/src/cpython/Python/context.c
Line
Count
Source (jump to first uncovered line)
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
16.0k
    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
static int
194
_PyContext_Enter(PyThreadState *ts, PyObject *octx)
195
0
{
196
0
    ENSURE_Context(octx, -1)
197
0
    PyContext *ctx = (PyContext *)octx;
198
199
0
    if (ctx->ctx_entered) {
200
0
        _PyErr_Format(ts, PyExc_RuntimeError,
201
0
                      "cannot enter context: %R is already entered", ctx);
202
0
        return -1;
203
0
    }
204
205
0
    ctx->ctx_prev = (PyContext *)ts->context;  /* borrow */
206
0
    ctx->ctx_entered = 1;
207
208
0
    ts->context = Py_NewRef(ctx);
209
0
    context_switched(ts);
210
0
    return 0;
211
0
}
212
213
214
int
215
PyContext_Enter(PyObject *octx)
216
0
{
217
0
    PyThreadState *ts = _PyThreadState_GET();
218
0
    assert(ts != NULL);
219
0
    return _PyContext_Enter(ts, octx);
220
0
}
221
222
223
static int
224
_PyContext_Exit(PyThreadState *ts, PyObject *octx)
225
0
{
226
0
    ENSURE_Context(octx, -1)
227
0
    PyContext *ctx = (PyContext *)octx;
228
229
0
    if (!ctx->ctx_entered) {
230
0
        PyErr_Format(PyExc_RuntimeError,
231
0
                     "cannot exit context: %R has not been entered", ctx);
232
0
        return -1;
233
0
    }
234
235
0
    if (ts->context != (PyObject *)ctx) {
236
        /* Can only happen if someone misuses the C API */
237
0
        PyErr_SetString(PyExc_RuntimeError,
238
0
                        "cannot exit context: thread state references "
239
0
                        "a different context object");
240
0
        return -1;
241
0
    }
242
243
0
    Py_SETREF(ts->context, (PyObject *)ctx->ctx_prev);
244
245
0
    ctx->ctx_prev = NULL;
246
0
    ctx->ctx_entered = 0;
247
0
    context_switched(ts);
248
0
    return 0;
249
0
}
250
251
int
252
PyContext_Exit(PyObject *octx)
253
0
{
254
0
    PyThreadState *ts = _PyThreadState_GET();
255
0
    assert(ts != NULL);
256
0
    return _PyContext_Exit(ts, octx);
257
0
}
258
259
260
PyObject *
261
PyContextVar_New(const char *name, PyObject *def)
262
16
{
263
16
    PyObject *pyname = PyUnicode_FromString(name);
264
16
    if (pyname == NULL) {
265
0
        return NULL;
266
0
    }
267
16
    PyContextVar *var = contextvar_new(pyname, def);
268
16
    Py_DECREF(pyname);
269
16
    return (PyObject *)var;
270
16
}
271
272
273
int
274
PyContextVar_Get(PyObject *ovar, PyObject *def, PyObject **val)
275
16.0k
{
276
16.0k
    ENSURE_ContextVar(ovar, -1)
277
16.0k
    PyContextVar *var = (PyContextVar *)ovar;
278
279
16.0k
    PyThreadState *ts = _PyThreadState_GET();
280
16.0k
    assert(ts != NULL);
281
16.0k
    if (ts->context == NULL) {
282
16.0k
        goto not_found;
283
16.0k
    }
284
285
0
#ifndef Py_GIL_DISABLED
286
0
    if (var->var_cached != NULL &&
287
0
            var->var_cached_tsid == ts->id &&
288
0
            var->var_cached_tsver == ts->context_ver)
289
0
    {
290
0
        *val = var->var_cached;
291
0
        goto found;
292
0
    }
293
0
#endif
294
295
0
    assert(PyContext_CheckExact(ts->context));
296
0
    PyHamtObject *vars = ((PyContext *)ts->context)->ctx_vars;
297
298
0
    PyObject *found = NULL;
299
0
    int res = _PyHamt_Find(vars, (PyObject*)var, &found);
300
0
    if (res < 0) {
301
0
        goto error;
302
0
    }
303
0
    if (res == 1) {
304
0
        assert(found != NULL);
305
0
#ifndef Py_GIL_DISABLED
306
0
        var->var_cached = found;  /* borrow */
307
0
        var->var_cached_tsid = ts->id;
308
0
        var->var_cached_tsver = ts->context_ver;
309
0
#endif
310
311
0
        *val = found;
312
0
        goto found;
313
0
    }
314
315
16.0k
not_found:
316
16.0k
    if (def == NULL) {
317
16.0k
        if (var->var_default != NULL) {
318
0
            *val = var->var_default;
319
0
            goto found;
320
0
        }
321
322
16.0k
        *val = NULL;
323
16.0k
        goto found;
324
16.0k
    }
325
0
    else {
326
0
        *val = def;
327
0
        goto found;
328
0
   }
329
330
16.0k
found:
331
16.0k
    Py_XINCREF(*val);
332
16.0k
    return 0;
333
334
0
error:
335
0
    *val = NULL;
336
0
    return -1;
337
16.0k
}
338
339
340
PyObject *
341
PyContextVar_Set(PyObject *ovar, PyObject *val)
342
0
{
343
0
    ENSURE_ContextVar(ovar, NULL)
344
0
    PyContextVar *var = (PyContextVar *)ovar;
345
346
0
    if (!PyContextVar_CheckExact(var)) {
347
0
        PyErr_SetString(
348
0
            PyExc_TypeError, "an instance of ContextVar was expected");
349
0
        return NULL;
350
0
    }
351
352
0
    PyContext *ctx = context_get();
353
0
    if (ctx == NULL) {
354
0
        return NULL;
355
0
    }
356
357
0
    PyObject *old_val = NULL;
358
0
    int found = _PyHamt_Find(ctx->ctx_vars, (PyObject *)var, &old_val);
359
0
    if (found < 0) {
360
0
        return NULL;
361
0
    }
362
363
0
    Py_XINCREF(old_val);
364
0
    PyContextToken *tok = token_new(ctx, var, old_val);
365
0
    Py_XDECREF(old_val);
366
367
0
    if (contextvar_set(var, val)) {
368
0
        Py_DECREF(tok);
369
0
        return NULL;
370
0
    }
371
372
0
    return (PyObject *)tok;
373
0
}
374
375
376
int
377
PyContextVar_Reset(PyObject *ovar, PyObject *otok)
378
0
{
379
0
    ENSURE_ContextVar(ovar, -1)
380
0
    ENSURE_ContextToken(otok, -1)
381
0
    PyContextVar *var = (PyContextVar *)ovar;
382
0
    PyContextToken *tok = (PyContextToken *)otok;
383
384
0
    if (tok->tok_used) {
385
0
        PyErr_Format(PyExc_RuntimeError,
386
0
                     "%R has already been used once", tok);
387
0
        return -1;
388
0
    }
389
390
0
    if (var != tok->tok_var) {
391
0
        PyErr_Format(PyExc_ValueError,
392
0
                     "%R was created by a different ContextVar", tok);
393
0
        return -1;
394
0
    }
395
396
0
    PyContext *ctx = context_get();
397
0
    if (ctx != tok->tok_ctx) {
398
0
        PyErr_Format(PyExc_ValueError,
399
0
                     "%R was created in a different Context", tok);
400
0
        return -1;
401
0
    }
402
403
0
    tok->tok_used = 1;
404
405
0
    if (tok->tok_oldval == NULL) {
406
0
        return contextvar_del(var);
407
0
    }
408
0
    else {
409
0
        return contextvar_set(var, tok->tok_oldval);
410
0
    }
411
0
}
412
413
414
/////////////////////////// PyContext
415
416
/*[clinic input]
417
class _contextvars.Context "PyContext *" "&PyContext_Type"
418
[clinic start generated code]*/
419
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=bdf87f8e0cb580e8]*/
420
421
422
0
#define _PyContext_CAST(op)     ((PyContext *)(op))
423
424
425
static inline PyContext *
426
_context_alloc(void)
427
0
{
428
0
    PyContext *ctx = _Py_FREELIST_POP(PyContext, contexts);
429
0
    if (ctx == NULL) {
430
0
        ctx = PyObject_GC_New(PyContext, &PyContext_Type);
431
0
        if (ctx == NULL) {
432
0
            return NULL;
433
0
        }
434
0
    }
435
436
0
    ctx->ctx_vars = NULL;
437
0
    ctx->ctx_prev = NULL;
438
0
    ctx->ctx_entered = 0;
439
0
    ctx->ctx_weakreflist = NULL;
440
441
0
    return ctx;
442
0
}
443
444
445
static PyContext *
446
context_new_empty(void)
447
0
{
448
0
    PyContext *ctx = _context_alloc();
449
0
    if (ctx == NULL) {
450
0
        return NULL;
451
0
    }
452
453
0
    ctx->ctx_vars = _PyHamt_New();
454
0
    if (ctx->ctx_vars == NULL) {
455
0
        Py_DECREF(ctx);
456
0
        return NULL;
457
0
    }
458
459
0
    _PyObject_GC_TRACK(ctx);
460
0
    return ctx;
461
0
}
462
463
464
static PyContext *
465
context_new_from_vars(PyHamtObject *vars)
466
0
{
467
0
    PyContext *ctx = _context_alloc();
468
0
    if (ctx == NULL) {
469
0
        return NULL;
470
0
    }
471
472
0
    ctx->ctx_vars = (PyHamtObject*)Py_NewRef(vars);
473
474
0
    _PyObject_GC_TRACK(ctx);
475
0
    return ctx;
476
0
}
477
478
479
static inline PyContext *
480
context_get(void)
481
0
{
482
0
    PyThreadState *ts = _PyThreadState_GET();
483
0
    assert(ts != NULL);
484
0
    PyContext *current_ctx = (PyContext *)ts->context;
485
0
    if (current_ctx == NULL) {
486
0
        current_ctx = context_new_empty();
487
0
        if (current_ctx == NULL) {
488
0
            return NULL;
489
0
        }
490
0
        ts->context = (PyObject *)current_ctx;
491
0
    }
492
0
    return current_ctx;
493
0
}
494
495
static int
496
context_check_key_type(PyObject *key)
497
0
{
498
0
    if (!PyContextVar_CheckExact(key)) {
499
        // abort();
500
0
        PyErr_Format(PyExc_TypeError,
501
0
                     "a ContextVar key was expected, got %R", key);
502
0
        return -1;
503
0
    }
504
0
    return 0;
505
0
}
506
507
static PyObject *
508
context_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
509
0
{
510
0
    if (PyTuple_Size(args) || (kwds != NULL && PyDict_Size(kwds))) {
511
0
        PyErr_SetString(
512
0
            PyExc_TypeError, "Context() does not accept any arguments");
513
0
        return NULL;
514
0
    }
515
0
    return PyContext_New();
516
0
}
517
518
static int
519
context_tp_clear(PyObject *op)
520
0
{
521
0
    PyContext *self = _PyContext_CAST(op);
522
0
    Py_CLEAR(self->ctx_prev);
523
0
    Py_CLEAR(self->ctx_vars);
524
0
    return 0;
525
0
}
526
527
static int
528
context_tp_traverse(PyObject *op, visitproc visit, void *arg)
529
0
{
530
0
    PyContext *self = _PyContext_CAST(op);
531
0
    Py_VISIT(self->ctx_prev);
532
0
    Py_VISIT(self->ctx_vars);
533
0
    return 0;
534
0
}
535
536
static void
537
context_tp_dealloc(PyObject *self)
538
0
{
539
0
    _PyObject_GC_UNTRACK(self);
540
0
    PyContext *ctx = _PyContext_CAST(self);
541
0
    if (ctx->ctx_weakreflist != NULL) {
542
0
        PyObject_ClearWeakRefs(self);
543
0
    }
544
0
    (void)context_tp_clear(self);
545
546
0
    _Py_FREELIST_FREE(contexts, self, Py_TYPE(self)->tp_free);
547
0
}
548
549
static PyObject *
550
context_tp_iter(PyObject *op)
551
0
{
552
0
    PyContext *self = _PyContext_CAST(op);
553
0
    return _PyHamt_NewIterKeys(self->ctx_vars);
554
0
}
555
556
static PyObject *
557
context_tp_richcompare(PyObject *v, PyObject *w, int op)
558
0
{
559
0
    if (!PyContext_CheckExact(v) || !PyContext_CheckExact(w) ||
560
0
            (op != Py_EQ && op != Py_NE))
561
0
    {
562
0
        Py_RETURN_NOTIMPLEMENTED;
563
0
    }
564
565
0
    int res = _PyHamt_Eq(
566
0
        ((PyContext *)v)->ctx_vars, ((PyContext *)w)->ctx_vars);
567
0
    if (res < 0) {
568
0
        return NULL;
569
0
    }
570
571
0
    if (op == Py_NE) {
572
0
        res = !res;
573
0
    }
574
575
0
    if (res) {
576
0
        Py_RETURN_TRUE;
577
0
    }
578
0
    else {
579
0
        Py_RETURN_FALSE;
580
0
    }
581
0
}
582
583
static Py_ssize_t
584
context_tp_len(PyObject *op)
585
0
{
586
0
    PyContext *self = _PyContext_CAST(op);
587
0
    return _PyHamt_Len(self->ctx_vars);
588
0
}
589
590
static PyObject *
591
context_tp_subscript(PyObject *op, PyObject *key)
592
0
{
593
0
    if (context_check_key_type(key)) {
594
0
        return NULL;
595
0
    }
596
0
    PyObject *val = NULL;
597
0
    PyContext *self = _PyContext_CAST(op);
598
0
    int found = _PyHamt_Find(self->ctx_vars, key, &val);
599
0
    if (found < 0) {
600
0
        return NULL;
601
0
    }
602
0
    if (found == 0) {
603
0
        PyErr_SetObject(PyExc_KeyError, key);
604
0
        return NULL;
605
0
    }
606
0
    return Py_NewRef(val);
607
0
}
608
609
static int
610
context_tp_contains(PyObject *op, PyObject *key)
611
0
{
612
0
    if (context_check_key_type(key)) {
613
0
        return -1;
614
0
    }
615
0
    PyObject *val = NULL;
616
0
    PyContext *self = _PyContext_CAST(op);
617
0
    return _PyHamt_Find(self->ctx_vars, key, &val);
618
0
}
619
620
621
/*[clinic input]
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 given,
630
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=c8eeb81505023995]*/
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
24
{
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
24
    Py_hash_t name_hash = PyObject_Hash(name);
859
24
    if (name_hash == -1) {
860
0
        return -1;
861
0
    }
862
863
24
    Py_hash_t res = Py_HashPointer(addr) ^ name_hash;
864
24
    return res == -1 ? -2 : res;
865
24
}
866
867
static PyContextVar *
868
contextvar_new(PyObject *name, PyObject *def)
869
24
{
870
24
    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
24
    PyContextVar *var = PyObject_GC_New(PyContextVar, &PyContextVar_Type);
877
24
    if (var == NULL) {
878
0
        return NULL;
879
0
    }
880
881
24
    var->var_name = Py_NewRef(name);
882
24
    var->var_default = Py_XNewRef(def);
883
884
24
#ifndef Py_GIL_DISABLED
885
24
    var->var_cached = NULL;
886
24
    var->var_cached_tsid = 0;
887
24
    var->var_cached_tsver = 0;
888
24
#endif
889
890
24
    var->var_hash = contextvar_generate_hash(var, name);
891
24
    if (var->var_hash == -1) {
892
0
        Py_DECREF(var);
893
0
        return NULL;
894
0
    }
895
896
24
    if (_PyObject_GC_MAY_BE_TRACKED(name) ||
897
24
            (def != NULL && _PyObject_GC_MAY_BE_TRACKED(def)))
898
0
    {
899
0
        PyObject_GC_Track(var);
900
0
    }
901
24
    return var;
902
24
}
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
8
{
917
8
    static char *kwlist[] = {"", "default", NULL};
918
8
    PyObject *name;
919
8
    PyObject *def = NULL;
920
921
8
    if (!PyArg_ParseTupleAndKeywords(
922
8
            args, kwds, "O|$O:ContextVar", kwlist, &name, &def))
923
0
    {
924
0
        return NULL;
925
0
    }
926
927
8
    return (PyObject *)contextvar_new(name, def);
928
8
}
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
@permit_long_docstring_body
1011
_contextvars.ContextVar.get
1012
    default: object = NULL
1013
    /
1014
1015
Return a value for the context variable for the current context.
1016
1017
If there is no value for the variable in the current context, the method will:
1018
 * return the value of the default argument of the method, if provided; or
1019
 * return the default value for the context variable, if it was created
1020
   with one; or
1021
 * raise a LookupError.
1022
[clinic start generated code]*/
1023
1024
static PyObject *
1025
_contextvars_ContextVar_get_impl(PyContextVar *self, PyObject *default_value)
1026
/*[clinic end generated code: output=0746bd0aa2ced7bf input=da66664d5d0af4ad]*/
1027
0
{
1028
0
    if (!PyContextVar_CheckExact(self)) {
1029
0
        PyErr_SetString(
1030
0
            PyExc_TypeError, "an instance of ContextVar was expected");
1031
0
        return NULL;
1032
0
    }
1033
1034
0
    PyObject *val;
1035
0
    if (PyContextVar_Get((PyObject *)self, default_value, &val) < 0) {
1036
0
        return NULL;
1037
0
    }
1038
1039
0
    if (val == NULL) {
1040
0
        PyErr_SetObject(PyExc_LookupError, (PyObject *)self);
1041
0
        return NULL;
1042
0
    }
1043
1044
0
    return val;
1045
0
}
1046
1047
/*[clinic input]
1048
@permit_long_docstring_body
1049
_contextvars.ContextVar.set
1050
    value: object
1051
    /
1052
1053
Call to set a new value for the context variable in the current context.
1054
1055
The required value argument is the new value for the context variable.
1056
1057
Returns a Token object that can be used to restore the variable to its previous
1058
value via the `ContextVar.reset()` method.
1059
[clinic start generated code]*/
1060
1061
static PyObject *
1062
_contextvars_ContextVar_set_impl(PyContextVar *self, PyObject *value)
1063
/*[clinic end generated code: output=1b562d35cc79c806 input=73ebbbfc7c98f6cd]*/
1064
0
{
1065
0
    return PyContextVar_Set((PyObject *)self, value);
1066
0
}
1067
1068
/*[clinic input]
1069
@permit_long_docstring_body
1070
_contextvars.ContextVar.reset
1071
    token: object
1072
    /
1073
1074
Reset the context variable.
1075
1076
The variable is reset to the value it had before the `ContextVar.set()` that
1077
created the token was used.
1078
[clinic start generated code]*/
1079
1080
static PyObject *
1081
_contextvars_ContextVar_reset_impl(PyContextVar *self, PyObject *token)
1082
/*[clinic end generated code: output=3205d2bdff568521 input=b8bc514a9245242a]*/
1083
0
{
1084
0
    if (!PyContextToken_CheckExact(token)) {
1085
0
        PyErr_Format(PyExc_TypeError,
1086
0
                     "expected an instance of Token, got %R", token);
1087
0
        return NULL;
1088
0
    }
1089
1090
0
    if (PyContextVar_Reset((PyObject *)self, token)) {
1091
0
        return NULL;
1092
0
    }
1093
1094
0
    Py_RETURN_NONE;
1095
0
}
1096
1097
1098
static PyMemberDef PyContextVar_members[] = {
1099
    {"name", _Py_T_OBJECT, offsetof(PyContextVar, var_name), Py_READONLY},
1100
    {NULL}
1101
};
1102
1103
static PyMethodDef PyContextVar_methods[] = {
1104
    _CONTEXTVARS_CONTEXTVAR_GET_METHODDEF
1105
    _CONTEXTVARS_CONTEXTVAR_SET_METHODDEF
1106
    _CONTEXTVARS_CONTEXTVAR_RESET_METHODDEF
1107
    {"__class_getitem__", Py_GenericAlias,
1108
    METH_O|METH_CLASS,       PyDoc_STR("See PEP 585")},
1109
    {NULL, NULL}
1110
};
1111
1112
PyTypeObject PyContextVar_Type = {
1113
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1114
    "_contextvars.ContextVar",
1115
    sizeof(PyContextVar),
1116
    .tp_methods = PyContextVar_methods,
1117
    .tp_members = PyContextVar_members,
1118
    .tp_dealloc = contextvar_tp_dealloc,
1119
    .tp_getattro = PyObject_GenericGetAttr,
1120
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1121
    .tp_traverse = contextvar_tp_traverse,
1122
    .tp_clear = contextvar_tp_clear,
1123
    .tp_new = contextvar_tp_new,
1124
    .tp_free = PyObject_GC_Del,
1125
    .tp_hash = contextvar_tp_hash,
1126
    .tp_repr = contextvar_tp_repr,
1127
};
1128
1129
1130
/////////////////////////// Token
1131
1132
static PyObject * get_token_missing(void);
1133
1134
1135
/*[clinic input]
1136
class _contextvars.Token "PyContextToken *" "&PyContextToken_Type"
1137
[clinic start generated code]*/
1138
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=338a5e2db13d3f5b]*/
1139
1140
1141
0
#define _PyContextToken_CAST(op)    ((PyContextToken *)(op))
1142
1143
1144
static PyObject *
1145
token_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1146
0
{
1147
0
    PyErr_SetString(PyExc_RuntimeError,
1148
0
                    "Tokens can only be created by ContextVars");
1149
0
    return NULL;
1150
0
}
1151
1152
static int
1153
token_tp_clear(PyObject *op)
1154
0
{
1155
0
    PyContextToken *self = _PyContextToken_CAST(op);
1156
0
    Py_CLEAR(self->tok_ctx);
1157
0
    Py_CLEAR(self->tok_var);
1158
0
    Py_CLEAR(self->tok_oldval);
1159
0
    return 0;
1160
0
}
1161
1162
static int
1163
token_tp_traverse(PyObject *op, visitproc visit, void *arg)
1164
0
{
1165
0
    PyContextToken *self = _PyContextToken_CAST(op);
1166
0
    Py_VISIT(self->tok_ctx);
1167
0
    Py_VISIT(self->tok_var);
1168
0
    Py_VISIT(self->tok_oldval);
1169
0
    return 0;
1170
0
}
1171
1172
static void
1173
token_tp_dealloc(PyObject *self)
1174
0
{
1175
0
    PyObject_GC_UnTrack(self);
1176
0
    (void)token_tp_clear(self);
1177
0
    Py_TYPE(self)->tp_free(self);
1178
0
}
1179
1180
static PyObject *
1181
token_tp_repr(PyObject *op)
1182
0
{
1183
0
    PyContextToken *self = _PyContextToken_CAST(op);
1184
0
    PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
1185
0
    if (writer == NULL) {
1186
0
        return NULL;
1187
0
    }
1188
0
    if (PyUnicodeWriter_WriteASCII(writer, "<Token", 6) < 0) {
1189
0
        goto error;
1190
0
    }
1191
0
    if (self->tok_used) {
1192
0
        if (PyUnicodeWriter_WriteASCII(writer, " used", 5) < 0) {
1193
0
            goto error;
1194
0
        }
1195
0
    }
1196
0
    if (PyUnicodeWriter_WriteASCII(writer, " var=", 5) < 0) {
1197
0
        goto error;
1198
0
    }
1199
0
    if (PyUnicodeWriter_WriteRepr(writer, (PyObject *)self->tok_var) < 0) {
1200
0
        goto error;
1201
0
    }
1202
0
    if (PyUnicodeWriter_Format(writer, " at %p>", self) < 0) {
1203
0
        goto error;
1204
0
    }
1205
0
    return PyUnicodeWriter_Finish(writer);
1206
1207
0
error:
1208
0
    PyUnicodeWriter_Discard(writer);
1209
0
    return NULL;
1210
0
}
1211
1212
static PyObject *
1213
token_get_var(PyObject *op, void *Py_UNUSED(ignored))
1214
0
{
1215
0
    PyContextToken *self = _PyContextToken_CAST(op);
1216
0
    return Py_NewRef(self->tok_var);;
1217
0
}
1218
1219
static PyObject *
1220
token_get_old_value(PyObject *op, void *Py_UNUSED(ignored))
1221
0
{
1222
0
    PyContextToken *self = _PyContextToken_CAST(op);
1223
0
    if (self->tok_oldval == NULL) {
1224
0
        return get_token_missing();
1225
0
    }
1226
1227
0
    return Py_NewRef(self->tok_oldval);
1228
0
}
1229
1230
static PyGetSetDef PyContextTokenType_getsetlist[] = {
1231
    {"var", token_get_var, NULL, NULL},
1232
    {"old_value", token_get_old_value, NULL, NULL},
1233
    {NULL}
1234
};
1235
1236
/*[clinic input]
1237
_contextvars.Token.__enter__ as token_enter
1238
1239
Enter into Token context manager.
1240
[clinic start generated code]*/
1241
1242
static PyObject *
1243
token_enter_impl(PyContextToken *self)
1244
/*[clinic end generated code: output=9af4d2054e93fb75 input=41a3d6c4195fd47a]*/
1245
0
{
1246
0
    return Py_NewRef(self);
1247
0
}
1248
1249
/*[clinic input]
1250
_contextvars.Token.__exit__ as token_exit
1251
1252
    type: object
1253
    val: object
1254
    tb: object
1255
    /
1256
1257
Exit from Token context manager, restore the linked ContextVar.
1258
[clinic start generated code]*/
1259
1260
static PyObject *
1261
token_exit_impl(PyContextToken *self, PyObject *type, PyObject *val,
1262
                PyObject *tb)
1263
/*[clinic end generated code: output=3e6a1c95d3da703a input=7f117445f0ccd92e]*/
1264
0
{
1265
0
    int ret = PyContextVar_Reset((PyObject *)self->tok_var, (PyObject *)self);
1266
0
    if (ret < 0) {
1267
0
        return NULL;
1268
0
    }
1269
0
    Py_RETURN_NONE;
1270
0
}
1271
1272
static PyMethodDef PyContextTokenType_methods[] = {
1273
    {"__class_getitem__",    Py_GenericAlias,
1274
    METH_O|METH_CLASS,       PyDoc_STR("See PEP 585")},
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
0
{
1300
0
    PyContextToken *tok = PyObject_GC_New(PyContextToken, &PyContextToken_Type);
1301
0
    if (tok == NULL) {
1302
0
        return NULL;
1303
0
    }
1304
1305
0
    tok->tok_ctx = (PyContext*)Py_NewRef(ctx);
1306
1307
0
    tok->tok_var = (PyContextVar*)Py_NewRef(var);
1308
1309
0
    tok->tok_oldval = Py_XNewRef(val);
1310
1311
0
    tok->tok_used = 0;
1312
1313
0
    PyObject_GC_Track(tok);
1314
0
    return tok;
1315
0
}
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
16
{
1353
16
    return (PyObject *)&_Py_SINGLETON(context_token_missing);
1354
16
}
1355
1356
1357
///////////////////////////
1358
1359
1360
PyStatus
1361
_PyContext_Init(PyInterpreterState *interp)
1362
16
{
1363
16
    if (!_Py_IsMainInterpreter(interp)) {
1364
0
        return _PyStatus_OK();
1365
0
    }
1366
1367
16
    PyObject *missing = get_token_missing();
1368
16
    if (PyDict_SetItemString(
1369
16
        _PyType_GetDict(&PyContextToken_Type), "MISSING", missing))
1370
0
    {
1371
0
        Py_DECREF(missing);
1372
0
        return _PyStatus_ERR("can't init context types");
1373
0
    }
1374
16
    Py_DECREF(missing);
1375
1376
16
    return _PyStatus_OK();
1377
16
}