Skip to content

bpo-39794: Add --without-decimal-contextvar #18702

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions Doc/library/decimal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1475,9 +1475,18 @@ are also included in the pure Python version for compatibility.

.. data:: HAVE_THREADS

The default value is ``True``. If Python is compiled without threads, the
C version automatically disables the expensive thread local context
machinery. In this case, the value is ``False``.
The value is ``True``. Deprecated, because Python now always has threads.

.. deprecated:: 3.9

.. data:: HAVE_CONTEXTVAR

The default value is ``True``. If Python is compiled ``--without-decimal-contextvar``,
the C version uses a thread-local rather than a coroutine-local context and the value
is ``False``. This is slightly faster in some nested context scenarios.

.. versionadded:: 3.9


Rounding modes
--------------
Expand Down
8 changes: 6 additions & 2 deletions Lib/_pydecimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,11 @@
# Limits for the C version for compatibility
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',

# C version: compile time choice that enables the thread local context
'HAVE_THREADS'
# C version: compile time choice that enables the thread local context (deprecated, now always true)
'HAVE_THREADS',

# C version: compile time choice that enables the coroutine local context
'HAVE_CONTEXTVAR'
]

__xname__ = __name__ # sys.modules lookup (--without-threads)
Expand Down Expand Up @@ -172,6 +175,7 @@

# Compatibility with the C version
HAVE_THREADS = True
HAVE_CONTEXTVAR = True
if sys.maxsize == 2**63-1:
MAX_PREC = 999999999999999999
MAX_EMAX = 999999999999999999
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_asyncio/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def tearDownModule():
asyncio.set_event_loop_policy(None)


@unittest.skipUnless(decimal.HAVE_CONTEXTVAR, "decimal is built with a thread-local context")
class DecimalContextTest(unittest.TestCase):

def test_asyncio_task_decimal_context(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add --without-decimal-contextvar build option. This enables a thread-local
rather than a coroutine local context.
169 changes: 156 additions & 13 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,14 @@ incr_false(void)
}


#ifndef WITH_DECIMAL_CONTEXTVAR
/* Key for thread state dictionary */
static PyObject *tls_context_key = NULL;
/* Invariant: NULL or the most recently accessed thread local context */
static PyDecContextObject *cached_context = NULL;
#else
static PyObject *current_context_var;
#endif

/* Template for creating new thread contexts, calling Context() without
* arguments and initializing the module_context on first access. */
Expand Down Expand Up @@ -1217,6 +1224,12 @@ context_new(PyTypeObject *type, PyObject *args UNUSED, PyObject *kwds UNUSED)
static void
context_dealloc(PyDecContextObject *self)
{
#ifndef WITH_DECIMAL_CONTEXTVAR
if (self == cached_context) {
cached_context = NULL;
}
#endif

Py_XDECREF(self->traps);
Py_XDECREF(self->flags);
Py_TYPE(self)->tp_free(self);
Expand Down Expand Up @@ -1491,6 +1504,134 @@ static PyGetSetDef context_getsets [] =
* operation.
*/

#ifndef WITH_DECIMAL_CONTEXTVAR
/* Get the context from the thread state dictionary. */
static PyObject *
current_context_from_dict(void)
{
PyObject *dict;
PyObject *tl_context;
PyThreadState *tstate;

dict = PyThreadState_GetDict();
if (dict == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"cannot get thread state");
return NULL;
}

tl_context = PyDict_GetItemWithError(dict, tls_context_key);
if (tl_context != NULL) {
/* We already have a thread local context. */
CONTEXT_CHECK(tl_context);
}
else {
if (PyErr_Occurred()) {
return NULL;
}

/* Set up a new thread local context. */
tl_context = context_copy(default_context_template, NULL);
if (tl_context == NULL) {
return NULL;
}
CTX(tl_context)->status = 0;

if (PyDict_SetItem(dict, tls_context_key, tl_context) < 0) {
Py_DECREF(tl_context);
return NULL;
}
Py_DECREF(tl_context);
}

/* Cache the context of the current thread, assuming that it
* will be accessed several times before a thread switch. */
tstate = PyThreadState_GET();
if (tstate) {
cached_context = (PyDecContextObject *)tl_context;
cached_context->tstate = tstate;
}

/* Borrowed reference with refcount==1 */
return tl_context;
}

/* Return borrowed reference to thread local context. */
static PyObject *
current_context(void)
{
PyThreadState *tstate;

tstate = PyThreadState_GET();
if (cached_context && cached_context->tstate == tstate) {
return (PyObject *)cached_context;
}

return current_context_from_dict();
}

/* ctxobj := borrowed reference to the current context */
#define CURRENT_CONTEXT(ctxobj) \
ctxobj = current_context(); \
if (ctxobj == NULL) { \
return NULL; \
}

/* Return a new reference to the current context */
static PyObject *
PyDec_GetCurrentContext(PyObject *self UNUSED, PyObject *args UNUSED)
{
PyObject *context;

context = current_context();
if (context == NULL) {
return NULL;
}

Py_INCREF(context);
return context;
}

/* Set the thread local context to a new context, decrement old reference */
static PyObject *
PyDec_SetCurrentContext(PyObject *self UNUSED, PyObject *v)
{
PyObject *dict;

CONTEXT_CHECK(v);

dict = PyThreadState_GetDict();
if (dict == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"cannot get thread state");
return NULL;
}

/* If the new context is one of the templates, make a copy.
* This is the current behavior of decimal.py. */
if (v == default_context_template ||
v == basic_context_template ||
v == extended_context_template) {
v = context_copy(v, NULL);
if (v == NULL) {
return NULL;
}
CTX(v)->status = 0;
}
else {
Py_INCREF(v);
}

cached_context = NULL;
if (PyDict_SetItem(dict, tls_context_key, v) < 0) {
Py_DECREF(v);
return NULL;
}

Py_DECREF(v);
Py_RETURN_NONE;
}
#else
static PyObject *
init_current_context(void)
{
Expand Down Expand Up @@ -1570,6 +1711,7 @@ PyDec_SetCurrentContext(PyObject *self UNUSED, PyObject *v)

Py_RETURN_NONE;
}
#endif

/* Context manager object for the 'with' statement. The manager
* owns one reference to the global (outer) context and one
Expand Down Expand Up @@ -4388,15 +4530,8 @@ _dec_hash(PyDecObject *v)
mpd_ssize_t exp;
uint32_t status = 0;
mpd_context_t maxctx;
PyObject *context;


context = current_context();
if (context == NULL) {
return -1;
}
Py_DECREF(context);

if (mpd_isspecial(MPD(v))) {
if (mpd_issnan(MPD(v))) {
PyErr_SetString(PyExc_TypeError,
Expand Down Expand Up @@ -5538,11 +5673,6 @@ PyInit__decimal(void)
mpd_free = PyMem_Free;
mpd_setminalloc(_Py_DEC_MINALLOC);

/* Init context variable */
current_context_var = PyContextVar_New("decimal_context", NULL);
if (current_context_var == NULL) {
goto error;
}

/* Init external C-API functions */
_py_long_multiply = PyLong_Type.tp_as_number->nb_multiply;
Expand Down Expand Up @@ -5714,6 +5844,15 @@ PyInit__decimal(void)
CHECK_INT(PyModule_AddObject(m, "DefaultContext",
default_context_template));

#ifndef WITH_DECIMAL_CONTEXTVAR
ASSIGN_PTR(tls_context_key, PyUnicode_FromString("___DECIMAL_CTX__"));
Py_INCREF(Py_False);
CHECK_INT(PyModule_AddObject(m, "HAVE_CONTEXTVAR", Py_False));
#else
ASSIGN_PTR(current_context_var, PyContextVar_New("decimal_context", NULL));
Py_INCREF(Py_True);
CHECK_INT(PyModule_AddObject(m, "HAVE_CONTEXTVAR", Py_True));
#endif
Py_INCREF(Py_True);
CHECK_INT(PyModule_AddObject(m, "HAVE_THREADS", Py_True));

Expand Down Expand Up @@ -5773,9 +5912,13 @@ PyInit__decimal(void)
Py_CLEAR(SignalTuple); /* GCOV_NOT_REACHED */
Py_CLEAR(DecimalTuple); /* GCOV_NOT_REACHED */
Py_CLEAR(default_context_template); /* GCOV_NOT_REACHED */
#ifndef WITH_DECIMAL_CONTEXTVAR
Py_CLEAR(tls_context_key); /* GCOV_NOT_REACHED */
#else
Py_CLEAR(current_context_var); /* GCOV_NOT_REACHED */
#endif
Py_CLEAR(basic_context_template); /* GCOV_NOT_REACHED */
Py_CLEAR(extended_context_template); /* GCOV_NOT_REACHED */
Py_CLEAR(current_context_var); /* GCOV_NOT_REACHED */
Py_CLEAR(m); /* GCOV_NOT_REACHED */

return NULL; /* GCOV_NOT_REACHED */
Expand Down
Loading