diff --git a/setup.py b/setup.py index 32d94ae8..c4f80595 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,7 @@ def finalize_options(self): need_cythonize = True if need_cythonize: - import pkg_resources + from packaging.requirements import Requirement # Double check Cython presence in case setup_requires # didn't go into effect (most likely because someone @@ -121,8 +121,8 @@ def finalize_options(self): 'please install {} to compile uvloop from source'.format( CYTHON_DEPENDENCY)) - cython_dep = pkg_resources.Requirement.parse(CYTHON_DEPENDENCY) - if Cython.__version__ not in cython_dep: + cython_req = Requirement(CYTHON_DEPENDENCY) + if not cython_req.specifier.contains(Cython.__version__): raise RuntimeError( 'uvloop requires {}, got Cython=={}'.format( CYTHON_DEPENDENCY, Cython.__version__ diff --git a/uvloop/handles/process.pyx b/uvloop/handles/process.pyx index 63b982ae..867439e5 100644 --- a/uvloop/handles/process.pyx +++ b/uvloop/handles/process.pyx @@ -68,21 +68,28 @@ cdef class UVProcess(UVHandle): self._abort_init() raise - if __forking or loop.active_process_handler is not None: - # Our pthread_atfork handlers won't work correctly when - # another loop is forking in another thread (even though - # GIL should help us to avoid that.) + # Acquire the global spawn lock to serialize process spawning + # across threads. Without this, concurrent spawns from different + # loops would race on the global pthread_atfork handlers. + __spawn_lock.acquire() + _spawn_lock_held = True + + if loop.active_process_handler is not None: + __spawn_lock.release() + _spawn_lock_held = False self._abort_init() raise RuntimeError( - 'Racing with another loop to spawn a process.') - - self._errpipe_read, self._errpipe_write = os_pipe() - fds_to_close = self._fds_to_close - self._fds_to_close = None - fds_to_close.append(self._errpipe_read) - # add the write pipe last so we can close it early - fds_to_close.append(self._errpipe_write) + 'Racing with the same loop to spawn a process.') + + fds_to_close = None try: + self._errpipe_read, self._errpipe_write = os_pipe() + fds_to_close = self._fds_to_close + self._fds_to_close = None + fds_to_close.append(self._errpipe_read) + # add the write pipe last so we can close it early + fds_to_close.append(self._errpipe_write) + os_set_inheritable(self._errpipe_write, True) self._preexec_fn = preexec_fn @@ -103,6 +110,8 @@ cdef class UVProcess(UVHandle): __forking_loop = None system.resetForkHandler() loop.active_process_handler = None + __spawn_lock.release() + _spawn_lock_held = False PyOS_AfterFork_Parent() @@ -128,8 +137,16 @@ cdef class UVProcess(UVHandle): break finally: - while fds_to_close: - os_close(fds_to_close.pop()) + if _spawn_lock_held: + __forking = 0 + __forking_loop = None + system.resetForkHandler() + loop.active_process_handler = None + __spawn_lock.release() + + if fds_to_close is not None: + while fds_to_close: + os_close(fds_to_close.pop()) for fd in restore_inheritable: os_set_inheritable(fd, False) diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index 577d45a4..00055cdc 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -41,6 +41,8 @@ from cpython cimport ( from cpython.pycapsule cimport PyCapsule_New, PyCapsule_GetPointer from . import _noop +import threading +__spawn_lock = threading.Lock() include "includes/stdlib.pxi"