diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py index 30425047e9801a..5d1314ce6c141b 100644 --- a/Lib/multiprocessing/synchronize.py +++ b/Lib/multiprocessing/synchronize.py @@ -125,21 +125,89 @@ 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): + 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 __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) @@ -151,11 +219,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 6a20a1eb03e32b..b567606dfe1fef 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -1664,10 +1664,26 @@ def test_semaphore(self): 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) + 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':