Coverage Report

Created: 2025-07-04 06:49

/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
17.2k
    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
17.2k
{
276
17.2k
    ENSURE_ContextVar(ovar, -1)
277
17.2k
    PyContextVar *var = (PyContextVar *)ovar;
278
279
17.2k
    PyThreadState *ts = _PyThreadState_GET();
280
17.2k
    assert(ts != NULL);
281
17.2k
    if (ts->context == NULL) {
282
17.2k
        goto not_found;
283
17.2k
    }
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
17.2k
not_found:
316
17.2k
    if (def == NULL) {
317
17.2k
        if (var->var_default != NULL) {
318
0
            *val = var->var_default;
319
0
            goto found;
320
0
        }
321
322
17.2k
        *val = NULL;
323
17.2k
        goto found;
324
17.2k
    }
325
0
    else {
326
0
        *val = def;
327
0
        goto found;
328
0
   }
329
330
17.2k
found:
331
17.2k
    Py_XINCREF(*val);
332
17.2k
    return 0;
333
334
0
error:
335
0
    *val = NULL;
336
0
    return -1;
337
17.2k
}
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
_contextvars.ContextVar.get
1011
    default: object = NULL
1012
    /
1013
1014
Return a value for the context variable for the current context.
1015
1016
If there is no value for the variable in the current context, the method will:
1017
 * return the value of the default argument of the method, if provided; or
1018
 * return the default value for the context variable, if it was created
1019
   with one; or
1020
 * raise a LookupError.
1021
[clinic start generated code]*/
1022
1023
static PyObject *
1024
_contextvars_ContextVar_get_impl(PyContextVar *self, PyObject *default_value)
1025
/*[clinic end generated code: output=0746bd0aa2ced7bf input=30aa2ab9e433e401]*/
1026
0
{
1027
0
    if (!PyContextVar_CheckExact(self)) {
1028
0
        PyErr_SetString(
1029
0
            PyExc_TypeError, "an instance of ContextVar was expected");
1030
0
        return NULL;
1031
0
    }
1032
1033
0
    PyObject *val;
1034
0
    if (PyContextVar_Get((PyObject *)self, default_value, &val) < 0) {
1035
0
        return NULL;
1036
0
    }
1037
1038
0
    if (val == NULL) {
1039
0
        PyErr_SetObject(PyExc_LookupError, (PyObject *)self);
1040
0
        return NULL;
1041
0
    }
1042
1043
0
    return val;
1044
0
}
1045
1046
/*[clinic input]
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 variable.
1054
1055
Returns a Token object that can be used to restore the variable to its previous
1056
value via the `ContextVar.reset()` method.
1057
[clinic start generated code]*/
1058
1059
static PyObject *
1060
_contextvars_ContextVar_set_impl(PyContextVar *self, PyObject *value)
1061
/*[clinic end generated code: output=1b562d35cc79c806 input=c0a6887154227453]*/
1062
0
{
1063
0
    return PyContextVar_Set((PyObject *)self, value);
1064
0
}
1065
1066
/*[clinic input]
1067
_contextvars.ContextVar.reset
1068
    token: object
1069
    /
1070
1071
Reset the context variable.
1072
1073
The variable is reset to the value it had before the `ContextVar.set()` that
1074
created the token was used.
1075
[clinic start generated code]*/
1076
1077
static PyObject *
1078
_contextvars_ContextVar_reset_impl(PyContextVar *self, PyObject *token)
1079
/*[clinic end generated code: output=3205d2bdff568521 input=ebe2881e5af4ffda]*/
1080
0
{
1081
0
    if (!PyContextToken_CheckExact(token)) {
1082
0
        PyErr_Format(PyExc_TypeError,
1083
0
                     "expected an instance of Token, got %R", token);
1084
0
        return NULL;
1085
0
    }
1086
1087
0
    if (PyContextVar_Reset((PyObject *)self, token)) {
1088
0
        return NULL;
1089
0
    }
1090
1091
0
    Py_RETURN_NONE;
1092
0
}
1093
1094
1095
static PyMemberDef PyContextVar_members[] = {
1096
    {"name", _Py_T_OBJECT, offsetof(PyContextVar, var_name), Py_READONLY},
1097
    {NULL}
1098
};
1099
1100
static PyMethodDef PyContextVar_methods[] = {
1101
    _CONTEXTVARS_CONTEXTVAR_GET_METHODDEF
1102
    _CONTEXTVARS_CONTEXTVAR_SET_METHODDEF
1103
    _CONTEXTVARS_CONTEXTVAR_RESET_METHODDEF
1104
    {"__class_getitem__", Py_GenericAlias,
1105
    METH_O|METH_CLASS,       PyDoc_STR("See PEP 585")},
1106
    {NULL, NULL}
1107
};
1108
1109
PyTypeObject PyContextVar_Type = {
1110
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1111
    "_contextvars.ContextVar",
1112
    sizeof(PyContextVar),
1113
    .tp_methods = PyContextVar_methods,
1114
    .tp_members = PyContextVar_members,
1115
    .tp_dealloc = contextvar_tp_dealloc,
1116
    .tp_getattro = PyObject_GenericGetAttr,
1117
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1118
    .tp_traverse = contextvar_tp_traverse,
1119
    .tp_clear = contextvar_tp_clear,
1120
    .tp_new = contextvar_tp_new,
1121
    .tp_free = PyObject_GC_Del,
1122
    .tp_hash = contextvar_tp_hash,
1123
    .tp_repr = contextvar_tp_repr,
1124
};
1125
1126
1127
/////////////////////////// Token
1128
1129
static PyObject * get_token_missing(void);
1130
1131
1132
/*[clinic input]
1133
class _contextvars.Token "PyContextToken *" "&PyContextToken_Type"
1134
[clinic start generated code]*/
1135
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=338a5e2db13d3f5b]*/
1136
1137
1138
0
#define _PyContextToken_CAST(op)    ((PyContextToken *)(op))
1139
1140
1141
static PyObject *
1142
token_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1143
0
{
1144
0
    PyErr_SetString(PyExc_RuntimeError,
1145
0
                    "Tokens can only be created by ContextVars");
1146
0
    return NULL;
1147
0
}
1148
1149
static int
1150
token_tp_clear(PyObject *op)
1151
0
{
1152
0
    PyContextToken *self = _PyContextToken_CAST(op);
1153
0
    Py_CLEAR(self->tok_ctx);
1154
0
    Py_CLEAR(self->tok_var);
1155
0
    Py_CLEAR(self->tok_oldval);
1156
0
    return 0;
1157
0
}
1158
1159
static int
1160
token_tp_traverse(PyObject *op, visitproc visit, void *arg)
1161
0
{
1162
0
    PyContextToken *self = _PyContextToken_CAST(op);
1163
0
    Py_VISIT(self->tok_ctx);
1164
0
    Py_VISIT(self->tok_var);
1165
0
    Py_VISIT(self->tok_oldval);
1166
0
    return 0;
1167
0
}
1168
1169
static void
1170
token_tp_dealloc(PyObject *self)
1171
0
{
1172
0
    PyObject_GC_UnTrack(self);
1173
0
    (void)token_tp_clear(self);
1174
0
    Py_TYPE(self)->tp_free(self);
1175
0
}
1176
1177
static PyObject *
1178
token_tp_repr(PyObject *op)
1179
0
{
1180
0
    PyContextToken *self = _PyContextToken_CAST(op);
1181
0
    PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
1182
0
    if (writer == NULL) {
1183
0
        return NULL;
1184
0
    }
1185
0
    if (PyUnicodeWriter_WriteASCII(writer, "<Token", 6) < 0) {
1186
0
        goto error;
1187
0
    }
1188
0
    if (self->tok_used) {
1189
0
        if (PyUnicodeWriter_WriteASCII(writer, " used", 5) < 0) {
1190
0
            goto error;
1191
0
        }
1192
0
    }
1193
0
    if (PyUnicodeWriter_WriteASCII(writer, " var=", 5) < 0) {
1194
0
        goto error;
1195
0
    }
1196
0
    if (PyUnicodeWriter_WriteRepr(writer, (PyObject *)self->tok_var) < 0) {
1197
0
        goto error;
1198
0
    }
1199
0
    if (PyUnicodeWriter_Format(writer, " at %p>", self) < 0) {
1200
0
        goto error;
1201
0
    }
1202
0
    return PyUnicodeWriter_Finish(writer);
1203
1204
0
error:
1205
0
    PyUnicodeWriter_Discard(writer);
1206
0
    return NULL;
1207
0
}
1208
1209
static PyObject *
1210
token_get_var(PyObject *op, void *Py_UNUSED(ignored))
1211
0
{
1212
0
    PyContextToken *self = _PyContextToken_CAST(op);
1213
0
    return Py_NewRef(self->tok_var);;
1214
0
}
1215
1216
static PyObject *
1217
token_get_old_value(PyObject *op, void *Py_UNUSED(ignored))
1218
0
{
1219
0
    PyContextToken *self = _PyContextToken_CAST(op);
1220
0
    if (self->tok_oldval == NULL) {
1221
0
        return get_token_missing();
1222
0
    }
1223
1224
0
    return Py_NewRef(self->tok_oldval);
1225
0
}
1226
1227
static PyGetSetDef PyContextTokenType_getsetlist[] = {
1228
    {"var", token_get_var, NULL, NULL},
1229
    {"old_value", token_get_old_value, NULL, NULL},
1230
    {NULL}
1231
};
1232
1233
/*[clinic input]
1234
_contextvars.Token.__enter__ as token_enter
1235
1236
Enter into Token context manager.
1237
[clinic start generated code]*/
1238
1239
static PyObject *
1240
token_enter_impl(PyContextToken *self)
1241
/*[clinic end generated code: output=9af4d2054e93fb75 input=41a3d6c4195fd47a]*/
1242
0
{
1243
0
    return Py_NewRef(self);
1244
0
}
1245
1246
/*[clinic input]
1247
_contextvars.Token.__exit__ as token_exit
1248
1249
    type: object
1250
    val: object
1251
    tb: object
1252
    /
1253
1254
Exit from Token context manager, restore the linked ContextVar.
1255
[clinic start generated code]*/
1256
1257
static PyObject *
1258
token_exit_impl(PyContextToken *self, PyObject *type, PyObject *val,
1259
                PyObject *tb)
1260
/*[clinic end generated code: output=3e6a1c95d3da703a input=7f117445f0ccd92e]*/
1261
0
{
1262
0
    int ret = PyContextVar_Reset((PyObject *)self->tok_var, (PyObject *)self);
1263
0
    if (ret < 0) {
1264
0
        return NULL;
1265
0
    }
1266
0
    Py_RETURN_NONE;
1267
0
}
1268
1269
static PyMethodDef PyContextTokenType_methods[] = {
1270
    {"__class_getitem__",    Py_GenericAlias,
1271
    METH_O|METH_CLASS,       PyDoc_STR("See PEP 585")},
1272
    TOKEN_ENTER_METHODDEF
1273
    TOKEN_EXIT_METHODDEF
1274
    {NULL}
1275
};
1276
1277
PyTypeObject PyContextToken_Type = {
1278
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1279
    "_contextvars.Token",
1280
    sizeof(PyContextToken),
1281
    .tp_methods = PyContextTokenType_methods,
1282
    .tp_getset = PyContextTokenType_getsetlist,
1283
    .tp_dealloc = token_tp_dealloc,
1284
    .tp_getattro = PyObject_GenericGetAttr,
1285
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
1286
    .tp_traverse = token_tp_traverse,
1287
    .tp_clear = token_tp_clear,
1288
    .tp_new = token_tp_new,
1289
    .tp_free = PyObject_GC_Del,
1290
    .tp_hash = PyObject_HashNotImplemented,
1291
    .tp_repr = token_tp_repr,
1292
};
1293
1294
static PyContextToken *
1295
token_new(PyContext *ctx, PyContextVar *var, PyObject *val)
1296
0
{
1297
0
    PyContextToken *tok = PyObject_GC_New(PyContextToken, &PyContextToken_Type);
1298
0
    if (tok == NULL) {
1299
0
        return NULL;
1300
0
    }
1301
1302
0
    tok->tok_ctx = (PyContext*)Py_NewRef(ctx);
1303
1304
0
    tok->tok_var = (PyContextVar*)Py_NewRef(var);
1305
1306
0
    tok->tok_oldval = Py_XNewRef(val);
1307
1308
0
    tok->tok_used = 0;
1309
1310
0
    PyObject_GC_Track(tok);
1311
0
    return tok;
1312
0
}
1313
1314
1315
/////////////////////////// Token.MISSING
1316
1317
1318
static PyObject *
1319
context_token_missing_tp_repr(PyObject *self)
1320
0
{
1321
0
    return PyUnicode_FromString("<Token.MISSING>");
1322
0
}
1323
1324
static void
1325
context_token_missing_tp_dealloc(PyObject *Py_UNUSED(self))
1326
0
{
1327
#ifdef Py_DEBUG
1328
    /* The singleton is statically allocated. */
1329
    _Py_FatalRefcountError("deallocating the token missing singleton");
1330
#else
1331
0
    return;
1332
0
#endif
1333
0
}
1334
1335
1336
PyTypeObject _PyContextTokenMissing_Type = {
1337
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
1338
    "Token.MISSING",
1339
    sizeof(_PyContextTokenMissing),
1340
    .tp_dealloc = context_token_missing_tp_dealloc,
1341
    .tp_getattro = PyObject_GenericGetAttr,
1342
    .tp_flags = Py_TPFLAGS_DEFAULT,
1343
    .tp_repr = context_token_missing_tp_repr,
1344
};
1345
1346
1347
static PyObject *
1348
get_token_missing(void)
1349
16
{
1350
16
    return (PyObject *)&_Py_SINGLETON(context_token_missing);
1351
16
}
1352
1353
1354
///////////////////////////
1355
1356
1357
PyStatus
1358
_PyContext_Init(PyInterpreterState *interp)
1359
16
{
1360
16
    if (!_Py_IsMainInterpreter(interp)) {
1361
0
        return _PyStatus_OK();
1362
0
    }
1363
1364
16
    PyObject *missing = get_token_missing();
1365
16
    if (PyDict_SetItemString(
1366
16
        _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
16
    Py_DECREF(missing);
1372
1373
16
    return _PyStatus_OK();
1374
16
}