[Python-checkins] gh-92452: Avoid race in initialization of sysconfig._CONFIG_VARS
FFY00
webhook-mailer at python.org
Fri Oct 28 14:17:50 EDT 2022
https://github.com/python/cpython/commit/7ee3aca00a0dffc9cb5906a24005a8c61d5bce14
commit: 7ee3aca00a0dffc9cb5906a24005a8c61d5bce14
branch: main
author: Gareth Rees <grees at undo.io>
committer: FFY00 <filipe.lains at gmail.com>
date: 2022-10-28T19:17:04+01:00
summary:
gh-92452: Avoid race in initialization of sysconfig._CONFIG_VARS
Co-authored-by: Filipe Laíns <lains at riseup.net>
files:
A Misc/NEWS.d/next/Library/2022-05-08-08-47-32.gh-issue-92452.3pNHe6.rst
M Lib/sysconfig.py
diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
index ebe371182742..73c25684db20 100644
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -2,6 +2,7 @@
import os
import sys
+import threading
from os.path import pardir, realpath
__all__ = [
@@ -172,7 +173,11 @@ def joinuser(*args):
_BASE_PREFIX = os.path.normpath(sys.base_prefix)
_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
_BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)
+# Mutex guarding initialization of _CONFIG_VARS.
+_CONFIG_VARS_LOCK = threading.RLock()
_CONFIG_VARS = None
+# True iff _CONFIG_VARS has been fully initialized.
+_CONFIG_VARS_INITIALIZED = False
_USER_BASE = None
# Regexes needed for parsing Makefile (and similar syntaxes,
@@ -626,6 +631,71 @@ def get_path(name, scheme=get_default_scheme(), vars=None, expand=True):
return get_paths(scheme, vars, expand)[name]
+def _init_config_vars():
+ global _CONFIG_VARS
+ _CONFIG_VARS = {}
+ # Normalized versions of prefix and exec_prefix are handy to have;
+ # in fact, these are the standard versions used most places in the
+ # Distutils.
+ _CONFIG_VARS['prefix'] = _PREFIX
+ _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
+ _CONFIG_VARS['py_version'] = _PY_VERSION
+ _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
+ _CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT
+ _CONFIG_VARS['installed_base'] = _BASE_PREFIX
+ _CONFIG_VARS['base'] = _PREFIX
+ _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX
+ _CONFIG_VARS['platbase'] = _EXEC_PREFIX
+ _CONFIG_VARS['projectbase'] = _PROJECT_BASE
+ _CONFIG_VARS['platlibdir'] = sys.platlibdir
+ try:
+ _CONFIG_VARS['abiflags'] = sys.abiflags
+ except AttributeError:
+ # sys.abiflags may not be defined on all platforms.
+ _CONFIG_VARS['abiflags'] = ''
+ try:
+ _CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '')
+ except AttributeError:
+ _CONFIG_VARS['py_version_nodot_plat'] = ''
+
+ if os.name == 'nt':
+ _init_non_posix(_CONFIG_VARS)
+ _CONFIG_VARS['VPATH'] = sys._vpath
+ if os.name == 'posix':
+ _init_posix(_CONFIG_VARS)
+ if _HAS_USER_BASE:
+ # Setting 'userbase' is done below the call to the
+ # init function to enable using 'get_config_var' in
+ # the init-function.
+ _CONFIG_VARS['userbase'] = _getuserbase()
+
+ # Always convert srcdir to an absolute path
+ srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE)
+ if os.name == 'posix':
+ if _PYTHON_BUILD:
+ # If srcdir is a relative path (typically '.' or '..')
+ # then it should be interpreted relative to the directory
+ # containing Makefile.
+ base = os.path.dirname(get_makefile_filename())
+ srcdir = os.path.join(base, srcdir)
+ else:
+ # srcdir is not meaningful since the installation is
+ # spread about the filesystem. We choose the
+ # directory containing the Makefile since we know it
+ # exists.
+ srcdir = os.path.dirname(get_makefile_filename())
+ _CONFIG_VARS['srcdir'] = _safe_realpath(srcdir)
+
+ # OS X platforms require special customization to handle
+ # multi-architecture, multi-os-version installers
+ if sys.platform == 'darwin':
+ import _osx_support
+ _osx_support.customize_config_vars(_CONFIG_VARS)
+
+ global _CONFIG_VARS_INITIALIZED
+ _CONFIG_VARS_INITIALIZED = True
+
+
def get_config_vars(*args):
"""With no arguments, return a dictionary of all configuration
variables relevant for the current platform.
@@ -636,66 +706,16 @@ def get_config_vars(*args):
With arguments, return a list of values that result from looking up
each argument in the configuration variable dictionary.
"""
- global _CONFIG_VARS
- if _CONFIG_VARS is None:
- _CONFIG_VARS = {}
- # Normalized versions of prefix and exec_prefix are handy to have;
- # in fact, these are the standard versions used most places in the
- # Distutils.
- _CONFIG_VARS['prefix'] = _PREFIX
- _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
- _CONFIG_VARS['py_version'] = _PY_VERSION
- _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
- _CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT
- _CONFIG_VARS['installed_base'] = _BASE_PREFIX
- _CONFIG_VARS['base'] = _PREFIX
- _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX
- _CONFIG_VARS['platbase'] = _EXEC_PREFIX
- _CONFIG_VARS['projectbase'] = _PROJECT_BASE
- _CONFIG_VARS['platlibdir'] = sys.platlibdir
- try:
- _CONFIG_VARS['abiflags'] = sys.abiflags
- except AttributeError:
- # sys.abiflags may not be defined on all platforms.
- _CONFIG_VARS['abiflags'] = ''
- try:
- _CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '')
- except AttributeError:
- _CONFIG_VARS['py_version_nodot_plat'] = ''
-
- if os.name == 'nt':
- _init_non_posix(_CONFIG_VARS)
- _CONFIG_VARS['VPATH'] = sys._vpath
- if os.name == 'posix':
- _init_posix(_CONFIG_VARS)
- if _HAS_USER_BASE:
- # Setting 'userbase' is done below the call to the
- # init function to enable using 'get_config_var' in
- # the init-function.
- _CONFIG_VARS['userbase'] = _getuserbase()
-
- # Always convert srcdir to an absolute path
- srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE)
- if os.name == 'posix':
- if _PYTHON_BUILD:
- # If srcdir is a relative path (typically '.' or '..')
- # then it should be interpreted relative to the directory
- # containing Makefile.
- base = os.path.dirname(get_makefile_filename())
- srcdir = os.path.join(base, srcdir)
- else:
- # srcdir is not meaningful since the installation is
- # spread about the filesystem. We choose the
- # directory containing the Makefile since we know it
- # exists.
- srcdir = os.path.dirname(get_makefile_filename())
- _CONFIG_VARS['srcdir'] = _safe_realpath(srcdir)
-
- # OS X platforms require special customization to handle
- # multi-architecture, multi-os-version installers
- if sys.platform == 'darwin':
- import _osx_support
- _osx_support.customize_config_vars(_CONFIG_VARS)
+
+ # Avoid claiming the lock once initialization is complete.
+ if not _CONFIG_VARS_INITIALIZED:
+ with _CONFIG_VARS_LOCK:
+ # Test again with the lock held to avoid races. Note that
+ # we test _CONFIG_VARS here, not _CONFIG_VARS_INITIALIZED,
+ # to ensure that recursive calls to get_config_vars()
+ # don't re-enter init_config_vars().
+ if _CONFIG_VARS is None:
+ _init_config_vars()
if args:
vals = []
diff --git a/Misc/NEWS.d/next/Library/2022-05-08-08-47-32.gh-issue-92452.3pNHe6.rst b/Misc/NEWS.d/next/Library/2022-05-08-08-47-32.gh-issue-92452.3pNHe6.rst
new file mode 100644
index 000000000000..25d6477aa913
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-05-08-08-47-32.gh-issue-92452.3pNHe6.rst
@@ -0,0 +1,2 @@
+Fixed a race condition that could cause :func:`sysconfig.get_config_var` to
+incorrectly return :const:`None` in multi-threaded programs.
More information about the Python-checkins
mailing list