bpo-32436: Implement PEP 567 (#5027)

This commit is contained in:
Yury Selivanov 2018-01-22 19:11:18 -05:00 committed by GitHub
parent 9089a26591
commit f23746a934
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 6269 additions and 120 deletions

View file

@ -109,6 +109,7 @@
#include "pyerrors.h"
#include "pystate.h"
#include "context.h"
#include "pyarena.h"
#include "modsupport.h"

86
Include/context.h Normal file
View file

@ -0,0 +1,86 @@
#ifndef Py_CONTEXT_H
#define Py_CONTEXT_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef Py_LIMITED_API
PyAPI_DATA(PyTypeObject) PyContext_Type;
typedef struct _pycontextobject PyContext;
PyAPI_DATA(PyTypeObject) PyContextVar_Type;
typedef struct _pycontextvarobject PyContextVar;
PyAPI_DATA(PyTypeObject) PyContextToken_Type;
typedef struct _pycontexttokenobject PyContextToken;
#define PyContext_CheckExact(o) (Py_TYPE(o) == &PyContext_Type)
#define PyContextVar_CheckExact(o) (Py_TYPE(o) == &PyContextVar_Type)
#define PyContextToken_CheckExact(o) (Py_TYPE(o) == &PyContextToken_Type)
PyAPI_FUNC(PyContext *) PyContext_New(void);
PyAPI_FUNC(PyContext *) PyContext_Copy(PyContext *);
PyAPI_FUNC(PyContext *) PyContext_CopyCurrent(void);
PyAPI_FUNC(int) PyContext_Enter(PyContext *);
PyAPI_FUNC(int) PyContext_Exit(PyContext *);
/* Create a new context variable.
default_value can be NULL.
*/
PyAPI_FUNC(PyContextVar *) PyContextVar_New(
const char *name, PyObject *default_value);
/* Get a value for the variable.
Returns -1 if an error occurred during lookup.
Returns 0 if value either was or was not found.
If value was found, *value will point to it.
If not, it will point to:
- default_value, if not NULL;
- the default value of "var", if not NULL;
- NULL.
'*value' will be a new ref, if not NULL.
*/
PyAPI_FUNC(int) PyContextVar_Get(
PyContextVar *var, PyObject *default_value, PyObject **value);
/* Set a new value for the variable.
Returns NULL if an error occurs.
*/
PyAPI_FUNC(PyContextToken *) PyContextVar_Set(
PyContextVar *var, PyObject *value);
/* Reset a variable to its previous value.
Returns 0 on sucess, -1 on error.
*/
PyAPI_FUNC(int) PyContextVar_Reset(
PyContextVar *var, PyContextToken *token);
/* This method is exposed only for CPython tests. Don not use it. */
PyAPI_FUNC(PyObject *) _PyContext_NewHamtForTests(void);
PyAPI_FUNC(int) PyContext_ClearFreeList(void);
#endif /* !Py_LIMITED_API */
#ifdef __cplusplus
}
#endif
#endif /* !Py_CONTEXT_H */

View file

@ -0,0 +1,41 @@
#ifndef Py_INTERNAL_CONTEXT_H
#define Py_INTERNAL_CONTEXT_H
#include "internal/hamt.h"
struct _pycontextobject {
PyObject_HEAD
PyContext *ctx_prev;
PyHamtObject *ctx_vars;
PyObject *ctx_weakreflist;
int ctx_entered;
};
struct _pycontextvarobject {
PyObject_HEAD
PyObject *var_name;
PyObject *var_default;
PyObject *var_cached;
uint64_t var_cached_tsid;
uint64_t var_cached_tsver;
Py_hash_t var_hash;
};
struct _pycontexttokenobject {
PyObject_HEAD
PyContext *tok_ctx;
PyContextVar *tok_var;
PyObject *tok_oldval;
int tok_used;
};
int _PyContext_Init(void);
void _PyContext_Fini(void);
#endif /* !Py_INTERNAL_CONTEXT_H */

113
Include/internal/hamt.h Normal file
View file

@ -0,0 +1,113 @@
#ifndef Py_INTERNAL_HAMT_H
#define Py_INTERNAL_HAMT_H
#define _Py_HAMT_MAX_TREE_DEPTH 7
#define PyHamt_Check(o) (Py_TYPE(o) == &_PyHamt_Type)
/* Abstract tree node. */
typedef struct {
PyObject_HEAD
} PyHamtNode;
/* An HAMT immutable mapping collection. */
typedef struct {
PyObject_HEAD
PyHamtNode *h_root;
PyObject *h_weakreflist;
Py_ssize_t h_count;
} PyHamtObject;
/* A struct to hold the state of depth-first traverse of the tree.
HAMT is an immutable collection. Iterators will hold a strong reference
to it, and every node in the HAMT has strong references to its children.
So for iterators, we can implement zero allocations and zero reference
inc/dec depth-first iteration.
- i_nodes: an array of seven pointers to tree nodes
- i_level: the current node in i_nodes
- i_pos: an array of positions within nodes in i_nodes.
*/
typedef struct {
PyHamtNode *i_nodes[_Py_HAMT_MAX_TREE_DEPTH];
Py_ssize_t i_pos[_Py_HAMT_MAX_TREE_DEPTH];
int8_t i_level;
} PyHamtIteratorState;
/* Base iterator object.
Contains the iteration state, a pointer to the HAMT tree,
and a pointer to the 'yield function'. The latter is a simple
function that returns a key/value tuple for the 'Items' iterator,
just a key for the 'Keys' iterator, and a value for the 'Values'
iterator.
*/
typedef struct {
PyObject_HEAD
PyHamtObject *hi_obj;
PyHamtIteratorState hi_iter;
binaryfunc hi_yield;
} PyHamtIterator;
PyAPI_DATA(PyTypeObject) _PyHamt_Type;
PyAPI_DATA(PyTypeObject) _PyHamt_ArrayNode_Type;
PyAPI_DATA(PyTypeObject) _PyHamt_BitmapNode_Type;
PyAPI_DATA(PyTypeObject) _PyHamt_CollisionNode_Type;
PyAPI_DATA(PyTypeObject) _PyHamtKeys_Type;
PyAPI_DATA(PyTypeObject) _PyHamtValues_Type;
PyAPI_DATA(PyTypeObject) _PyHamtItems_Type;
/* Create a new HAMT immutable mapping. */
PyHamtObject * _PyHamt_New(void);
/* Return a new collection based on "o", but with an additional
key/val pair. */
PyHamtObject * _PyHamt_Assoc(PyHamtObject *o, PyObject *key, PyObject *val);
/* Return a new collection based on "o", but without "key". */
PyHamtObject * _PyHamt_Without(PyHamtObject *o, PyObject *key);
/* Find "key" in the "o" collection.
Return:
- -1: An error ocurred.
- 0: "key" wasn't found in "o".
- 1: "key" is in "o"; "*val" is set to its value (a borrowed ref).
*/
int _PyHamt_Find(PyHamtObject *o, PyObject *key, PyObject **val);
/* Check if "v" is equal to "w".
Return:
- 0: v != w
- 1: v == w
- -1: An error occurred.
*/
int _PyHamt_Eq(PyHamtObject *v, PyHamtObject *w);
/* Return the size of "o"; equivalent of "len(o)". */
Py_ssize_t _PyHamt_Len(PyHamtObject *o);
/* Return a Keys iterator over "o". */
PyObject * _PyHamt_NewIterKeys(PyHamtObject *o);
/* Return a Values iterator over "o". */
PyObject * _PyHamt_NewIterValues(PyHamtObject *o);
/* Return a Items iterator over "o". */
PyObject * _PyHamt_NewIterItems(PyHamtObject *o);
int _PyHamt_Init(void);
void _PyHamt_Fini(void);
#endif /* !Py_INTERNAL_HAMT_H */

View file

@ -143,6 +143,8 @@ typedef struct _is {
/* AtExit module */
void (*pyexitfunc)(PyObject *);
PyObject *pyexitmodule;
uint64_t tstate_next_unique_id;
} PyInterpreterState;
#endif /* !Py_LIMITED_API */
@ -270,6 +272,12 @@ typedef struct _ts {
PyObject *async_gen_firstiter;
PyObject *async_gen_finalizer;
PyObject *context;
uint64_t context_ver;
/* Unique thread state id. */
uint64_t id;
/* XXX signal handlers should also be here */
} PyThreadState;

View file

@ -489,7 +489,7 @@ def time(self):
"""
return time.monotonic()
def call_later(self, delay, callback, *args):
def call_later(self, delay, callback, *args, context=None):
"""Arrange for a callback to be called at a given time.
Return a Handle: an opaque object with a cancel() method that
@ -505,12 +505,13 @@ def call_later(self, delay, callback, *args):
Any positional arguments after the callback will be passed to
the callback when it is called.
"""
timer = self.call_at(self.time() + delay, callback, *args)
timer = self.call_at(self.time() + delay, callback, *args,
context=context)
if timer._source_traceback:
del timer._source_traceback[-1]
return timer
def call_at(self, when, callback, *args):
def call_at(self, when, callback, *args, context=None):
"""Like call_later(), but uses an absolute time.
Absolute time corresponds to the event loop's time() method.
@ -519,14 +520,14 @@ def call_at(self, when, callback, *args):
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_at')
timer = events.TimerHandle(when, callback, args, self)
timer = events.TimerHandle(when, callback, args, self, context)
if timer._source_traceback:
del timer._source_traceback[-1]
heapq.heappush(self._scheduled, timer)
timer._scheduled = True
return timer
def call_soon(self, callback, *args):
def call_soon(self, callback, *args, context=None):
"""Arrange for a callback to be called as soon as possible.
This operates as a FIFO queue: callbacks are called in the
@ -540,7 +541,7 @@ def call_soon(self, callback, *args):
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_soon')
handle = self._call_soon(callback, args)
handle = self._call_soon(callback, args, context)
if handle._source_traceback:
del handle._source_traceback[-1]
return handle
@ -555,8 +556,8 @@ def _check_callback(self, callback, method):
f'a callable object was expected by {method}(), '
f'got {callback!r}')
def _call_soon(self, callback, args):
handle = events.Handle(callback, args, self)
def _call_soon(self, callback, args, context):
handle = events.Handle(callback, args, self, context)
if handle._source_traceback:
del handle._source_traceback[-1]
self._ready.append(handle)
@ -579,12 +580,12 @@ def _check_thread(self):
"Non-thread-safe operation invoked on an event loop other "
"than the current one")
def call_soon_threadsafe(self, callback, *args):
def call_soon_threadsafe(self, callback, *args, context=None):
"""Like call_soon(), but thread-safe."""
self._check_closed()
if self._debug:
self._check_callback(callback, 'call_soon_threadsafe')
handle = self._call_soon(callback, args)
handle = self._call_soon(callback, args, context)
if handle._source_traceback:
del handle._source_traceback[-1]
self._write_to_self()

View file

@ -41,13 +41,13 @@ def format_cb(callback):
return format_helpers._format_callback_source(callback, ())
if size == 1:
cb = format_cb(cb[0])
cb = format_cb(cb[0][0])
elif size == 2:
cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1]))
cb = '{}, {}'.format(format_cb(cb[0][0]), format_cb(cb[1][0]))
elif size > 2:
cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
cb = '{}, <{} more>, {}'.format(format_cb(cb[0][0]),
size - 2,
format_cb(cb[-1]))
format_cb(cb[-1][0]))
return f'cb=[{cb}]'

View file

@ -11,6 +11,7 @@
'_get_running_loop',
)
import contextvars
import os
import socket
import subprocess
@ -32,9 +33,13 @@ class Handle:
"""Object returned by callback registration methods."""
__slots__ = ('_callback', '_args', '_cancelled', '_loop',
'_source_traceback', '_repr', '__weakref__')
'_source_traceback', '_repr', '__weakref__',
'_context')
def __init__(self, callback, args, loop):
def __init__(self, callback, args, loop, context=None):
if context is None:
context = contextvars.copy_context()
self._context = context
self._loop = loop
self._callback = callback
self._args = args
@ -80,7 +85,7 @@ def cancelled(self):
def _run(self):
try:
self._callback(*self._args)
self._context.run(self._callback, *self._args)
except Exception as exc:
cb = format_helpers._format_callback_source(
self._callback, self._args)
@ -101,9 +106,9 @@ class TimerHandle(Handle):
__slots__ = ['_scheduled', '_when']
def __init__(self, when, callback, args, loop):
def __init__(self, when, callback, args, loop, context=None):
assert when is not None
super().__init__(callback, args, loop)
super().__init__(callback, args, loop, context)
if self._source_traceback:
del self._source_traceback[-1]
self._when = when

View file

@ -6,6 +6,7 @@
)
import concurrent.futures
import contextvars
import logging
import sys
@ -144,8 +145,8 @@ def _schedule_callbacks(self):
return
self._callbacks[:] = []
for callback in callbacks:
self._loop.call_soon(callback, self)
for callback, ctx in callbacks:
self._loop.call_soon(callback, self, context=ctx)
def cancelled(self):
"""Return True if the future was cancelled."""
@ -192,7 +193,7 @@ def exception(self):
self.__log_traceback = False
return self._exception
def add_done_callback(self, fn):
def add_done_callback(self, fn, *, context=None):
"""Add a callback to be run when the future becomes done.
The callback is called with a single argument - the future object. If
@ -200,9 +201,11 @@ def add_done_callback(self, fn):
scheduled with call_soon.
"""
if self._state != _PENDING:
self._loop.call_soon(fn, self)
self._loop.call_soon(fn, self, context=context)
else:
self._callbacks.append(fn)
if context is None:
context = contextvars.copy_context()
self._callbacks.append((fn, context))
# New method not in PEP 3148.
@ -211,7 +214,9 @@ def remove_done_callback(self, fn):
Returns the number of callbacks removed.
"""
filtered_callbacks = [f for f in self._callbacks if f != fn]
filtered_callbacks = [(f, ctx)
for (f, ctx) in self._callbacks
if f != fn]
removed_count = len(self._callbacks) - len(filtered_callbacks)
if removed_count:
self._callbacks[:] = filtered_callbacks

View file

@ -256,7 +256,7 @@ def _ensure_fd_no_transport(self, fd):
def _add_reader(self, fd, callback, *args):
self._check_closed()
handle = events.Handle(callback, args, self)
handle = events.Handle(callback, args, self, None)
try:
key = self._selector.get_key(fd)
except KeyError:
@ -292,7 +292,7 @@ def _remove_reader(self, fd):
def _add_writer(self, fd, callback, *args):
self._check_closed()
handle = events.Handle(callback, args, self)
handle = events.Handle(callback, args, self, None)
try:
key = self._selector.get_key(fd)
except KeyError:

View file

@ -10,6 +10,7 @@
)
import concurrent.futures
import contextvars
import functools
import inspect
import types
@ -96,8 +97,9 @@ def __init__(self, coro, *, loop=None):
self._must_cancel = False
self._fut_waiter = None
self._coro = coro
self._context = contextvars.copy_context()
self._loop.call_soon(self._step)
self._loop.call_soon(self._step, context=self._context)
_register_task(self)
def __del__(self):
@ -229,15 +231,18 @@ def _step(self, exc=None):
new_exc = RuntimeError(
f'Task {self!r} got Future '
f'{result!r} attached to a different loop')
self._loop.call_soon(self._step, new_exc)
self._loop.call_soon(
self._step, new_exc, context=self._context)
elif blocking:
if result is self:
new_exc = RuntimeError(
f'Task cannot await on itself: {self!r}')
self._loop.call_soon(self._step, new_exc)
self._loop.call_soon(
self._step, new_exc, context=self._context)
else:
result._asyncio_future_blocking = False
result.add_done_callback(self._wakeup)
result.add_done_callback(
self._wakeup, context=self._context)
self._fut_waiter = result
if self._must_cancel:
if self._fut_waiter.cancel():
@ -246,21 +251,24 @@ def _step(self, exc=None):
new_exc = RuntimeError(
f'yield was used instead of yield from '
f'in task {self!r} with {result!r}')
self._loop.call_soon(self._step, new_exc)
self._loop.call_soon(
self._step, new_exc, context=self._context)
elif result is None:
# Bare yield relinquishes control for one event loop iteration.
self._loop.call_soon(self._step)
self._loop.call_soon(self._step, context=self._context)
elif inspect.isgenerator(result):
# Yielding a generator is just wrong.
new_exc = RuntimeError(
f'yield was used instead of yield from for '
f'generator in task {self!r} with {result}')
self._loop.call_soon(self._step, new_exc)
self._loop.call_soon(
self._step, new_exc, context=self._context)
else:
# Yielding something else is an error.
new_exc = RuntimeError(f'Task got bad yield: {result!r}')
self._loop.call_soon(self._step, new_exc)
self._loop.call_soon(
self._step, new_exc, context=self._context)
finally:
_leave_task(self._loop, self)
self = None # Needed to break cycles when an exception occurs.

View file

@ -92,7 +92,7 @@ def add_signal_handler(self, sig, callback, *args):
except (ValueError, OSError) as exc:
raise RuntimeError(str(exc))
handle = events.Handle(callback, args, self)
handle = events.Handle(callback, args, self, None)
self._signal_handlers[sig] = handle
try:

4
Lib/contextvars.py Normal file
View file

@ -0,0 +1,4 @@
from _contextvars import Context, ContextVar, Token, copy_context
__all__ = ('Context', 'ContextVar', 'Token', 'copy_context')

View file

@ -192,14 +192,14 @@ def test_close(self):
self.assertRaises(RuntimeError, self.loop.run_until_complete, f)
def test__add_callback_handle(self):
h = asyncio.Handle(lambda: False, (), self.loop)
h = asyncio.Handle(lambda: False, (), self.loop, None)
self.loop._add_callback(h)
self.assertFalse(self.loop._scheduled)
self.assertIn(h, self.loop._ready)
def test__add_callback_cancelled_handle(self):
h = asyncio.Handle(lambda: False, (), self.loop)
h = asyncio.Handle(lambda: False, (), self.loop, None)
h.cancel()
self.loop._add_callback(h)
@ -333,9 +333,9 @@ def test_thread(loop, debug, create_loop=False):
def test__run_once(self):
h1 = asyncio.TimerHandle(time.monotonic() + 5.0, lambda: True, (),
self.loop)
self.loop, None)
h2 = asyncio.TimerHandle(time.monotonic() + 10.0, lambda: True, (),
self.loop)
self.loop, None)
h1.cancel()
@ -390,7 +390,7 @@ def cb(loop):
handle = loop.call_soon(lambda: True)
h = asyncio.TimerHandle(time.monotonic() - 1, cb, (self.loop,),
self.loop)
self.loop, None)
self.loop._process_events = mock.Mock()
self.loop._scheduled.append(h)

View file

@ -565,16 +565,22 @@ def __del__(self):
@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
class CFutureTests(BaseFutureTests, test_utils.TestCase):
cls = futures._CFuture
try:
cls = futures._CFuture
except AttributeError:
cls = None
@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
class CSubFutureTests(BaseFutureTests, test_utils.TestCase):
class CSubFuture(futures._CFuture):
pass
try:
class CSubFuture(futures._CFuture):
pass
cls = CSubFuture
cls = CSubFuture
except AttributeError:
cls = None
class PyFutureTests(BaseFutureTests, test_utils.TestCase):

View file

@ -2,10 +2,11 @@
import collections
import contextlib
import contextvars
import functools
import gc
import io
import os
import random
import re
import sys
import types
@ -1377,9 +1378,9 @@ def __init__(self, *args, **kwds):
self.cb_added = False
super().__init__(*args, **kwds)
def add_done_callback(self, fn):
def add_done_callback(self, *args, **kwargs):
self.cb_added = True
super().add_done_callback(fn)
super().add_done_callback(*args, **kwargs)
fut = Fut(loop=self.loop)
result = None
@ -2091,7 +2092,7 @@ def main():
@mock.patch('asyncio.base_events.logger')
def test_error_in_call_soon(self, m_log):
def call_soon(callback, *args):
def call_soon(callback, *args, **kwargs):
raise ValueError
self.loop.call_soon = call_soon
@ -2176,6 +2177,91 @@ async def coro():
self.loop.run_until_complete(coro())
def test_context_1(self):
cvar = contextvars.ContextVar('cvar', default='nope')
async def sub():
await asyncio.sleep(0.01, loop=loop)
self.assertEqual(cvar.get(), 'nope')
cvar.set('something else')
async def main():
self.assertEqual(cvar.get(), 'nope')
subtask = self.new_task(loop, sub())
cvar.set('yes')
self.assertEqual(cvar.get(), 'yes')
await subtask
self.assertEqual(cvar.get(), 'yes')
loop = asyncio.new_event_loop()
try:
task = self.new_task(loop, main())
loop.run_until_complete(task)
finally:
loop.close()
def test_context_2(self):
cvar = contextvars.ContextVar('cvar', default='nope')
async def main():
def fut_on_done(fut):
# This change must not pollute the context
# of the "main()" task.
cvar.set('something else')
self.assertEqual(cvar.get(), 'nope')
for j in range(2):
fut = self.new_future(loop)
fut.add_done_callback(fut_on_done)
cvar.set(f'yes{j}')
loop.call_soon(fut.set_result, None)
await fut
self.assertEqual(cvar.get(), f'yes{j}')
for i in range(3):
# Test that task passed its context to add_done_callback:
cvar.set(f'yes{i}-{j}')
await asyncio.sleep(0.001, loop=loop)
self.assertEqual(cvar.get(), f'yes{i}-{j}')
loop = asyncio.new_event_loop()
try:
task = self.new_task(loop, main())
loop.run_until_complete(task)
finally:
loop.close()
self.assertEqual(cvar.get(), 'nope')
def test_context_3(self):
# Run 100 Tasks in parallel, each modifying cvar.
cvar = contextvars.ContextVar('cvar', default=-1)
async def sub(num):
for i in range(10):
cvar.set(num + i)
await asyncio.sleep(
random.uniform(0.001, 0.05), loop=loop)
self.assertEqual(cvar.get(), num + i)
async def main():
tasks = []
for i in range(100):
task = loop.create_task(sub(random.randint(0, 10)))
tasks.append(task)
await asyncio.gather(*tasks, loop=loop)
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.close()
self.assertEqual(cvar.get(), -1)
def add_subclass_tests(cls):
BaseTask = cls.Task
@ -2193,9 +2279,9 @@ def _schedule_callbacks(self):
self.calls['_schedule_callbacks'] += 1
return super()._schedule_callbacks()
def add_done_callback(self, *args):
def add_done_callback(self, *args, **kwargs):
self.calls['add_done_callback'] += 1
return super().add_done_callback(*args)
return super().add_done_callback(*args, **kwargs)
class Task(CommonFuture, BaseTask):
def _step(self, *args):
@ -2486,10 +2572,13 @@ class PyIntrospectionTests(unittest.TestCase, BaseTaskIntrospectionTests):
@unittest.skipUnless(hasattr(tasks, '_c_register_task'),
'requires the C _asyncio module')
class CIntrospectionTests(unittest.TestCase, BaseTaskIntrospectionTests):
_register_task = staticmethod(tasks._c_register_task)
_unregister_task = staticmethod(tasks._c_unregister_task)
_enter_task = staticmethod(tasks._c_enter_task)
_leave_task = staticmethod(tasks._c_leave_task)
if hasattr(tasks, '_c_register_task'):
_register_task = staticmethod(tasks._c_register_task)
_unregister_task = staticmethod(tasks._c_unregister_task)
_enter_task = staticmethod(tasks._c_enter_task)
_leave_task = staticmethod(tasks._c_leave_task)
else:
_register_task = _unregister_task = _enter_task = _leave_task = None
class BaseCurrentLoopTests:

View file

@ -365,7 +365,7 @@ def close(self):
raise AssertionError("Time generator is not finished")
def _add_reader(self, fd, callback, *args):
self.readers[fd] = events.Handle(callback, args, self)
self.readers[fd] = events.Handle(callback, args, self, None)
def _remove_reader(self, fd):
self.remove_reader_count[fd] += 1
@ -391,7 +391,7 @@ def assert_no_reader(self, fd):
raise AssertionError(f'fd {fd} is registered')
def _add_writer(self, fd, callback, *args):
self.writers[fd] = events.Handle(callback, args, self)
self.writers[fd] = events.Handle(callback, args, self, None)
def _remove_writer(self, fd):
self.remove_writer_count[fd] += 1
@ -457,9 +457,9 @@ def _run_once(self):
self.advance_time(advance)
self._timers = []
def call_at(self, when, callback, *args):
def call_at(self, when, callback, *args, context=None):
self._timers.append(when)
return super().call_at(when, callback, *args)
return super().call_at(when, callback, *args, context=context)
def _process_events(self, event_list):
return

1064
Lib/test/test_context.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -354,6 +354,8 @@ PYTHON_OBJS= \
Python/pylifecycle.o \
Python/pymath.o \
Python/pystate.o \
Python/context.o \
Python/hamt.o \
Python/pythonrun.o \
Python/pytime.o \
Python/bootstrap_hash.o \
@ -996,6 +998,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/pymem.h \
$(srcdir)/Include/pyport.h \
$(srcdir)/Include/pystate.h \
$(srcdir)/Include/context.h \
$(srcdir)/Include/pystrcmp.h \
$(srcdir)/Include/pystrtod.h \
$(srcdir)/Include/pystrhex.h \
@ -1023,6 +1026,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/mem.h \
$(srcdir)/Include/internal/pygetopt.h \
$(srcdir)/Include/internal/pystate.h \
$(srcdir)/Include/internal/context.h \
$(srcdir)/Include/internal/warnings.h \
$(DTRACE_HEADERS)

View file

@ -0,0 +1 @@
Implement PEP 567

View file

@ -176,6 +176,7 @@ _symtable symtablemodule.c
#array arraymodule.c # array objects
#cmath cmathmodule.c _math.c # -lm # complex math library functions
#math mathmodule.c _math.c # -lm # math library functions, e.g. sin()
#_contextvars _contextvarsmodule.c # Context Variables
#_struct _struct.c # binary structure packing/unpacking
#_weakref _weakref.c # basic weak reference support
#_testcapi _testcapimodule.c # Python C API test module

View file

@ -36,6 +36,7 @@ static PyObject *asyncio_task_print_stack_func;
static PyObject *asyncio_task_repr_info_func;
static PyObject *asyncio_InvalidStateError;
static PyObject *asyncio_CancelledError;
static PyObject *context_kwname;
/* WeakSet containing all alive tasks. */
@ -59,6 +60,7 @@ typedef enum {
PyObject_HEAD \
PyObject *prefix##_loop; \
PyObject *prefix##_callback0; \
PyContext *prefix##_context0; \
PyObject *prefix##_callbacks; \
PyObject *prefix##_exception; \
PyObject *prefix##_result; \
@ -77,6 +79,7 @@ typedef struct {
FutureObj_HEAD(task)
PyObject *task_fut_waiter;
PyObject *task_coro;
PyContext *task_context;
int task_must_cancel;
int task_log_destroy_pending;
} TaskObj;
@ -336,11 +339,38 @@ get_event_loop(void)
static int
call_soon(PyObject *loop, PyObject *func, PyObject *arg)
call_soon(PyObject *loop, PyObject *func, PyObject *arg, PyContext *ctx)
{
PyObject *handle;
handle = _PyObject_CallMethodIdObjArgs(
loop, &PyId_call_soon, func, arg, NULL);
PyObject *stack[3];
Py_ssize_t nargs;
if (ctx == NULL) {
handle = _PyObject_CallMethodIdObjArgs(
loop, &PyId_call_soon, func, arg, NULL);
}
else {
/* Use FASTCALL to pass a keyword-only argument to call_soon */
PyObject *callable = _PyObject_GetAttrId(loop, &PyId_call_soon);
if (callable == NULL) {
return -1;
}
/* All refs in 'stack' are borrowed. */
nargs = 1;
stack[0] = func;
if (arg != NULL) {
stack[1] = arg;
nargs++;
}
stack[nargs] = (PyObject *)ctx;
handle = _PyObject_FastCallKeywords(
callable, stack, nargs, context_kwname);
Py_DECREF(callable);
}
if (handle == NULL) {
return -1;
}
@ -387,8 +417,11 @@ future_schedule_callbacks(FutureObj *fut)
/* There's a 1st callback */
int ret = call_soon(
fut->fut_loop, fut->fut_callback0, (PyObject *)fut);
fut->fut_loop, fut->fut_callback0,
(PyObject *)fut, fut->fut_context0);
Py_CLEAR(fut->fut_callback0);
Py_CLEAR(fut->fut_context0);
if (ret) {
/* If an error occurs in pure-Python implementation,
all callbacks are cleared. */
@ -413,9 +446,11 @@ future_schedule_callbacks(FutureObj *fut)
}
for (i = 0; i < len; i++) {
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, i);
PyObject *cb_tup = PyList_GET_ITEM(fut->fut_callbacks, i);
PyObject *cb = PyTuple_GET_ITEM(cb_tup, 0);
PyObject *ctx = PyTuple_GET_ITEM(cb_tup, 1);
if (call_soon(fut->fut_loop, cb, (PyObject *)fut)) {
if (call_soon(fut->fut_loop, cb, (PyObject *)fut, (PyContext *)ctx)) {
/* If an error occurs in pure-Python implementation,
all callbacks are cleared. */
Py_CLEAR(fut->fut_callbacks);
@ -462,6 +497,7 @@ future_init(FutureObj *fut, PyObject *loop)
}
fut->fut_callback0 = NULL;
fut->fut_context0 = NULL;
fut->fut_callbacks = NULL;
return 0;
@ -566,7 +602,7 @@ future_get_result(FutureObj *fut, PyObject **result)
}
static PyObject *
future_add_done_callback(FutureObj *fut, PyObject *arg)
future_add_done_callback(FutureObj *fut, PyObject *arg, PyContext *ctx)
{
if (!future_is_alive(fut)) {
PyErr_SetString(PyExc_RuntimeError, "uninitialized Future object");
@ -576,7 +612,7 @@ future_add_done_callback(FutureObj *fut, PyObject *arg)
if (fut->fut_state != STATE_PENDING) {
/* The future is done/cancelled, so schedule the callback
right away. */
if (call_soon(fut->fut_loop, arg, (PyObject*) fut)) {
if (call_soon(fut->fut_loop, arg, (PyObject*) fut, ctx)) {
return NULL;
}
}
@ -602,24 +638,38 @@ future_add_done_callback(FutureObj *fut, PyObject *arg)
with a new list and add the new callback to it.
*/
if (fut->fut_callbacks != NULL) {
int err = PyList_Append(fut->fut_callbacks, arg);
if (err != 0) {
return NULL;
}
}
else if (fut->fut_callback0 == NULL) {
if (fut->fut_callbacks == NULL && fut->fut_callback0 == NULL) {
Py_INCREF(arg);
fut->fut_callback0 = arg;
Py_INCREF(ctx);
fut->fut_context0 = ctx;
}
else {
fut->fut_callbacks = PyList_New(1);
if (fut->fut_callbacks == NULL) {
PyObject *tup = PyTuple_New(2);
if (tup == NULL) {
return NULL;
}
Py_INCREF(arg);
PyList_SET_ITEM(fut->fut_callbacks, 0, arg);
PyTuple_SET_ITEM(tup, 0, arg);
Py_INCREF(ctx);
PyTuple_SET_ITEM(tup, 1, (PyObject *)ctx);
if (fut->fut_callbacks != NULL) {
int err = PyList_Append(fut->fut_callbacks, tup);
if (err) {
Py_DECREF(tup);
return NULL;
}
Py_DECREF(tup);
}
else {
fut->fut_callbacks = PyList_New(1);
if (fut->fut_callbacks == NULL) {
return NULL;
}
PyList_SET_ITEM(fut->fut_callbacks, 0, tup); /* borrow */
}
}
}
@ -676,6 +726,7 @@ FutureObj_clear(FutureObj *fut)
{
Py_CLEAR(fut->fut_loop);
Py_CLEAR(fut->fut_callback0);
Py_CLEAR(fut->fut_context0);
Py_CLEAR(fut->fut_callbacks);
Py_CLEAR(fut->fut_result);
Py_CLEAR(fut->fut_exception);
@ -689,6 +740,7 @@ FutureObj_traverse(FutureObj *fut, visitproc visit, void *arg)
{
Py_VISIT(fut->fut_loop);
Py_VISIT(fut->fut_callback0);
Py_VISIT(fut->fut_context0);
Py_VISIT(fut->fut_callbacks);
Py_VISIT(fut->fut_result);
Py_VISIT(fut->fut_exception);
@ -821,6 +873,8 @@ _asyncio.Future.add_done_callback
fn: object
/
*
context: object = NULL
Add a callback to be run when the future becomes done.
@ -830,10 +884,21 @@ scheduled with call_soon.
[clinic start generated code]*/
static PyObject *
_asyncio_Future_add_done_callback(FutureObj *self, PyObject *fn)
/*[clinic end generated code: output=819e09629b2ec2b5 input=8f818b39990b027d]*/
_asyncio_Future_add_done_callback_impl(FutureObj *self, PyObject *fn,
PyObject *context)
/*[clinic end generated code: output=7ce635bbc9554c1e input=15ab0693a96e9533]*/
{
return future_add_done_callback(self, fn);
if (context == NULL) {
context = (PyObject *)PyContext_CopyCurrent();
if (context == NULL) {
return NULL;
}
PyObject *res = future_add_done_callback(
self, fn, (PyContext *)context);
Py_DECREF(context);
return res;
}
return future_add_done_callback(self, fn, (PyContext *)context);
}
/*[clinic input]
@ -865,6 +930,7 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
if (cmp == 1) {
/* callback0 == fn */
Py_CLEAR(self->fut_callback0);
Py_CLEAR(self->fut_context0);
cleared_callback0 = 1;
}
}
@ -880,8 +946,9 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
}
if (len == 1) {
PyObject *cb_tup = PyList_GET_ITEM(self->fut_callbacks, 0);
int cmp = PyObject_RichCompareBool(
fn, PyList_GET_ITEM(self->fut_callbacks, 0), Py_EQ);
fn, PyTuple_GET_ITEM(cb_tup, 0), Py_EQ);
if (cmp == -1) {
return NULL;
}
@ -903,7 +970,7 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
int ret;
PyObject *item = PyList_GET_ITEM(self->fut_callbacks, i);
Py_INCREF(item);
ret = PyObject_RichCompareBool(fn, item, Py_EQ);
ret = PyObject_RichCompareBool(fn, PyTuple_GET_ITEM(item, 0), Py_EQ);
if (ret == 0) {
if (j < len) {
PyList_SET_ITEM(newlist, j, item);
@ -1081,47 +1148,49 @@ static PyObject *
FutureObj_get_callbacks(FutureObj *fut)
{
Py_ssize_t i;
Py_ssize_t len;
PyObject *new_list;
ENSURE_FUTURE_ALIVE(fut)
if (fut->fut_callbacks == NULL) {
if (fut->fut_callback0 == NULL) {
if (fut->fut_callback0 == NULL) {
if (fut->fut_callbacks == NULL) {
Py_RETURN_NONE;
}
else {
new_list = PyList_New(1);
if (new_list == NULL) {
return NULL;
}
Py_INCREF(fut->fut_callback0);
PyList_SET_ITEM(new_list, 0, fut->fut_callback0);
return new_list;
}
}
assert(fut->fut_callbacks != NULL);
if (fut->fut_callback0 == NULL) {
Py_INCREF(fut->fut_callbacks);
return fut->fut_callbacks;
}
assert(fut->fut_callback0 != NULL);
Py_ssize_t len = 1;
if (fut->fut_callbacks != NULL) {
len += PyList_GET_SIZE(fut->fut_callbacks);
}
len = PyList_GET_SIZE(fut->fut_callbacks);
new_list = PyList_New(len + 1);
PyObject *new_list = PyList_New(len);
if (new_list == NULL) {
return NULL;
}
PyObject *tup0 = PyTuple_New(2);
if (tup0 == NULL) {
Py_DECREF(new_list);
return NULL;
}
Py_INCREF(fut->fut_callback0);
PyList_SET_ITEM(new_list, 0, fut->fut_callback0);
for (i = 0; i < len; i++) {
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, i);
Py_INCREF(cb);
PyList_SET_ITEM(new_list, i + 1, cb);
PyTuple_SET_ITEM(tup0, 0, fut->fut_callback0);
assert(fut->fut_context0 != NULL);
Py_INCREF(fut->fut_context0);
PyTuple_SET_ITEM(tup0, 1, (PyObject *)fut->fut_context0);
PyList_SET_ITEM(new_list, 0, tup0);
if (fut->fut_callbacks != NULL) {
for (i = 0; i < PyList_GET_SIZE(fut->fut_callbacks); i++) {
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, i);
Py_INCREF(cb);
PyList_SET_ITEM(new_list, i + 1, cb);
}
}
return new_list;
@ -1912,6 +1981,11 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop)
return -1;
}
self->task_context = PyContext_CopyCurrent();
if (self->task_context == NULL) {
return -1;
}
self->task_fut_waiter = NULL;
self->task_must_cancel = 0;
self->task_log_destroy_pending = 1;
@ -1928,6 +2002,7 @@ static int
TaskObj_clear(TaskObj *task)
{
(void)FutureObj_clear((FutureObj*) task);
Py_CLEAR(task->task_context);
Py_CLEAR(task->task_coro);
Py_CLEAR(task->task_fut_waiter);
return 0;
@ -1936,6 +2011,7 @@ TaskObj_clear(TaskObj *task)
static int
TaskObj_traverse(TaskObj *task, visitproc visit, void *arg)
{
Py_VISIT(task->task_context);
Py_VISIT(task->task_coro);
Py_VISIT(task->task_fut_waiter);
(void)FutureObj_traverse((FutureObj*) task, visit, arg);
@ -2451,7 +2527,7 @@ task_call_step_soon(TaskObj *task, PyObject *arg)
return -1;
}
int ret = call_soon(task->task_loop, cb, NULL);
int ret = call_soon(task->task_loop, cb, NULL, task->task_context);
Py_DECREF(cb);
return ret;
}
@ -2650,7 +2726,8 @@ task_step_impl(TaskObj *task, PyObject *exc)
if (wrapper == NULL) {
goto fail;
}
res = future_add_done_callback((FutureObj*)result, wrapper);
res = future_add_done_callback(
(FutureObj*)result, wrapper, task->task_context);
Py_DECREF(wrapper);
if (res == NULL) {
goto fail;
@ -2724,14 +2801,23 @@ task_step_impl(TaskObj *task, PyObject *exc)
goto fail;
}
/* result.add_done_callback(task._wakeup) */
wrapper = TaskWakeupMethWrapper_new(task);
if (wrapper == NULL) {
goto fail;
}
res = _PyObject_CallMethodIdObjArgs(result,
&PyId_add_done_callback,
wrapper, NULL);
/* result.add_done_callback(task._wakeup) */
PyObject *add_cb = _PyObject_GetAttrId(
result, &PyId_add_done_callback);
if (add_cb == NULL) {
goto fail;
}
PyObject *stack[2];
stack[0] = wrapper;
stack[1] = (PyObject *)task->task_context;
res = _PyObject_FastCallKeywords(
add_cb, stack, 1, context_kwname);
Py_DECREF(add_cb);
Py_DECREF(wrapper);
if (res == NULL) {
goto fail;
@ -3141,6 +3227,8 @@ module_free(void *m)
Py_CLEAR(current_tasks);
Py_CLEAR(iscoroutine_typecache);
Py_CLEAR(context_kwname);
module_free_freelists();
}
@ -3164,6 +3252,17 @@ module_init(void)
goto fail;
}
context_kwname = PyTuple_New(1);
if (context_kwname == NULL) {
goto fail;
}
PyObject *context_str = PyUnicode_FromString("context");
if (context_str == NULL) {
goto fail;
}
PyTuple_SET_ITEM(context_kwname, 0, context_str);
#define WITH_MOD(NAME) \
Py_CLEAR(module); \
module = PyImport_ImportModule(NAME); \

View file

@ -0,0 +1,75 @@
#include "Python.h"
#include "clinic/_contextvarsmodule.c.h"
/*[clinic input]
module _contextvars
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=a0955718c8b8cea6]*/
/*[clinic input]
_contextvars.copy_context
[clinic start generated code]*/
static PyObject *
_contextvars_copy_context_impl(PyObject *module)
/*[clinic end generated code: output=1fcd5da7225c4fa9 input=89bb9ae485888440]*/
{
return (PyObject *)PyContext_CopyCurrent();
}
PyDoc_STRVAR(module_doc, "Context Variables");
static PyMethodDef _contextvars_methods[] = {
_CONTEXTVARS_COPY_CONTEXT_METHODDEF
{NULL, NULL}
};
static struct PyModuleDef _contextvarsmodule = {
PyModuleDef_HEAD_INIT, /* m_base */
"_contextvars", /* m_name */
module_doc, /* m_doc */
-1, /* m_size */
_contextvars_methods, /* m_methods */
NULL, /* m_slots */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
PyMODINIT_FUNC
PyInit__contextvars(void)
{
PyObject *m = PyModule_Create(&_contextvarsmodule);
if (m == NULL) {
return NULL;
}
Py_INCREF(&PyContext_Type);
if (PyModule_AddObject(m, "Context",
(PyObject *)&PyContext_Type) < 0)
{
Py_DECREF(&PyContext_Type);
return NULL;
}
Py_INCREF(&PyContextVar_Type);
if (PyModule_AddObject(m, "ContextVar",
(PyObject *)&PyContextVar_Type) < 0)
{
Py_DECREF(&PyContextVar_Type);
return NULL;
}
Py_INCREF(&PyContextToken_Type);
if (PyModule_AddObject(m, "Token",
(PyObject *)&PyContextToken_Type) < 0)
{
Py_DECREF(&PyContextToken_Type);
return NULL;
}
return m;
}

View file

@ -4438,6 +4438,13 @@ test_pythread_tss_key_state(PyObject *self, PyObject *args)
}
static PyObject*
new_hamt(PyObject *self, PyObject *args)
{
return _PyContext_NewHamtForTests();
}
static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS},
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
@ -4655,6 +4662,7 @@ static PyMethodDef TestMethods[] = {
{"get_mapping_values", get_mapping_values, METH_O},
{"get_mapping_items", get_mapping_items, METH_O},
{"test_pythread_tss_key_state", test_pythread_tss_key_state, METH_VARARGS},
{"hamt", new_hamt, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

View file

@ -110,7 +110,7 @@ PyDoc_STRVAR(_asyncio_Future_set_exception__doc__,
{"set_exception", (PyCFunction)_asyncio_Future_set_exception, METH_O, _asyncio_Future_set_exception__doc__},
PyDoc_STRVAR(_asyncio_Future_add_done_callback__doc__,
"add_done_callback($self, fn, /)\n"
"add_done_callback($self, fn, /, *, context=None)\n"
"--\n"
"\n"
"Add a callback to be run when the future becomes done.\n"
@ -120,7 +120,30 @@ PyDoc_STRVAR(_asyncio_Future_add_done_callback__doc__,
"scheduled with call_soon.");
#define _ASYNCIO_FUTURE_ADD_DONE_CALLBACK_METHODDEF \
{"add_done_callback", (PyCFunction)_asyncio_Future_add_done_callback, METH_O, _asyncio_Future_add_done_callback__doc__},
{"add_done_callback", (PyCFunction)_asyncio_Future_add_done_callback, METH_FASTCALL|METH_KEYWORDS, _asyncio_Future_add_done_callback__doc__},
static PyObject *
_asyncio_Future_add_done_callback_impl(FutureObj *self, PyObject *fn,
PyObject *context);
static PyObject *
_asyncio_Future_add_done_callback(FutureObj *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"", "context", NULL};
static _PyArg_Parser _parser = {"O|$O:add_done_callback", _keywords, 0};
PyObject *fn;
PyObject *context = NULL;
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
&fn, &context)) {
goto exit;
}
return_value = _asyncio_Future_add_done_callback_impl(self, fn, context);
exit:
return return_value;
}
PyDoc_STRVAR(_asyncio_Future_remove_done_callback__doc__,
"remove_done_callback($self, fn, /)\n"
@ -763,4 +786,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
exit:
return return_value;
}
/*[clinic end generated code: output=616e814431893dcc input=a9049054013a1b77]*/
/*[clinic end generated code: output=bcbaf1b2480f4aa9 input=a9049054013a1b77]*/

View file

@ -0,0 +1,21 @@
/*[clinic input]
preserve
[clinic start generated code]*/
PyDoc_STRVAR(_contextvars_copy_context__doc__,
"copy_context($module, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_COPY_CONTEXT_METHODDEF \
{"copy_context", (PyCFunction)_contextvars_copy_context, METH_NOARGS, _contextvars_copy_context__doc__},
static PyObject *
_contextvars_copy_context_impl(PyObject *module);
static PyObject *
_contextvars_copy_context(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return _contextvars_copy_context_impl(module);
}
/*[clinic end generated code: output=26e07024451baf52 input=a9049054013a1b77]*/

View file

@ -24,6 +24,7 @@
*/
#include "Python.h"
#include "internal/context.h"
#include "internal/mem.h"
#include "internal/pystate.h"
#include "frameobject.h" /* for PyFrame_ClearFreeList */
@ -790,6 +791,7 @@ clear_freelists(void)
(void)PyDict_ClearFreeList();
(void)PySet_ClearFreeList();
(void)PyAsyncGen_ClearFreeLists();
(void)PyContext_ClearFreeList();
}
/* This is the main function. Read this to understand how the

View file

@ -3,6 +3,7 @@
#include "Python.h"
#include "internal/pystate.h"
#include "internal/context.h"
#include "frameobject.h"
#ifdef __cplusplus

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|Win32">
<Configuration>PGInstrument</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|x64">
<Configuration>PGInstrument</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|Win32">
<Configuration>PGUpdate</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|x64">
<Configuration>PGUpdate</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{B8BF1D81-09DC-42D4-B406-4F868B33A89E}</ProjectGuid>
<RootNamespace>_contextvars</RootNamespace>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="python.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<TargetExt>.pyd</TargetExt>
</PropertyGroup>
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="pyproject.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="..\Modules\_contextvarsmodule.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="pythoncore.vcxproj">
<Project>{cf7ac3d1-e2df-41d2-bea6-1e2556cdea26}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
</ItemGroup>
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{7CBD8910-233D-4E9A-9164-9BA66C1F0E6D}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\Modules\_contextvarsmodule.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -121,4 +121,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -49,7 +49,7 @@
<!-- pyshellext.dll -->
<Projects Include="pyshellext.vcxproj" />
<!-- Extension modules -->
<ExtensionModules Include="_asyncio;_ctypes;_decimal;_distutils_findvs;_elementtree;_msi;_multiprocessing;_overlapped;pyexpat;_queue;select;unicodedata;winsound" />
<ExtensionModules Include="_asyncio;_contextvars;_ctypes;_decimal;_distutils_findvs;_elementtree;_msi;_multiprocessing;_overlapped;pyexpat;_queue;select;unicodedata;winsound" />
<!-- Extension modules that require external sources -->
<ExternalModules Include="_bz2;_lzma;_sqlite3" />
<!-- _ssl will build _socket as well, which may cause conflicts in parallel builds -->

View file

@ -94,6 +94,7 @@
<ClInclude Include="..\Include\codecs.h" />
<ClInclude Include="..\Include\compile.h" />
<ClInclude Include="..\Include\complexobject.h" />
<ClInclude Include="..\Include\context.h" />
<ClInclude Include="..\Include\datetime.h" />
<ClInclude Include="..\Include\descrobject.h" />
<ClInclude Include="..\Include\dictobject.h" />
@ -112,7 +113,9 @@
<ClInclude Include="..\Include\import.h" />
<ClInclude Include="..\Include\internal\ceval.h" />
<ClInclude Include="..\Include\internal\condvar.h" />
<ClInclude Include="..\Include\internal\context.h" />
<ClInclude Include="..\Include\internal\gil.h" />
<ClInclude Include="..\Include\internal\hamt.h" />
<ClInclude Include="..\Include\internal\mem.h" />
<ClInclude Include="..\Include\internal\pystate.h" />
<ClInclude Include="..\Include\internal\warnings.h" />
@ -232,6 +235,7 @@
<ClCompile Include="..\Modules\_blake2\blake2s_impl.c" />
<ClCompile Include="..\Modules\_codecsmodule.c" />
<ClCompile Include="..\Modules\_collectionsmodule.c" />
<ClCompile Include="..\Modules\_contextvarsmodule.c" />
<ClCompile Include="..\Modules\_csv.c" />
<ClCompile Include="..\Modules\_functoolsmodule.c" />
<ClCompile Include="..\Modules\_heapqmodule.c" />
@ -359,6 +363,7 @@
<ClCompile Include="..\Python\ceval.c" />
<ClCompile Include="..\Python\codecs.c" />
<ClCompile Include="..\Python\compile.c" />
<ClCompile Include="..\Python\context.c" />
<ClCompile Include="..\Python\dynamic_annotations.c" />
<ClCompile Include="..\Python\dynload_win.c" />
<ClCompile Include="..\Python\errors.c" />
@ -373,6 +378,7 @@
<ClCompile Include="..\Python\getplatform.c" />
<ClCompile Include="..\Python\getversion.c" />
<ClCompile Include="..\Python\graminit.c" />
<ClCompile Include="..\Python\hamt.c" />
<ClCompile Include="..\Python\import.c" />
<ClCompile Include="..\Python\importdl.c" />
<ClCompile Include="..\Python\marshal.c" />

View file

@ -81,6 +81,9 @@
<ClInclude Include="..\Include\complexobject.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\context.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\datetime.h">
<Filter>Include</Filter>
</ClInclude>
@ -135,9 +138,15 @@
<ClInclude Include="..\Include\internal\condvar.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\internal\context.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\internal\gil.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\internal\hamt.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\internal\mem.h">
<Filter>Include</Filter>
</ClInclude>
@ -842,6 +851,9 @@
<ClCompile Include="..\Python\compile.c">
<Filter>Python</Filter>
</ClCompile>
<ClCompile Include="..\Python\context.h">
<Filter>Python</Filter>
</ClCompile>
<ClCompile Include="..\Python\dynamic_annotations.c">
<Filter>Python</Filter>
</ClCompile>
@ -884,6 +896,9 @@
<ClCompile Include="..\Python\graminit.c">
<Filter>Python</Filter>
</ClCompile>
<ClCompile Include="..\Python\hamt.h">
<Filter>Python</Filter>
</ClCompile>
<ClCompile Include="..\Python\import.c">
<Filter>Python</Filter>
</ClCompile>
@ -998,6 +1013,9 @@
<ClCompile Include="..\Modules\_asynciomodule.c">
<Filter>Modules</Filter>
</ClCompile>
<ClCompile Include="..\Modules\_contextvarsmodule.c">
<Filter>Modules</Filter>
</ClCompile>
<ClCompile Include="$(zlibDir)\adler32.c">
<Filter>Modules\zlib</Filter>
</ClCompile>

146
Python/clinic/context.c.h Normal file
View file

@ -0,0 +1,146 @@
/*[clinic input]
preserve
[clinic start generated code]*/
PyDoc_STRVAR(_contextvars_Context_get__doc__,
"get($self, key, default=None, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXT_GET_METHODDEF \
{"get", (PyCFunction)_contextvars_Context_get, METH_FASTCALL, _contextvars_Context_get__doc__},
static PyObject *
_contextvars_Context_get_impl(PyContext *self, PyObject *key,
PyObject *default_value);
static PyObject *
_contextvars_Context_get(PyContext *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
PyObject *key;
PyObject *default_value = Py_None;
if (!_PyArg_UnpackStack(args, nargs, "get",
1, 2,
&key, &default_value)) {
goto exit;
}
return_value = _contextvars_Context_get_impl(self, key, default_value);
exit:
return return_value;
}
PyDoc_STRVAR(_contextvars_Context_items__doc__,
"items($self, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXT_ITEMS_METHODDEF \
{"items", (PyCFunction)_contextvars_Context_items, METH_NOARGS, _contextvars_Context_items__doc__},
static PyObject *
_contextvars_Context_items_impl(PyContext *self);
static PyObject *
_contextvars_Context_items(PyContext *self, PyObject *Py_UNUSED(ignored))
{
return _contextvars_Context_items_impl(self);
}
PyDoc_STRVAR(_contextvars_Context_keys__doc__,
"keys($self, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXT_KEYS_METHODDEF \
{"keys", (PyCFunction)_contextvars_Context_keys, METH_NOARGS, _contextvars_Context_keys__doc__},
static PyObject *
_contextvars_Context_keys_impl(PyContext *self);
static PyObject *
_contextvars_Context_keys(PyContext *self, PyObject *Py_UNUSED(ignored))
{
return _contextvars_Context_keys_impl(self);
}
PyDoc_STRVAR(_contextvars_Context_values__doc__,
"values($self, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXT_VALUES_METHODDEF \
{"values", (PyCFunction)_contextvars_Context_values, METH_NOARGS, _contextvars_Context_values__doc__},
static PyObject *
_contextvars_Context_values_impl(PyContext *self);
static PyObject *
_contextvars_Context_values(PyContext *self, PyObject *Py_UNUSED(ignored))
{
return _contextvars_Context_values_impl(self);
}
PyDoc_STRVAR(_contextvars_Context_copy__doc__,
"copy($self, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXT_COPY_METHODDEF \
{"copy", (PyCFunction)_contextvars_Context_copy, METH_NOARGS, _contextvars_Context_copy__doc__},
static PyObject *
_contextvars_Context_copy_impl(PyContext *self);
static PyObject *
_contextvars_Context_copy(PyContext *self, PyObject *Py_UNUSED(ignored))
{
return _contextvars_Context_copy_impl(self);
}
PyDoc_STRVAR(_contextvars_ContextVar_get__doc__,
"get($self, default=None, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXTVAR_GET_METHODDEF \
{"get", (PyCFunction)_contextvars_ContextVar_get, METH_FASTCALL, _contextvars_ContextVar_get__doc__},
static PyObject *
_contextvars_ContextVar_get_impl(PyContextVar *self, PyObject *default_value);
static PyObject *
_contextvars_ContextVar_get(PyContextVar *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
PyObject *default_value = NULL;
if (!_PyArg_UnpackStack(args, nargs, "get",
0, 1,
&default_value)) {
goto exit;
}
return_value = _contextvars_ContextVar_get_impl(self, default_value);
exit:
return return_value;
}
PyDoc_STRVAR(_contextvars_ContextVar_set__doc__,
"set($self, value, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXTVAR_SET_METHODDEF \
{"set", (PyCFunction)_contextvars_ContextVar_set, METH_O, _contextvars_ContextVar_set__doc__},
PyDoc_STRVAR(_contextvars_ContextVar_reset__doc__,
"reset($self, token, /)\n"
"--\n"
"\n");
#define _CONTEXTVARS_CONTEXTVAR_RESET_METHODDEF \
{"reset", (PyCFunction)_contextvars_ContextVar_reset, METH_O, _contextvars_ContextVar_reset__doc__},
/*[clinic end generated code: output=d9a675e3a52a14fc input=a9049054013a1b77]*/

1220
Python/context.c Normal file

File diff suppressed because it is too large Load diff

2982
Python/hamt.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,8 @@
#include "Python-ast.h"
#undef Yield /* undefine macro conflicting with winbase.h */
#include "internal/context.h"
#include "internal/hamt.h"
#include "internal/pystate.h"
#include "grammar.h"
#include "node.h"
@ -758,6 +760,9 @@ _Py_InitializeCore(const _PyCoreConfig *core_config)
return _Py_INIT_ERR("can't initialize warnings");
}
if (!_PyContext_Init())
return _Py_INIT_ERR("can't init context");
/* This call sets up builtin and frozen import support */
if (!interp->core_config._disable_importlib) {
err = initimport(interp, sysmod);
@ -1176,6 +1181,7 @@ Py_FinalizeEx(void)
_Py_HashRandomization_Fini();
_PyArg_Fini();
PyAsyncGen_Fini();
_PyContext_Fini();
/* Cleanup Unicode implementation */
_PyUnicode_Fini();

View file

@ -173,6 +173,8 @@ PyInterpreterState_New(void)
}
HEAD_UNLOCK();
interp->tstate_next_unique_id = 0;
return interp;
}
@ -313,6 +315,11 @@ new_threadstate(PyInterpreterState *interp, int init)
tstate->async_gen_firstiter = NULL;
tstate->async_gen_finalizer = NULL;
tstate->context = NULL;
tstate->context_ver = 1;
tstate->id = ++interp->tstate_next_unique_id;
if (init)
_PyThreadState_Init(tstate);
@ -499,6 +506,8 @@ PyThreadState_Clear(PyThreadState *tstate)
Py_CLEAR(tstate->coroutine_wrapper);
Py_CLEAR(tstate->async_gen_firstiter);
Py_CLEAR(tstate->async_gen_finalizer);
Py_CLEAR(tstate->context);
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_msi;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_distutils_findvs ?>
<?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_msi;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_distutils_findvs;_contextvars ?>
<Fragment>
<ComponentGroup Id="lib_extensions">
<?foreach ext in $(var.exts)?>

View file

@ -644,6 +644,9 @@ def detect_modules(self):
# array objects
exts.append( Extension('array', ['arraymodule.c']) )
# Context Variables
exts.append( Extension('_contextvars', ['_contextvarsmodule.c']) )
shared_math = 'Modules/_math.o'
# complex math library functions
exts.append( Extension('cmath', ['cmathmodule.c'],