Skip to content

Commit

Permalink
bpo-39794: Add --without-decimal-contextvar (python#18702)
Browse files Browse the repository at this point in the history
  • Loading branch information
skrah authored Feb 29, 2020
1 parent 0aeab5c commit 815280e
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 36 deletions.
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

0 comments on commit 815280e

Please sign in to comment.