commit: a1024a6d02ca3a55f86525e0d8d5089e754d3713 Author: Zac Medico <zmedico <AT> gentoo <DOT> org> AuthorDate: Sun Feb 11 05:38:58 2024 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Sun Feb 11 19:46:55 2024 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=a1024a6d
spawn_wrapper: Make pre_exec function picklable Local functions are unpicklable, which triggered this error with the multiprocessing "spawn" start method: AttributeError: Can't pickle local object 'spawn_wrapper.__call__.<locals>._pre_exec' Bug: https://bugs.gentoo.org/924273 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> lib/portage/_selinux.py | 17 ++++++++--------- lib/portage/process.py | 26 ++++++++++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/portage/_selinux.py b/lib/portage/_selinux.py index bf6ad24895..5ae1b4e715 100644 --- a/lib/portage/_selinux.py +++ b/lib/portage/_selinux.py @@ -1,4 +1,4 @@ -# Copyright 1999-2020 Gentoo Authors +# Copyright 1999-2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # Don't use the unicode-wrapped os and shutil modules here since @@ -6,6 +6,7 @@ import os import shutil import warnings +from functools import partial try: import selinux @@ -134,14 +135,12 @@ class spawn_wrapper: def __call__(self, *args, **kwargs): if self._con is not None: - pre_exec = kwargs.get("pre_exec") - - def _pre_exec(): - if pre_exec is not None: - pre_exec() - setexec(self._con) - - kwargs["pre_exec"] = _pre_exec + pre_exec = partial(setexec, self._con) + kwargs["pre_exec"] = ( + portage.process._chain_pre_exec_fns(pre_exec, kwargs["pre_exec"]) + if kwargs.get("pre_exec") + else pre_exec + ) return self._spawn_func(*args, **kwargs) diff --git a/lib/portage/process.py b/lib/portage/process.py index a33e7b4747..6cad250e34 100644 --- a/lib/portage/process.py +++ b/lib/portage/process.py @@ -18,7 +18,7 @@ import os as _os import warnings from dataclasses import dataclass -from functools import lru_cache +from functools import lru_cache, partial from typing import Any, Optional, Callable, Union from portage import os @@ -1383,18 +1383,28 @@ def _start_fork( return pid -class _setup_pipes_after_fork: - def __init__(self, target, fd_pipes): +class _chain_pre_exec_fns: + """ + Wraps a target function to call pre_exec functions just before + the original target function. + """ + + def __init__(self, target, *args): self._target = target - self._fd_pipes = fd_pipes + self._pre_exec_fns = args def __call__(self, *args, **kwargs): - for fd in set(self._fd_pipes.values()): - os.set_inheritable(fd, True) - _setup_pipes(self._fd_pipes, close_fds=False, inheritable=True) + for pre_exec in self._pre_exec_fns: + pre_exec() return self._target(*args, **kwargs) +def _setup_pipes_after_fork(fd_pipes): + for fd in set(fd_pipes.values()): + os.set_inheritable(fd, True) + _setup_pipes(fd_pipes, close_fds=False, inheritable=True) + + def _start_proc( target: Callable[..., None], args: Optional[tuple[Any, ...]] = (), @@ -1419,7 +1429,7 @@ def _start_proc( # which ForkProcess does not handle because its target # function does not necessarily exec. if fd_pipes and multiprocessing.get_start_method() == "fork": - target = _setup_pipes_after_fork(target, fd_pipes) + target = _chain_pre_exec_fns(target, partial(_setup_pipes_after_fork, fd_pipes)) fd_pipes = None proc = ForkProcess(
