commit:     8b96ca57e15bbc989f39ec4973fc3c9492fcc059
Author:     Florian Schmaus <flow <AT> gentoo <DOT> org>
AuthorDate: Thu Aug 28 09:41:59 2025 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Fri Aug 29 12:19:50 2025 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=8b96ca57

process: do not poll join() in MultiprocessingProcesss

Based on the initial analysis of Esteve Varela Colominas, the polling
on join() with the fixed 100ms delay incurs a significant performance
penalty, especially for short-lived processes. And since portage is
prone to spawning many of those, the penalty adds up easily.

Instead of pooling proc.join() with a fixed 100ms delay, we now use
the blocking variant of join() started in an executor which we await
in the coroutine.

Before this change:

( cd lib; python3 -m timeit 'import portage.process; 
portage.process.spawn("true")' )
2 loops, best of 5: 104 msec per loop

After this change:

( cd lib; python3 -m timeit 'import portage.process; 
portage.process.spawn("true")' )
50 loops, best of 5: 4.45 msec per loop

Bug: https://bugs.gentoo.org/958635
Signed-off-by: Florian Schmaus <flow <AT> gentoo.org>
Part-of: https://github.com/gentoo/portage/pull/1452
Closes: https://github.com/gentoo/portage/pull/1452
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/portage/process.py | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/lib/portage/process.py b/lib/portage/process.py
index 3f2c48e927..502f49f81f 100644
--- a/lib/portage/process.py
+++ b/lib/portage/process.py
@@ -426,10 +426,6 @@ class MultiprocessingProcess(AbstractProcess):
     An object that wraps OS processes created by multiprocessing.Process.
     """
 
-    # Number of seconds between poll attempts for process exit status
-    # (after the sentinel has become ready).
-    _proc_join_interval = 0.1
-
     def __init__(self, proc: multiprocessing.Process):
         self._proc = proc
         self.pid = proc.pid
@@ -480,13 +476,7 @@ class MultiprocessingProcess(AbstractProcess):
             except ValueError:
                 pass
 
-        # Now that proc.sentinel is ready, poll until process exit
-        # status has become available.
-        while True:
-            proc.join(0)
-            if proc.exitcode is not None:
-                break
-            await asyncio.sleep(self._proc_join_interval, loop=loop)
+        await loop.run_in_executor(None, proc.join)
 
     def _proc_join_done(self, future):
         # The join task should never be cancelled, so let it raise

Reply via email to