Skip to content

Commit 4d70124

Browse files
author
Stefan Krah
authored
[3.8] bpo-39794: Add --without-decimal-contextvar (GH-18702)
(cherry picked from commit 815280e)
1 parent e4686b7 commit 4d70124

File tree

11 files changed

+248
-36
lines changed

11 files changed

+248
-36
lines changed

Doc/library/decimal.rst

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,9 +1475,18 @@ are also included in the pure Python version for compatibility.
14751475

14761476
.. data:: HAVE_THREADS
14771477

1478-
The default value is ``True``. If Python is compiled without threads, the
1479-
C version automatically disables the expensive thread local context
1480-
machinery. In this case, the value is ``False``.
1478+
The value is ``True``. Deprecated, because Python now always has threads.
1479+
1480+
.. deprecated:: 3.9
1481+
1482+
.. data:: HAVE_CONTEXTVAR
1483+
1484+
The default value is ``True``. If Python is compiled ``--without-decimal-contextvar``,
1485+
the C version uses a thread-local rather than a coroutine-local context and the value
1486+
is ``False``. This is slightly faster in some nested context scenarios.
1487+
1488+
.. versionadded:: 3.9, backported to 3.7 and 3.8
1489+
14811490

14821491
Rounding modes
14831492
--------------

Lib/_pydecimal.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,11 @@
140140
# Limits for the C version for compatibility
141141
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',
142142

143-
# C version: compile time choice that enables the thread local context
144-
'HAVE_THREADS'
143+
# C version: compile time choice that enables the thread local context (deprecated, now always true)
144+
'HAVE_THREADS',
145+
146+
# C version: compile time choice that enables the coroutine local context
147+
'HAVE_CONTEXTVAR'
145148
]
146149

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

173176
# Compatibility with the C version
174177
HAVE_THREADS = True
178+
HAVE_CONTEXTVAR = True
175179
if sys.maxsize == 2**63-1:
176180
MAX_PREC = 999999999999999999
177181
MAX_EMAX = 999999999999999999

Lib/test/test_asyncio/test_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def tearDownModule():
77
asyncio.set_event_loop_policy(None)
88

99

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

1213
def test_asyncio_task_decimal_context(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add --without-decimal-contextvar build option. This enables a thread-local
2+
rather than a coroutine local context.

Modules/_decimal/_decimal.c

Lines changed: 156 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,14 @@ incr_false(void)
122122
}
123123

124124

125+
#ifndef WITH_DECIMAL_CONTEXTVAR
126+
/* Key for thread state dictionary */
127+
static PyObject *tls_context_key = NULL;
128+
/* Invariant: NULL or the most recently accessed thread local context */
129+
static PyDecContextObject *cached_context = NULL;
130+
#else
125131
static PyObject *current_context_var;
132+
#endif
126133

127134
/* Template for creating new thread contexts, calling Context() without
128135
* arguments and initializing the module_context on first access. */
@@ -1217,6 +1224,12 @@ context_new(PyTypeObject *type, PyObject *args UNUSED, PyObject *kwds UNUSED)
12171224
static void
12181225
context_dealloc(PyDecContextObject *self)
12191226
{
1227+
#ifndef WITH_DECIMAL_CONTEXTVAR
1228+
if (self == cached_context) {
1229+
cached_context = NULL;
1230+
}
1231+
#endif
1232+
12201233
Py_XDECREF(self->traps);
12211234
Py_XDECREF(self->flags);
12221235
Py_TYPE(self)->tp_free(self);
@@ -1491,6 +1504,134 @@ static PyGetSetDef context_getsets [] =
14911504
* operation.
14921505
*/
14931506

1507+
#ifndef WITH_DECIMAL_CONTEXTVAR
1508+
/* Get the context from the thread state dictionary. */
1509+
static PyObject *
1510+
current_context_from_dict(void)
1511+
{
1512+
PyObject *dict;
1513+
PyObject *tl_context;
1514+
PyThreadState *tstate;
1515+
1516+
dict = PyThreadState_GetDict();
1517+
if (dict == NULL) {
1518+
PyErr_SetString(PyExc_RuntimeError,
1519+
"cannot get thread state");
1520+
return NULL;
1521+
}
1522+
1523+
tl_context = PyDict_GetItemWithError(dict, tls_context_key);
1524+
if (tl_context != NULL) {
1525+
/* We already have a thread local context. */
1526+
CONTEXT_CHECK(tl_context);
1527+
}
1528+
else {
1529+
if (PyErr_Occurred()) {
1530+
return NULL;
1531+
}
1532+
1533+
/* Set up a new thread local context. */
1534+
tl_context = context_copy(default_context_template, NULL);
1535+
if (tl_context == NULL) {
1536+
return NULL;
1537+
}
1538+
CTX(tl_context)->status = 0;
1539+
1540+
if (PyDict_SetItem(dict, tls_context_key, tl_context) < 0) {
1541+
Py_DECREF(tl_context);
1542+
return NULL;
1543+
}
1544+
Py_DECREF(tl_context);
1545+
}
1546+
1547+
/* Cache the context of the current thread, assuming that it
1548+
* will be accessed several times before a thread switch. */
1549+
tstate = PyThreadState_GET();
1550+
if (tstate) {
1551+
cached_context = (PyDecContextObject *)tl_context;
1552+
cached_context->tstate = tstate;
1553+
}
1554+
1555+
/* Borrowed reference with refcount==1 */
1556+
return tl_context;
1557+
}
1558+
1559+
/* Return borrowed reference to thread local context. */
1560+
static PyObject *
1561+
current_context(void)
1562+
{
1563+
PyThreadState *tstate;
1564+
1565+
tstate = PyThreadState_GET();
1566+
if (cached_context && cached_context->tstate == tstate) {
1567+
return (PyObject *)cached_context;
1568+
}
1569+
1570+
return current_context_from_dict();
1571+
}
1572+
1573+
/* ctxobj := borrowed reference to the current context */
1574+
#define CURRENT_CONTEXT(ctxobj) \
1575+
ctxobj = current_context(); \
1576+
if (ctxobj == NULL) { \
1577+
return NULL; \
1578+
}
1579+
1580+
/* Return a new reference to the current context */
1581+
static PyObject *
1582+
PyDec_GetCurrentContext(PyObject *self UNUSED, PyObject *args UNUSED)
1583+
{
1584+
PyObject *context;
1585+
1586+
context = current_context();
1587+
if (context == NULL) {
1588+
return NULL;
1589+
}
1590+
1591+
Py_INCREF(context);
1592+
return context;
1593+
}
1594+
1595+
/* Set the thread local context to a new context, decrement old reference */
1596+
static PyObject *
1597+
PyDec_SetCurrentContext(PyObject *self UNUSED, PyObject *v)
1598+
{
1599+
PyObject *dict;
1600+
1601+
CONTEXT_CHECK(v);
1602+
1603+
dict = PyThreadState_GetDict();
1604+
if (dict == NULL) {
1605+
PyErr_SetString(PyExc_RuntimeError,
1606+
"cannot get thread state");
1607+
return NULL;
1608+
}
1609+
1610+
/* If the new context is one of the templates, make a copy.
1611+
* This is the current behavior of decimal.py. */
1612+
if (v == default_context_template ||
1613+
v == basic_context_template ||
1614+
v == extended_context_template) {
1615+
v = context_copy(v, NULL);
1616+
if (v == NULL) {
1617+
return NULL;
1618+
}
1619+
CTX(v)->status = 0;
1620+
}
1621+
else {
1622+
Py_INCREF(v);
1623+
}
1624+
1625+
cached_context = NULL;
1626+
if (PyDict_SetItem(dict, tls_context_key, v) < 0) {
1627+
Py_DECREF(v);
1628+
return NULL;
1629+
}
1630+
1631+
Py_DECREF(v);
1632+
Py_RETURN_NONE;
1633+
}
1634+
#else
14941635
static PyObject *
14951636
init_current_context(void)
14961637
{
@@ -1570,6 +1711,7 @@ PyDec_SetCurrentContext(PyObject *self UNUSED, PyObject *v)
15701711

15711712
Py_RETURN_NONE;
15721713
}
1714+
#endif
15731715

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

43934534

4394-
context = current_context();
4395-
if (context == NULL) {
4396-
return -1;
4397-
}
4398-
Py_DECREF(context);
4399-
44004535
if (mpd_isspecial(MPD(v))) {
44014536
if (mpd_issnan(MPD(v))) {
44024537
PyErr_SetString(PyExc_TypeError,
@@ -5538,11 +5673,6 @@ PyInit__decimal(void)
55385673
mpd_free = PyMem_Free;
55395674
mpd_setminalloc(_Py_DEC_MINALLOC);
55405675

5541-
/* Init context variable */
5542-
current_context_var = PyContextVar_New("decimal_context", NULL);
5543-
if (current_context_var == NULL) {
5544-
goto error;
5545-
}
55465676

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

5847+
#ifndef WITH_DECIMAL_CONTEXTVAR
5848+
ASSIGN_PTR(tls_context_key, PyUnicode_FromString("___DECIMAL_CTX__"));
5849+
Py_INCREF(Py_False);
5850+
CHECK_INT(PyModule_AddObject(m, "HAVE_CONTEXTVAR", Py_False));
5851+
#else
5852+
ASSIGN_PTR(current_context_var, PyContextVar_New("decimal_context", NULL));
5853+
Py_INCREF(Py_True);
5854+
CHECK_INT(PyModule_AddObject(m, "HAVE_CONTEXTVAR", Py_True));
5855+
#endif
57175856
Py_INCREF(Py_True);
57185857
CHECK_INT(PyModule_AddObject(m, "HAVE_THREADS", Py_True));
57195858

@@ -5773,9 +5912,13 @@ PyInit__decimal(void)
57735912
Py_CLEAR(SignalTuple); /* GCOV_NOT_REACHED */
57745913
Py_CLEAR(DecimalTuple); /* GCOV_NOT_REACHED */
57755914
Py_CLEAR(default_context_template); /* GCOV_NOT_REACHED */
5915+
#ifndef WITH_DECIMAL_CONTEXTVAR
5916+
Py_CLEAR(tls_context_key); /* GCOV_NOT_REACHED */
5917+
#else
5918+
Py_CLEAR(current_context_var); /* GCOV_NOT_REACHED */
5919+
#endif
57765920
Py_CLEAR(basic_context_template); /* GCOV_NOT_REACHED */
57775921
Py_CLEAR(extended_context_template); /* GCOV_NOT_REACHED */
5778-
Py_CLEAR(current_context_var); /* GCOV_NOT_REACHED */
57795922
Py_CLEAR(m); /* GCOV_NOT_REACHED */
57805923

57815924
return NULL; /* GCOV_NOT_REACHED */

0 commit comments

Comments
 (0)