Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions Lib/test/test_capi/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,11 @@ def expect_bool_not(value):
for value in new_values:
expected, expect_flag = expect_func(value)

old_flags = sys.flags
config_set(name, value)
self.assertEqual(config_get(name), expected)
self.assertEqual(getattr(sys.flags, sys_flag), expect_flag)
self.assertIsNot(sys.flags, old_flags)
if name == "write_bytecode":
self.assertEqual(getattr(sys, "dont_write_bytecode"),
expect_flag)
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_free_threading/test_sys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sys
import unittest
from test.support import threading_helper


class SysModuleTest(unittest.TestCase):
def test_int_max_str_digits_thread(self):
# gh-151218: Check that it's safe to call set_int_max_str_digits()
# in parallel. Previously, this test triggered warnings in TSan
# on a free threaded build.

old_limit = sys.get_int_max_str_digits()
self.addCleanup(sys.set_int_max_str_digits, old_limit)

def worker():
for i in range (20_000):
sys.set_int_max_str_digits(4300 + (i & 7))

threading_helper.run_concurrently(worker, nthreads=4)


if __name__ == "__main__":
unittest.main()
20 changes: 20 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,26 @@ def test_pystats(self):
def test_disable_gil_abi(self):
self.assertEqual('t' in sys.abiflags, support.Py_GIL_DISABLED)

def test_int_max_str_digits(self):
old_limit = sys.get_int_max_str_digits()
self.assertIsInstance(old_limit, int)
self.assertGreaterEqual(old_limit, 0)
self.addCleanup(sys.set_int_max_str_digits, old_limit)

sys.set_int_max_str_digits(0)
self.assertEqual(sys.get_int_max_str_digits(), 0)

sys.set_int_max_str_digits(2_048)
self.assertEqual(sys.get_int_max_str_digits(), 2_048)

with self.assertRaises(ValueError):
# the minimum is 640 digits
sys.set_int_max_str_digits(5)
with self.assertRaises(ValueError):
sys.set_int_max_str_digits(-2)
with self.assertRaises(TypeError):
sys.set_int_max_str_digits(2_048.0)


@test.support.cpython_only
@test.support.force_not_colorized_test_class
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:c:func:`PyConfig_Set()` and :func:`sys.set_int_max_str_digits` now replace
:data:`sys.flags` (create a new object), instead of modifying :data:`sys.flags`
in-place. Patch by Victor Stinner.
Comment thread
vstinner marked this conversation as resolved.
Outdated
80 changes: 63 additions & 17 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1874,7 +1874,8 @@ sys_get_int_max_str_digits_impl(PyObject *module)
/*[clinic end generated code: output=0042f5e8ae0e8631 input=77fb74e987ba7ecb]*/
{
PyInterpreterState *interp = _PyInterpreterState_GET();
return PyLong_FromLong(interp->long_state.max_str_digits);
int maxdigits = _Py_atomic_load_int(&interp->long_state.max_str_digits);
return PyLong_FromLong(maxdigits);
}


Expand Down Expand Up @@ -3490,14 +3491,50 @@ sys_set_flag(PyObject *flags, Py_ssize_t pos, PyObject *value)
int
_PySys_SetFlagObj(Py_ssize_t pos, PyObject *value)
{
PyObject *flags = PySys_GetAttrString("flags");
if (flags == NULL) {
return -1;
PyObject *old_flags = NULL;
PyObject *new_flags = NULL;
PyObject *flags_str = NULL;

flags_str = PyUnicode_FromString("flags");
if (flags_str == NULL) {
goto error;
}
Comment thread
vstinner marked this conversation as resolved.
Outdated

sys_set_flag(flags, pos, value);
Py_DECREF(flags);
return 0;
old_flags = PySys_GetAttr(flags_str);
if (old_flags == NULL) {
goto error;
}

new_flags = PyStructSequence_New(&FlagsType);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this dance makes me wonder whether we need a PyStructSequence_Copy. Would be nicer to just copy the structure and alter one field.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I searched for PyStructSequence_New() and the only other place which creates a copy of an existing structseq is structseq_replace(): structseq.__replace__() method. I'm not sure that it's worth it to write a generic PyStructSequence_Copy() function.

Example using structseq.__replace__() method:

$ python
>>> import sys, copy
>>> copy.replace(sys.flags, verbose=3)
sys.flags(..., verbose=3, ...)

if (new_flags == NULL) {
goto error;
}

for (Py_ssize_t i=0; i < (Py_ssize_t)(Py_ARRAY_LENGTH(flags_fields) - 1); i++) {
Comment thread
vstinner marked this conversation as resolved.
Outdated
if (i != pos) {
PyObject *old_value;
old_value = PyStructSequence_GET_ITEM(old_flags, i); // borrowed ref
if (old_value == NULL) {
goto error;
}
Comment thread
vstinner marked this conversation as resolved.
Outdated
sys_set_flag(new_flags, i, old_value);
}
else {
sys_set_flag(new_flags, pos, value);
}
}

int res = _PySys_SetAttr(flags_str, new_flags);
Py_DECREF(flags_str);
Py_DECREF(old_flags);
Py_DECREF(new_flags);
return res;

error:
Py_DECREF(flags_str);
Py_DECREF(old_flags);
Py_DECREF(new_flags);
return -1;
Comment thread
vstinner marked this conversation as resolved.
}


Expand All @@ -3521,8 +3558,6 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags)
const PyPreConfig *preconfig = &interp->runtime->preconfig;
const PyConfig *config = _PyInterpreterState_GetConfig(interp);

// _PySys_UpdateConfig() modifies sys.flags in-place:
// Py_XDECREF() is needed in this case.
Py_ssize_t pos = 0;
#define SetFlagObj(expr) \
do { \
Expand Down Expand Up @@ -4153,16 +4188,27 @@ _PySys_UpdateConfig(PyThreadState *tstate)
#undef COPY_LIST
#undef COPY_WSTR

// sys.flags
PyObject *flags = PySys_GetAttrString("flags");
if (flags == NULL) {
// replace sys.flags
PyObject *new_flags = PyStructSequence_New(&FlagsType);
if (new_flags == NULL) {
return -1;
}
if (set_flags_from_config(interp, flags) < 0) {
Py_DECREF(flags);
if (set_flags_from_config(interp, new_flags) < 0) {
Py_DECREF(new_flags);
return -1;
}

PyObject *flags_str = PyUnicode_FromString("flags");
if (flags_str == NULL) {
Py_DECREF(new_flags);
return -1;
}
res = _PySys_SetAttr(flags_str, new_flags);
Py_DECREF(new_flags);
Py_DECREF(flags_str);
if (res < 0) {
return -1;
}
Py_DECREF(flags);

SET_SYS("dont_write_bytecode", PyBool_FromLong(!config->write_bytecode));

Expand Down Expand Up @@ -4675,7 +4721,7 @@ _PySys_SetIntMaxStrDigits(int maxdigits)
// Set PyInterpreterState.long_state.max_str_digits
// and PyInterpreterState.config.int_max_str_digits.
PyInterpreterState *interp = _PyInterpreterState_GET();
interp->long_state.max_str_digits = maxdigits;
interp->config.int_max_str_digits = maxdigits;
_Py_atomic_store_int(&interp->long_state.max_str_digits, maxdigits);
_Py_atomic_store_int(&interp->config.int_max_str_digits, maxdigits);
Comment thread
picnixz marked this conversation as resolved.
return 0;
}
Loading