From 4083a7ff5a44ac744d2bd2ede3a6332add1e9981 Mon Sep 17 00:00:00 2001 From: Duprat Date: Sun, 20 Oct 2024 22:10:37 +0200 Subject: [PATCH] Initail commit --- Lib/multiprocessing/synchronize.py | 87 ++++++++++++++++++++++++++++-- Lib/test/_test_multiprocessing.py | 26 +++++++-- 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py index 3ccbfe311c71f3..8aa55a289811cd 100644 --- a/Lib/multiprocessing/synchronize.py +++ b/Lib/multiprocessing/synchronize.py @@ -123,21 +123,98 @@ def _make_name(): return '%s-%s' % (process.current_process()._config['semprefix'], next(SemLock._rand)) +if sys.platform == 'darwin': + # + # Specific MacOSX Semaphore + # + + class _MacOSXSemaphore(SemLock): + """Dedicated class used only to workaround the missing + function 'sem_getvalue', when interpreter runs on MacOSX. + Add a shared counter for each [Bounded]Semaphore in order + to handle internal counter when acquire and release operations + are called. + """ + + def __init__(self, kind, value, maxvalue, *, ctx): + util.debug(f"_MacOSXSemaphore:: creation of a {self.__class__.__name__}"\ + f"with '{value = }'") + SemLock.__init__(self, kind, value, maxvalue, ctx=ctx) + self._count = ctx.Value('L', value) # May be more than 'L' ? + + def _acquire(self, *args, **kwargs) -> bool: + if self._semlock.acquire(*args, **kwargs): + with self._count: + util.debug(f"_MacOSXSemaphore: acquire {repr(self)}") + self._count.value -= 1 + return True + return False + + def _release(self): + with self._count: + self._count.value += 1 + self._semlock.release() + util.debug(f"_MacOSXSemaphore: release {repr(self)}") + + def _release_bounded(self): + with self._count: + if self._count.value + 1 > self._semlock.maxvalue: + raise ValueError(f"Cannot exceed initial value of"\ + f" {self._semlock.maxvalue!a}") + self._release() + + def _get_value(self) -> int: + return self._count.value + + def _make_methods(self): + super()._make_methods() + util.debug("_MacOSXSemaphore: _make_methods call") + self.acquire = self._acquire + if isinstance(self, BoundedSemaphore): + self.release = self._release_bounded + elif isinstance(self, Semaphore): + self.release = self._release + else: + raise RuntimeError("Class dedicated only to Semaphore or BoundedSemaphore OSX") + self.get_value = self._get_value + + def __enter__(self): + util.debug(f'_MacOSXSemaphore: enter {repr(self)}') + return self.acquire() + + def __exit__(self, *args): + util.debug(f'_MacOSXSemaphore: exit {repr(self)}') + return self.release() + + def __setstate__(self, state): + self._count, state = state[-1], state[:-1] + super().__setstate__(state) + + def __getstate__(self) -> tuple: + return super().__getstate__() + (self._count,) + + + _SemClass = _MacOSXSemaphore +else: + _SemClass = SemLock + # # Semaphore # -class Semaphore(SemLock): +class Semaphore(_SemClass): def __init__(self, value=1, *, ctx): - SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx) + _SemClass.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx) def get_value(self): + """redefined when MacOSX. + """ return self._semlock._get_value() def __repr__(self): try: - value = self._semlock._get_value() + value = self.get_value() except Exception: value = 'unknown' return '<%s(value=%s)>' % (self.__class__.__name__, value) @@ -149,11 +226,11 @@ def __repr__(self): class BoundedSemaphore(Semaphore): def __init__(self, value=1, *, ctx): - SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx) + _SemClass.__init__(self, SEMAPHORE, value, value, ctx=ctx) def __repr__(self): try: - value = self._semlock._get_value() + value = self.get_value() except Exception: value = 'unknown' return '<%s(value=%s, maxvalue=%s)>' % \ diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 065fc27b770438..b73aaff5509d8e 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -1411,9 +1411,27 @@ def test_bounded_semaphore(self): sem = self.BoundedSemaphore(2) self._test_semaphore(sem) # Currently fails on OS/X - #if HAVE_GETVALUE: - # self.assertRaises(ValueError, sem.release) - # self.assertReturnsIfImplemented(2, get_value, sem) + # if HAVE_GETVALUE: + self.assertRaises(ValueError, sem.release) + self.assertReturnsIfImplemented(2, get_value, sem) + + @unittest.skipIf(sys.platform != 'darwin', 'Darwin only') + def test_detect_macosx_semaphore(self): + if self.TYPE != 'processes': + self.skipTest('test not appropriate for {}'.format(self.TYPE)) + + sem = self.Semaphore(2) + mro = sem.__class__.mro() + self.assertTrue(any('_MacOSXSemaphore' in cls.__name__ for cls in mro)) + + @unittest.skipIf(sys.platform != 'darwin', 'Darwin only') + def test_detect_macosx_boundedsemaphore(self): + if self.TYPE != 'processes': + self.skipTest('test not appropriate for {}'.format(self.TYPE)) + + sem = self.BoundedSemaphore(2) + mro = sem.__class__.mro() + self.assertTrue(any('_MacOSXSemaphore' in cls.__name__ for cls in mro)) def test_timeout(self): if self.TYPE != 'processes': @@ -5761,8 +5779,6 @@ def test_resource_tracker_sigterm(self): # Catchable signal (ignored by semaphore tracker) self.check_resource_tracker_death(signal.SIGTERM, False) - @unittest.skipIf(sys.platform.startswith("netbsd"), - "gh-125620: Skip on NetBSD due to long wait for SIGKILL process termination.") def test_resource_tracker_sigkill(self): # Uncatchable signal. self.check_resource_tracker_death(signal.SIGKILL, True)