Skip to content

Commit

Permalink
[3.7] bpo-34247: Fix Python 3.7 initialization (#8659)
Browse files Browse the repository at this point in the history
* -X dev: it is now possible to override the memory allocator using
  PYTHONMALLOC even if the developer mode is enabled.
* Add _Py_InitializeFromConfig()
* Add _Py_Initialize_ReadEnvVars() to set global configuration
  variables from environment variables
* Fix the code to initialize Python: Py_Initialize() now also reads
  environment variables
* _Py_InitializeCore() can now be called twice: the second call
  only replaces the configuration.
* Write unit tests on Py_Initialize() and the different ways to
  configure Python
* The isolated mode now always sets Py_IgnoreEnvironmentFlag and
  Py_NoUserSiteDirectory to 1.
* pymain_read_conf() now saves/restores the configuration
  if the encoding changed
  • Loading branch information
vstinner committed Aug 5, 2018
1 parent e65ec49 commit 0c90d6f
Show file tree
Hide file tree
Showing 10 changed files with 781 additions and 162 deletions.
20 changes: 16 additions & 4 deletions Include/pylifecycle.h
Expand Up @@ -51,14 +51,24 @@ PyAPI_FUNC(int) Py_SetStandardStreamEncoding(const char *encoding,
const char *errors);

/* PEP 432 Multi-phase initialization API (Private while provisional!) */
PyAPI_FUNC(_PyInitError) _Py_InitializeCore(const _PyCoreConfig *);
PyAPI_FUNC(_PyInitError) _Py_InitializeCore(
PyInterpreterState **interp_p,
const _PyCoreConfig *config);
PyAPI_FUNC(int) _Py_IsCoreInitialized(void);
PyAPI_FUNC(_PyInitError) _Py_InitializeFromConfig(
const _PyCoreConfig *config);
#ifdef Py_BUILD_CORE
PyAPI_FUNC(void) _Py_Initialize_ReadEnvVarsNoAlloc(void);
#endif

PyAPI_FUNC(_PyInitError) _PyCoreConfig_Read(_PyCoreConfig *);
PyAPI_FUNC(void) _PyCoreConfig_Clear(_PyCoreConfig *);
PyAPI_FUNC(int) _PyCoreConfig_Copy(
_PyCoreConfig *config,
const _PyCoreConfig *config2);
PyAPI_FUNC(void) _PyCoreConfig_SetGlobalConfig(
const _PyCoreConfig *config);


PyAPI_FUNC(_PyInitError) _PyMainInterpreterConfig_Read(
_PyMainInterpreterConfig *config,
Expand All @@ -68,14 +78,16 @@ PyAPI_FUNC(int) _PyMainInterpreterConfig_Copy(
_PyMainInterpreterConfig *config,
const _PyMainInterpreterConfig *config2);

PyAPI_FUNC(_PyInitError) _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *);
#endif
PyAPI_FUNC(_PyInitError) _Py_InitializeMainInterpreter(
PyInterpreterState *interp,
const _PyMainInterpreterConfig *config);
#endif /* !defined(Py_LIMITED_API) */


/* Initialization and finalization */
PyAPI_FUNC(void) Py_Initialize(void);
PyAPI_FUNC(void) Py_InitializeEx(int);
#ifndef Py_LIMITED_API
PyAPI_FUNC(_PyInitError) _Py_InitializeEx_Private(int, int);
PyAPI_FUNC(void) _Py_FatalInitError(_PyInitError err) _Py_NO_RETURN;
#endif
PyAPI_FUNC(void) Py_Finalize(void);
Expand Down
3 changes: 3 additions & 0 deletions Include/pystate.h
Expand Up @@ -79,8 +79,11 @@ typedef struct {
#define _PyCoreConfig_INIT \
(_PyCoreConfig){ \
.install_signal_handlers = -1, \
.ignore_environment = -1, \
.use_hash_seed = -1, \
.coerce_c_locale = -1, \
.faulthandler = -1, \
.tracemalloc = -1, \
.utf8_mode = -1, \
.argc = -1, \
.nmodule_search_path = -1}
Expand Down
4 changes: 1 addition & 3 deletions Lib/test/test_cmd_line.py
Expand Up @@ -523,9 +523,7 @@ def run_xdev(self, *args, check_exitcode=True, xdev=True):
env = dict(os.environ)
env.pop('PYTHONWARNINGS', None)
env.pop('PYTHONDEVMODE', None)
# Force malloc() to disable the debug hooks which are enabled
# by default for Python compiled in debug mode
env['PYTHONMALLOC'] = 'malloc'
env.pop('PYTHONMALLOC', None)

if xdev:
args = (sys.executable, '-X', 'dev', *args)
Expand Down
148 changes: 147 additions & 1 deletion Lib/test/test_embed.py
Expand Up @@ -9,7 +9,7 @@
import sys


class EmbeddingTests(unittest.TestCase):
class EmbeddingTestsMixin:
def setUp(self):
here = os.path.abspath(__file__)
basepath = os.path.dirname(os.path.dirname(os.path.dirname(here)))
Expand Down Expand Up @@ -110,6 +110,8 @@ def run_repeated_init_and_subinterpreters(self):
yield current_run
current_run = []


class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
def test_subinterps_main(self):
for run in self.run_repeated_init_and_subinterpreters():
main = run[0]
Expand Down Expand Up @@ -247,5 +249,149 @@ def test_initialize_pymain(self):
self.assertEqual(err, '')


class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
maxDiff = 4096
DEFAULT_CONFIG = {
'install_signal_handlers': 1,
'Py_IgnoreEnvironmentFlag': 0,
'use_hash_seed': 0,
'hash_seed': 0,
'allocator': '(null)',
'dev_mode': 0,
'faulthandler': 0,
'tracemalloc': 0,
'import_time': 0,
'show_ref_count': 0,
'show_alloc_count': 0,
'dump_refs': 0,
'malloc_stats': 0,
'utf8_mode': 0,

'coerce_c_locale': 0,
'coerce_c_locale_warn': 0,

'program_name': './_testembed',
'argc': 0,
'argv': '[]',
'program': '(null)',

'Py_IsolatedFlag': 0,
'Py_NoSiteFlag': 0,
'Py_BytesWarningFlag': 0,
'Py_InspectFlag': 0,
'Py_InteractiveFlag': 0,
'Py_OptimizeFlag': 0,
'Py_DebugFlag': 0,
'Py_DontWriteBytecodeFlag': 0,
'Py_VerboseFlag': 0,
'Py_QuietFlag': 0,
'Py_NoUserSiteDirectory': 0,
'Py_UnbufferedStdioFlag': 0,

'_disable_importlib': 0,
'Py_FrozenFlag': 0,
}

def check_config(self, testname, expected):
env = dict(os.environ)
for key in list(env):
if key.startswith('PYTHON'):
del env[key]
# Disable C locale coercion and UTF-8 mode to not depend
# on the current locale
env['PYTHONCOERCECLOCALE'] = '0'
env['PYTHONUTF8'] = '0'
out, err = self.run_embedded_interpreter(testname, env=env)
# Ignore err

expected = dict(self.DEFAULT_CONFIG, **expected)
for key, value in expected.items():
expected[key] = str(value)

config = {}
for line in out.splitlines():
key, value = line.split(' = ', 1)
config[key] = value
self.assertEqual(config, expected)

def test_init_default_config(self):
self.check_config("init_default_config", {})

def test_init_global_config(self):
config = {
'program_name': './globalvar',
'Py_NoSiteFlag': 1,
'Py_BytesWarningFlag': 1,
'Py_InspectFlag': 1,
'Py_InteractiveFlag': 1,
'Py_OptimizeFlag': 2,
'Py_DontWriteBytecodeFlag': 1,
'Py_VerboseFlag': 1,
'Py_QuietFlag': 1,
'Py_UnbufferedStdioFlag': 1,
'utf8_mode': 1,
'Py_NoUserSiteDirectory': 1,
'Py_FrozenFlag': 1,
}
self.check_config("init_global_config", config)

def test_init_from_config(self):
config = {
'install_signal_handlers': 0,
'use_hash_seed': 1,
'hash_seed': 123,
'allocator': 'malloc_debug',
'tracemalloc': 2,
'import_time': 1,
'show_ref_count': 1,
'show_alloc_count': 1,
'malloc_stats': 1,

'utf8_mode': 1,

'program_name': './conf_program_name',
'program': 'conf_program',

'faulthandler': 1,
}
self.check_config("init_from_config", config)

def test_init_env(self):
config = {
'use_hash_seed': 1,
'hash_seed': 42,
'allocator': 'malloc_debug',
'tracemalloc': 2,
'import_time': 1,
'malloc_stats': 1,
'utf8_mode': 1,
'Py_InspectFlag': 1,
'Py_OptimizeFlag': 2,
'Py_DontWriteBytecodeFlag': 1,
'Py_VerboseFlag': 1,
'Py_UnbufferedStdioFlag': 1,
'Py_NoUserSiteDirectory': 1,
'faulthandler': 1,
'dev_mode': 1,
}
self.check_config("init_env", config)

def test_init_dev_mode(self):
config = {
'dev_mode': 1,
'faulthandler': 1,
'allocator': 'debug',
}
self.check_config("init_dev_mode", config)

def test_init_isolated(self):
config = {
'Py_IsolatedFlag': 1,
'Py_IgnoreEnvironmentFlag': 1,
'Py_NoUserSiteDirectory': 1,
}
self.check_config("init_isolated", config)


if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,2 @@
Fix Py_Initialize() regression introduced in 3.7.0: read environment
variables like PYTHONOPTIMIZE.
@@ -0,0 +1,2 @@
-X dev: it is now possible to override the memory allocator using
PYTHONMALLOC even if the developer mode is enabled.

0 comments on commit 0c90d6f

Please sign in to comment.