diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py index 9188114ae284c7..d445138fa9234f 100644 --- a/Lib/multiprocessing/synchronize.py +++ b/Lib/multiprocessing/synchronize.py @@ -125,22 +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 two shared counters for each [Bounded]Semaphore in order + to calculate the internal value of the semaphore, + 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._rlock = ctx.RLock() + self._count = ctx.Value('h', value, lock=self._rlock) + self._pending_acquires = ctx.Value('h', 0, lock=self._rlock) + super().__init__(kind, value, maxvalue, ctx=ctx) + + def acquire(self, blocking=True, timeout=None): + with self._rlock: + self._pending_acquires.value += 1 + if self._semlock.acquire(blocking, timeout): + with self._rlock: + self._pending_acquires.value -= 1 + self._count.value -= 1 + return True + with self._rlock: + self._pending_acquires.value -= 1 + return False + + def release(self): + if isinstance(self, BoundedSemaphore): + with self._rlock: + if self.get_value() + 1 > self._semlock.maxvalue: + raise ValueError(f"semaphore released too many times") + with self._rlock: + self._count.value += 1 + self._semlock.release() + + def get_value(self): + with self._rlock: + val = self._count.value - self._pending_acquires.value + return val if val > 0 else 0 + + def _make_methods(self): + # Do not call the `Semlock._make_methods` method, + # because that breaks the reference to the local + # `acquire` and `release` methods. + pass + + def __setstate__(self, state): + self._count, self._pending_acquires, self._rlock, state \ + = state[-3], state[-2], state[-1], state[:-3] + super().__setstate__(state) + + def __getstate__(self) -> tuple: + return super().__getstate__() \ + + (self._count, self._pending_acquires, self._rlock) + + + _SemClass = _MacOSXSemaphore +else: + class _NotMacOSXSemaphore(SemLock): + def __init__(self, kind, value, maxvalue, *, ctx): + super().__init__(kind, value, maxvalue, ctx=ctx) + + def get_value(self) -> int: + return self._semlock._get_value() + + _SemClass = _NotMacOSXSemaphore + # # Semaphore # -class Semaphore(SemLock): +class Semaphore(_SemClass): def __init__(self, value=1, *, ctx): - SemLock.__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() + _SemClass.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx) def __repr__(self): try: @@ -156,7 +223,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 580d9f2b32544e..d8e4a58090ce89 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -1750,10 +1750,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..ef02f1b55a9074 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-26-16-18-19.gh-issue-125828.W6BLH8.rst @@ -0,0 +1 @@ +Fix the unimplemented ``get_value`` method of the ``_multiprocessing.SemLock`` class on MacOSX by adding a specific inherited class between ``multiprocessing.SemLock`` and :class:`multiprocessing.Semaphore`. diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index 1bcf98a7600851..0f07d03b05a329 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -13,10 +13,10 @@ unsigned char M_test_frozenmain[] = { 92,2,31,0,81,4,92,6,12,0,81,5,92,5,92,6, 42,26,0,0,0,0,0,0,0,0,0,0,12,0,48,4, 50,1,0,0,0,0,0,0,29,0,74,26,0,0,9,0, - 28,0,80,7,33,0,41,7,233,0,0,0,0,122,18,70, + 28,0,80,7,33,0,41,7,233,0,0,0,0,218,18,70, 114,111,122,101,110,32,72,101,108,108,111,32,87,111,114,108, - 100,122,8,115,121,115,46,97,114,103,118,218,6,99,111,110, - 102,105,103,122,7,99,111,110,102,105,103,32,122,2,58,32, + 100,218,8,115,121,115,46,97,114,103,118,218,6,99,111,110, + 102,105,103,218,7,99,111,110,102,105,103,32,218,2,58,32, 41,5,218,12,112,114,111,103,114,97,109,95,110,97,109,101, 218,10,101,120,101,99,117,116,97,98,108,101,218,15,117,115, 101,95,101,110,118,105,114,111,110,109,101,110,116,218,17,99, @@ -25,15 +25,15 @@ unsigned char M_test_frozenmain[] = { 41,7,218,3,115,121,115,218,17,95,116,101,115,116,105,110, 116,101,114,110,97,108,99,97,112,105,218,5,112,114,105,110, 116,218,4,97,114,103,118,218,11,103,101,116,95,99,111,110, - 102,105,103,115,114,3,0,0,0,218,3,107,101,121,169,0, + 102,105,103,115,114,5,0,0,0,218,3,107,101,121,169,0, 243,0,0,0,0,218,18,116,101,115,116,95,102,114,111,122, 101,110,109,97,105,110,46,112,121,218,8,60,109,111,100,117, - 108,101,62,114,18,0,0,0,1,0,0,0,115,94,0,0, + 108,101,62,114,22,0,0,0,1,0,0,0,115,94,0,0, 0,241,3,1,1,1,243,8,0,1,11,219,0,24,225,0, 5,208,6,26,212,0,27,217,0,5,128,106,144,35,151,40, 145,40,212,0,27,216,9,26,215,9,38,210,9,38,211,9, 40,168,24,213,9,50,128,6,244,2,6,12,2,128,67,241, 14,0,5,10,136,71,144,67,144,53,152,2,152,54,160,35, 157,59,152,45,208,10,40,214,4,41,243,15,6,12,2,114, - 16,0,0,0, + 20,0,0,0, };