Fix infinite recursion in import hook and stdout contamination in apply.sh
sitecustomize.py: exec_module was calling importlib.util.find_spec(fullname) after Python had already added the module to sys.modules. find_spec short-circuits to sys.modules[name].__spec__, which is our own wrapper spec, so real_spec.loader.exec_module(module) immediately called exec_module again — infinite recursion. Fix: resolve the real file spec in find_spec (before sys.modules is populated) and store it on the Loader. exec_module now uses the stored spec directly with no find_spec call. Also move _mark_done into the finally block so a module that fails to load doesn't trigger infinite retry loops on subsequent import attempts. apply.sh: warn() writes to stdout. Inside $(find_mw_python), stdout is captured by the command substitution, so any warn() call folded its text into $PYTHON — causing every subsequent "$PYTHON" invocation to fail. Fix: emit the fallback warning to stderr (>&2) so it goes to the log without being captured.
This commit is contained in:
+2
-1
@@ -53,8 +53,9 @@ find_mw_python() {
|
||||
fi
|
||||
|
||||
# Verify the chosen interpreter can actually import middlewared.
|
||||
# Use >&2 so this message goes to stderr, not captured by $(...) substitution.
|
||||
if ! "$py" -c "import middlewared" 2>/dev/null; then
|
||||
warn "Detected Python '$py' cannot import middlewared; falling back to python3"
|
||||
echo "WARNING: '$py' cannot import middlewared; falling back to python3" >&2
|
||||
py="python3"
|
||||
fi
|
||||
|
||||
|
||||
+30
-16
@@ -42,12 +42,32 @@ class _Finder:
|
||||
|
||||
def find_spec(self, fullname, path, target=None): # noqa: ARG002
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
|
||||
if (
|
||||
fullname in self._targets
|
||||
and fullname not in self._done
|
||||
and fullname not in self._loading
|
||||
):
|
||||
return importlib.machinery.ModuleSpec(fullname, _Loader(self, fullname))
|
||||
# Find the real file spec HERE, before Python adds the module to
|
||||
# sys.modules. If we deferred this to exec_module, find_spec would
|
||||
# short-circuit via sys.modules[fullname].__spec__ (our own spec) and
|
||||
# exec_module would call itself recursively forever.
|
||||
self._loading.add(fullname)
|
||||
try:
|
||||
real_spec = importlib.util.find_spec(fullname)
|
||||
finally:
|
||||
self._loading.discard(fullname)
|
||||
|
||||
if real_spec is None:
|
||||
return None # module doesn't exist; don't intercept
|
||||
|
||||
return importlib.machinery.ModuleSpec(
|
||||
fullname,
|
||||
_Loader(self, fullname, real_spec),
|
||||
origin=real_spec.origin,
|
||||
is_package=real_spec.submodule_search_locations is not None,
|
||||
)
|
||||
return None
|
||||
|
||||
def _mark_done(self, fullname):
|
||||
@@ -60,37 +80,31 @@ class _Finder:
|
||||
|
||||
|
||||
class _Loader:
|
||||
def __init__(self, finder, fullname):
|
||||
def __init__(self, finder, fullname, real_spec):
|
||||
self._finder = finder
|
||||
self._fullname = fullname
|
||||
self._real_spec = real_spec
|
||||
|
||||
def create_module(self, spec): # noqa: ARG002
|
||||
return None # use Python's default module creation
|
||||
|
||||
def exec_module(self, module):
|
||||
import importlib.util
|
||||
|
||||
fullname = self._fullname
|
||||
self._finder._loading.add(fullname)
|
||||
real_spec = self._real_spec
|
||||
|
||||
try:
|
||||
# find_spec for the real file — our finder returns None while
|
||||
# fullname is in _loading, so the normal finders handle this.
|
||||
real_spec = importlib.util.find_spec(fullname)
|
||||
if real_spec is None:
|
||||
raise ImportError(f"No module named {fullname!r}")
|
||||
|
||||
real_spec.loader.exec_module(module)
|
||||
|
||||
# Fix module metadata so it looks like a normal import.
|
||||
module.__spec__ = real_spec
|
||||
module.__loader__ = real_spec.loader
|
||||
if getattr(real_spec, "origin", None):
|
||||
if real_spec.origin:
|
||||
module.__file__ = real_spec.origin
|
||||
finally:
|
||||
self._finder._loading.discard(fullname)
|
||||
|
||||
self._finder._mark_done(fullname)
|
||||
# Mark done even on failure so a broken module doesn't cause
|
||||
# infinite retry loops on subsequent import attempts.
|
||||
self._finder._mark_done(fullname)
|
||||
|
||||
# exec_module succeeded — apply our patch.
|
||||
try:
|
||||
_PATCHES[fullname](module)
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user