diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py index 9188114ae284c7..6c5c15980b21cb 100644 --- a/Lib/multiprocessing/synchronize.py +++ b/Lib/multiprocessing/synchronize.py @@ -125,21 +125,79 @@ 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): + if not isinstance(self, Semaphore): + raise TypeError("_MacOSXSemaphore can only be used " + "as base class of Semaphore class") + self._count = ctx.Value('h', value) + SemLock.__init__(self, kind, value, maxvalue, ctx=ctx) + + def _acquire(self, *args, **kwargs) -> bool: + if self._semlock.acquire(*args, **kwargs): + with self._count: + self._count.value -= 1 + return True + return False + + def _release(self): + with self._count: + self._count.value += 1 + self._semlock.release() + + 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() + self.acquire = self._acquire + if isinstance(self, BoundedSemaphore): + self.release = self._release_bounded + elif isinstance(self, Semaphore): + self.release = self._release + 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): - '''Returns current value of Semaphore. - - Raises NotImplementedError on Mac OSX - because of broken sem_getvalue(). - ''' return self._semlock._get_value() def __repr__(self): @@ -156,7 +214,7 @@ 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: diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index cc07062eee6f98..67b6635c0c60bb 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -1698,10 +1698,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': diff --git a/Misc/NEWS.d/next/Library/2026-01-26-16-18-19.gh-issue-125828.W6BLH8.rst b/Misc/NEWS.d/next/Library/2026-01-26-16-18-19.gh-issue-125828.W6BLH8.rst new file mode 100644 index 00000000000000..764a7e608bc7f1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-26-16-18-19.gh-issue-125828.W6BLH8.rst @@ -0,0 +1,2 @@ +Fix the not implemented ``get_value`` for :class:`multiprocessing.Semaphore` on MacOSX +by adding a dedicated class in the ``synchronize.py`` file.