From 5d2997e7329ffa53b8d6ea52d887562912ee82b2 Mon Sep 17 00:00:00 2001 From: Duprat Date: Sun, 20 Oct 2024 22:10:37 +0200 Subject: [PATCH 1/3] Initial commit --- Lib/multiprocessing/synchronize.py | 78 ++++++++++++++++++++++++++++-- Lib/test/_test_multiprocessing.py | 24 +++++++-- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py index 3ccbfe311c71f3..6831e87ea8e576 100644 --- a/Lib/multiprocessing/synchronize.py +++ b/Lib/multiprocessing/synchronize.py @@ -123,21 +123,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) @@ -149,11 +217,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..a95ff64163222b 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -1410,10 +1410,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': From 6e2c56cef968cd0e83025bac423caa2bb5293856 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:18:30 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-01-26-16-18-19.gh-issue-125828.W6BLH8.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-01-26-16-18-19.gh-issue-125828.W6BLH8.rst 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. From 5472ce2a6974293cd442c7e3e1e25586c9bd0c0e Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" Date: Mon, 26 Jan 2026 17:13:54 +0100 Subject: [PATCH 3/3] Refactor code --- Lib/multiprocessing/synchronize.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py index aca46cd0a65206..6c5c15980b21cb 100644 --- a/Lib/multiprocessing/synchronize.py +++ b/Lib/multiprocessing/synchronize.py @@ -139,15 +139,15 @@ class _MacOSXSemaphore(SemLock): """ def __init__(self, kind, value, maxvalue, *, ctx): - util.debug(f"_MacOSXSemaphore:: creation of a {self.__class__.__name__}"\ - f"with '{value = }'") + 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) - 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 @@ -156,27 +156,24 @@ 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() + 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 __setstate__(self, state):