Skip to content

Commit a7368ac

Browse files
authored
bpo-32030: Enhance Py_Main() (#4412)
Parse more env vars in Py_Main(): * Add more options to _PyCoreConfig: * faulthandler * tracemalloc * importtime * Move code to parse environment variables from _Py_InitializeCore() to Py_Main(). This change fixes a regression from Python 3.6: PYTHONUNBUFFERED is now read before calling pymain_init_stdio(). * _PyFaulthandler_Init() and _PyTraceMalloc_Init() now take an argument to decide if the module has to be enabled at startup. * tracemalloc_start() is now responsible to check the maximum number of frames. Other changes: * Cleanup Py_Main(): * Rename some pymain_xxx() subfunctions * Add pymain_run_python() subfunction * Cleanup Py_NewInterpreter() * _PyInterpreterState_Enable() now reports failure * init_hash_secret() now considers pyurandom() failure as an "user error": don't fail with abort(). * pymain_optlist_append() and pymain_strdup() now sets err on memory allocation failure.
1 parent f7e5b56 commit a7368ac

File tree

12 files changed

+467
-383
lines changed

12 files changed

+467
-383
lines changed

Include/internal/pystate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ PyAPI_FUNC(_PyInitError) _PyRuntime_Initialize(void);
9090

9191
/* Other */
9292

93-
PyAPI_FUNC(void) _PyInterpreterState_Enable(_PyRuntimeState *);
93+
PyAPI_FUNC(_PyInitError) _PyInterpreterState_Enable(_PyRuntimeState *);
9494

9595
#ifdef __cplusplus
9696
}

Include/pydebug.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ PyAPI_DATA(int) Py_HashRandomizationFlag;
2525
PyAPI_DATA(int) Py_IsolatedFlag;
2626

2727
#ifdef MS_WINDOWS
28+
PyAPI_DATA(int) Py_LegacyWindowsFSEncodingFlag;
2829
PyAPI_DATA(int) Py_LegacyWindowsStdioFlag;
2930
#endif
3031

Include/pystate.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,20 @@ typedef struct {
3030
unsigned long hash_seed;
3131
int _disable_importlib; /* Needed by freeze_importlib */
3232
char *allocator;
33+
int faulthandler;
34+
int tracemalloc; /* Number of saved frames, 0=don't trace */
35+
int importtime; /* -X importtime */
3336
} _PyCoreConfig;
3437

35-
#define _PyCoreConfig_INIT {0, -1, 0, 0, NULL}
38+
#define _PyCoreConfig_INIT \
39+
{.ignore_environment = 0, \
40+
.use_hash_seed = -1, \
41+
.hash_seed = 0, \
42+
._disable_importlib = 0, \
43+
.allocator = NULL, \
44+
.faulthandler = 0, \
45+
.tracemalloc = 0, \
46+
.importtime = 0}
3647

3748
/* Placeholders while working on the new configuration API
3849
*

Lib/test/test_tracemalloc.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -829,16 +829,23 @@ def test_env_limit(self):
829829
stdout = stdout.rstrip()
830830
self.assertEqual(stdout, b'10')
831831

832+
def check_env_var_invalid(self, nframe):
833+
with support.SuppressCrashReport():
834+
ok, stdout, stderr = assert_python_failure(
835+
'-c', 'pass',
836+
PYTHONTRACEMALLOC=str(nframe))
837+
838+
if b'ValueError: the number of frames must be in range' in stderr:
839+
return
840+
if b'PYTHONTRACEMALLOC: invalid number of frames' in stderr:
841+
return
842+
self.fail(f"unexpeced output: {stderr!a}")
843+
844+
832845
def test_env_var_invalid(self):
833846
for nframe in (-1, 0, 2**30):
834847
with self.subTest(nframe=nframe):
835-
with support.SuppressCrashReport():
836-
ok, stdout, stderr = assert_python_failure(
837-
'-c', 'pass',
838-
PYTHONTRACEMALLOC=str(nframe))
839-
self.assertIn(b'PYTHONTRACEMALLOC: invalid '
840-
b'number of frames',
841-
stderr)
848+
self.check_env_var_invalid(nframe)
842849

843850
def test_sys_xoptions(self):
844851
for xoptions, nframe in (
@@ -852,15 +859,21 @@ def test_sys_xoptions(self):
852859
stdout = stdout.rstrip()
853860
self.assertEqual(stdout, str(nframe).encode('ascii'))
854861

862+
def check_sys_xoptions_invalid(self, nframe):
863+
args = ('-X', 'tracemalloc=%s' % nframe, '-c', 'pass')
864+
with support.SuppressCrashReport():
865+
ok, stdout, stderr = assert_python_failure(*args)
866+
867+
if b'ValueError: the number of frames must be in range' in stderr:
868+
return
869+
if b'-X tracemalloc=NFRAME: invalid number of frames' in stderr:
870+
return
871+
self.fail(f"unexpeced output: {stderr!a}")
872+
855873
def test_sys_xoptions_invalid(self):
856874
for nframe in (-1, 0, 2**30):
857875
with self.subTest(nframe=nframe):
858-
with support.SuppressCrashReport():
859-
args = ('-X', 'tracemalloc=%s' % nframe, '-c', 'pass')
860-
ok, stdout, stderr = assert_python_failure(*args)
861-
self.assertIn(b'-X tracemalloc=NFRAME: invalid '
862-
b'number of frames',
863-
stderr)
876+
self.check_sys_xoptions_invalid(nframe)
864877

865878
@unittest.skipIf(_testcapi is None, 'need _testcapi')
866879
def test_pymem_alloc0(self):

Modules/_tracemalloc.c

Lines changed: 16 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,8 +1066,16 @@ tracemalloc_start(int max_nframe)
10661066
PyMemAllocatorEx alloc;
10671067
size_t size;
10681068

1069-
if (tracemalloc_init() < 0)
1069+
if (max_nframe < 1 || max_nframe > MAX_NFRAME) {
1070+
PyErr_Format(PyExc_ValueError,
1071+
"the number of frames must be in range [1; %i]",
1072+
(int)MAX_NFRAME);
10701073
return -1;
1074+
}
1075+
1076+
if (tracemalloc_init() < 0) {
1077+
return -1;
1078+
}
10711079

10721080
if (tracemalloc_config.tracing) {
10731081
/* hook already installed: do nothing */
@@ -1500,7 +1508,7 @@ _PyMem_DumpTraceback(int fd, const void *ptr)
15001508
/*[clinic input]
15011509
_tracemalloc.start
15021510
1503-
nframe: Py_ssize_t = 1
1511+
nframe: int = 1
15041512
/
15051513
15061514
Start tracing Python memory allocations.
@@ -1510,22 +1518,12 @@ trace to nframe.
15101518
[clinic start generated code]*/
15111519

15121520
static PyObject *
1513-
_tracemalloc_start_impl(PyObject *module, Py_ssize_t nframe)
1514-
/*[clinic end generated code: output=0f558d2079511553 input=997841629cc441cb]*/
1521+
_tracemalloc_start_impl(PyObject *module, int nframe)
1522+
/*[clinic end generated code: output=caae05c23c159d3c input=40d849b5b29d1933]*/
15151523
{
1516-
int nframe_int;
1517-
1518-
if (nframe < 1 || nframe > MAX_NFRAME) {
1519-
PyErr_Format(PyExc_ValueError,
1520-
"the number of frames must be in range [1; %i]",
1521-
(int)MAX_NFRAME);
1524+
if (tracemalloc_start(nframe) < 0) {
15221525
return NULL;
15231526
}
1524-
nframe_int = Py_SAFE_DOWNCAST(nframe, Py_ssize_t, int);
1525-
1526-
if (tracemalloc_start(nframe_int) < 0)
1527-
return NULL;
1528-
15291527
Py_RETURN_NONE;
15301528
}
15311529

@@ -1658,87 +1656,13 @@ PyInit__tracemalloc(void)
16581656
}
16591657

16601658

1661-
static int
1662-
parse_sys_xoptions(PyObject *value)
1663-
{
1664-
PyObject *valuelong;
1665-
long nframe;
1666-
1667-
if (value == Py_True)
1668-
return 1;
1669-
1670-
assert(PyUnicode_Check(value));
1671-
if (PyUnicode_GetLength(value) == 0)
1672-
return -1;
1673-
1674-
valuelong = PyLong_FromUnicodeObject(value, 10);
1675-
if (valuelong == NULL)
1676-
return -1;
1677-
1678-
nframe = PyLong_AsLong(valuelong);
1679-
Py_DECREF(valuelong);
1680-
if (nframe == -1 && PyErr_Occurred())
1681-
return -1;
1682-
1683-
if (nframe < 1 || nframe > MAX_NFRAME)
1684-
return -1;
1685-
1686-
return Py_SAFE_DOWNCAST(nframe, long, int);
1687-
}
1688-
1689-
16901659
int
1691-
_PyTraceMalloc_Init(void)
1660+
_PyTraceMalloc_Init(int nframe)
16921661
{
1693-
char *p;
1694-
int nframe;
1695-
16961662
assert(PyGILState_Check());
1697-
1698-
if ((p = Py_GETENV("PYTHONTRACEMALLOC")) && *p != '\0') {
1699-
char *endptr = p;
1700-
long value;
1701-
1702-
errno = 0;
1703-
value = strtol(p, &endptr, 10);
1704-
if (*endptr != '\0'
1705-
|| value < 1
1706-
|| value > MAX_NFRAME
1707-
|| errno == ERANGE)
1708-
{
1709-
Py_FatalError("PYTHONTRACEMALLOC: invalid number of frames");
1710-
return -1;
1711-
}
1712-
1713-
nframe = (int)value;
1714-
}
1715-
else {
1716-
PyObject *xoptions, *key, *value;
1717-
1718-
xoptions = PySys_GetXOptions();
1719-
if (xoptions == NULL)
1720-
return -1;
1721-
1722-
key = PyUnicode_FromString("tracemalloc");
1723-
if (key == NULL)
1724-
return -1;
1725-
1726-
value = PyDict_GetItemWithError(xoptions, key); /* borrowed */
1727-
Py_DECREF(key);
1728-
if (value == NULL) {
1729-
if (PyErr_Occurred())
1730-
return -1;
1731-
1732-
/* -X tracemalloc is not used */
1733-
return 0;
1734-
}
1735-
1736-
nframe = parse_sys_xoptions(value);
1737-
if (nframe < 0) {
1738-
Py_FatalError("-X tracemalloc=NFRAME: invalid number of frames");
1739-
}
1663+
if (nframe == 0) {
1664+
return 0;
17401665
}
1741-
17421666
return tracemalloc_start(nframe);
17431667
}
17441668

Modules/clinic/_tracemalloc.c.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ PyDoc_STRVAR(_tracemalloc_start__doc__,
8787
{"start", (PyCFunction)_tracemalloc_start, METH_FASTCALL, _tracemalloc_start__doc__},
8888

8989
static PyObject *
90-
_tracemalloc_start_impl(PyObject *module, Py_ssize_t nframe);
90+
_tracemalloc_start_impl(PyObject *module, int nframe);
9191

9292
static PyObject *
9393
_tracemalloc_start(PyObject *module, PyObject **args, Py_ssize_t nargs)
9494
{
9595
PyObject *return_value = NULL;
96-
Py_ssize_t nframe = 1;
96+
int nframe = 1;
9797

98-
if (!_PyArg_ParseStack(args, nargs, "|n:start",
98+
if (!_PyArg_ParseStack(args, nargs, "|i:start",
9999
&nframe)) {
100100
goto exit;
101101
}
@@ -185,4 +185,4 @@ _tracemalloc_get_traced_memory(PyObject *module, PyObject *Py_UNUSED(ignored))
185185
{
186186
return _tracemalloc_get_traced_memory_impl(module);
187187
}
188-
/*[clinic end generated code: output=c9a0111391b3ec45 input=a9049054013a1b77]*/
188+
/*[clinic end generated code: output=db4f909464c186e2 input=a9049054013a1b77]*/

Modules/faulthandler.c

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,36 +1299,8 @@ faulthandler_init_enable(void)
12991299
return 0;
13001300
}
13011301

1302-
/* Call faulthandler.enable() if the PYTHONFAULTHANDLER environment variable
1303-
is defined, or if sys._xoptions has a 'faulthandler' key. */
1304-
1305-
static int
1306-
faulthandler_init_parse(void)
1307-
{
1308-
char *p = Py_GETENV("PYTHONFAULTHANDLER");
1309-
if (p && *p != '\0') {
1310-
return 1;
1311-
}
1312-
1313-
/* PYTHONFAULTHANDLER environment variable is missing
1314-
or an empty string */
1315-
PyObject *xoptions = PySys_GetXOptions();
1316-
if (xoptions == NULL) {
1317-
return -1;
1318-
}
1319-
1320-
PyObject *key = PyUnicode_FromString("faulthandler");
1321-
if (key == NULL) {
1322-
return -1;
1323-
}
1324-
1325-
int has_key = PyDict_Contains(xoptions, key);
1326-
Py_DECREF(key);
1327-
return has_key;
1328-
}
1329-
13301302
_PyInitError
1331-
_PyFaulthandler_Init(void)
1303+
_PyFaulthandler_Init(int enable)
13321304
{
13331305
#ifdef HAVE_SIGALTSTACK
13341306
int err;
@@ -1357,11 +1329,6 @@ _PyFaulthandler_Init(void)
13571329
PyThread_acquire_lock(thread.cancel_event, 1);
13581330
#endif
13591331

1360-
int enable = faulthandler_init_parse();
1361-
if (enable < 0) {
1362-
return _Py_INIT_ERR("failed to parse faulthandler env var and cmdline");
1363-
}
1364-
13651332
if (enable) {
13661333
if (faulthandler_init_enable() < 0) {
13671334
return _Py_INIT_ERR("failed to enable faulthandler");

0 commit comments

Comments
 (0)