diff options
author | Zac Medico <zmedico@gentoo.org> | 2023-10-03 07:59:17 -0700 |
---|---|---|
committer | Zac Medico <zmedico@gentoo.org> | 2023-10-03 07:59:17 -0700 |
commit | 7622da95fd97c07d7a51f14dfcfb07048db687c1 (patch) | |
tree | 41665974d7eb5389e90d0c4d0e2765ef1ab5fc57 | |
parent | AsyncFunction: Migrate to ForkProcess target parameter (diff) | |
download | portage-7622da95fd97c07d7a51f14dfcfb07048db687c1.tar.gz portage-7622da95fd97c07d7a51f14dfcfb07048db687c1.tar.bz2 portage-7622da95fd97c07d7a51f14dfcfb07048db687c1.zip |
_get_lock_fn: support multiprocessing spawn start method (bug 915119)
Ensure that _get_lock_fn arguments to multiprocessing.Process will
successfully pickle, as required by the spawn start method, which
is the default for macOS since Python 3.8.
Since file descriptors are not inherited unless the fork start
method is used, the subprocess should only try to close an
inherited file descriptor for the fork start method.
Bug: https://bugs.gentoo.org/915119
Signed-off-by: Zac Medico <zmedico@gentoo.org>
-rw-r--r-- | lib/portage/locks.py | 50 |
1 files changed, 32 insertions, 18 deletions
diff --git a/lib/portage/locks.py b/lib/portage/locks.py index 1c3e13ce4..ee40451b1 100644 --- a/lib/portage/locks.py +++ b/lib/portage/locks.py @@ -79,6 +79,11 @@ class _lock_manager: del _open_inodes[self.inode_key] +def _lockf_test_lock_fn(path, fd, flags): + fcntl.lockf(fd, flags) + return functools.partial(unlockfile, (path, fd, flags, fcntl.lockf)) + + def _get_lock_fn(): """ Returns fcntl.lockf if proven to work, and otherwise returns fcntl.flock. @@ -88,10 +93,7 @@ def _get_lock_fn(): if _lock_fn is not None: return _lock_fn - if _test_lock_fn( - lambda path, fd, flags: fcntl.lockf(fd, flags) - and functools.partial(unlockfile, (path, fd, flags, fcntl.lockf)) - ): + if _test_lock_fn(_lockf_test_lock_fn): _lock_fn = fcntl.lockf return _lock_fn @@ -103,19 +105,6 @@ def _get_lock_fn(): def _test_lock_fn( lock_fn: typing.Callable[[str, int, int], typing.Callable[[], None]] ) -> bool: - def _test_lock(fd, lock_path): - os.close(fd) - try: - with open(lock_path, "a") as f: - lock_fn(lock_path, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) - except (TryAgain, OSError) as e: - if isinstance(e, TryAgain) or e.errno == errno.EAGAIN: - # Parent process holds lock, as expected. - sys.exit(0) - - # Something went wrong. - sys.exit(1) - fd, lock_path = tempfile.mkstemp() unlock_fn = None try: @@ -125,7 +114,17 @@ def _test_lock_fn( pass else: _lock_manager(fd, os.fstat(fd), lock_path) - proc = multiprocessing.Process(target=_test_lock, args=(fd, lock_path)) + proc = multiprocessing.Process( + target=_subprocess_test_lock, + args=( + # Since file descriptors are not inherited unless the fork start + # method is used, the subprocess should only try to close an + # inherited file descriptor for the fork start method. + fd if multiprocessing.get_start_method() == "fork" else None, + lock_fn, + lock_path, + ), + ) proc.start() proc.join() if proc.exitcode == os.EX_OK: @@ -141,6 +140,21 @@ def _test_lock_fn( return False +def _subprocess_test_lock(fd, lock_fn, lock_path): + if fd is not None: + os.close(fd) + try: + with open(lock_path, "a") as f: + lock_fn(lock_path, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + except (TryAgain, OSError) as e: + if isinstance(e, TryAgain) or e.errno == errno.EAGAIN: + # Parent process holds lock, as expected. + sys.exit(0) + + # Something went wrong. + sys.exit(1) + + def _close_fds(): """ This is intended to be called after a fork, in order to close file |