Skip to content

Commit 5d2997e

Browse files
committed
Initial commit
1 parent 4b421e8 commit 5d2997e

2 files changed

Lines changed: 93 additions & 9 deletions

File tree

Lib/multiprocessing/synchronize.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,89 @@ def _make_name():
123123
return '%s-%s' % (process.current_process()._config['semprefix'],
124124
next(SemLock._rand))
125125

126+
if sys.platform == 'darwin':
127+
#
128+
# Specific MacOSX Semaphore
129+
#
130+
131+
class _MacOSXSemaphore(SemLock):
132+
"""Dedicated class used only to workaround the missing
133+
function 'sem_getvalue', when interpreter runs on MacOSX.
134+
Add a shared counter for each [Bounded]Semaphore in order
135+
to handle internal counter when acquire and release operations
136+
are called.
137+
"""
138+
139+
def __init__(self, kind, value, maxvalue, *, ctx):
140+
util.debug(f"_MacOSXSemaphore:: creation of a {self.__class__.__name__}"\
141+
f"with '{value = }'")
142+
SemLock.__init__(self, kind, value, maxvalue, ctx=ctx)
143+
self._count = ctx.Value('L', value) # May be more than 'L' ?
144+
145+
def _acquire(self, *args, **kwargs) -> bool:
146+
if self._semlock.acquire(*args, **kwargs):
147+
with self._count:
148+
util.debug(f"_MacOSXSemaphore: acquire {repr(self)}")
149+
self._count.value -= 1
150+
return True
151+
return False
152+
153+
def _release(self):
154+
with self._count:
155+
self._count.value += 1
156+
self._semlock.release()
157+
util.debug(f"_MacOSXSemaphore: release {repr(self)}")
158+
159+
def _release_bounded(self):
160+
if self._count.value + 1 > self._semlock.maxvalue:
161+
raise ValueError(f"Cannot exceed initial value of"\
162+
f" {self._semlock.maxvalue!a}")
163+
self._release()
164+
165+
def _get_value(self) -> int:
166+
return self._count.value
167+
168+
def _make_methods(self):
169+
super()._make_methods()
170+
util.debug("_MacOSXSemaphore: _make_methods call")
171+
self.acquire = self._acquire
172+
if isinstance(self, BoundedSemaphore):
173+
self.release = self._release_bounded
174+
elif isinstance(self, Semaphore):
175+
self.release = self._release
176+
else:
177+
raise RuntimeError("Class dedicated only to Semaphore or BoundedSemaphore OSX")
178+
self.get_value = self._get_value
179+
180+
def __setstate__(self, state):
181+
self._count, state = state[-1], state[:-1]
182+
super().__setstate__(state)
183+
184+
def __getstate__(self) -> tuple:
185+
return super().__getstate__() + (self._count,)
186+
187+
188+
_SemClass = _MacOSXSemaphore
189+
else:
190+
_SemClass = SemLock
191+
126192
#
127193
# Semaphore
128194
#
129195

130-
class Semaphore(SemLock):
196+
class Semaphore(_SemClass):
131197

132198
def __init__(self, value=1, *, ctx):
133-
SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)
199+
_SemClass.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)
134200

135201
def get_value(self):
202+
"""redefined when MacOSX.
203+
"""
136204
return self._semlock._get_value()
137205

138206
def __repr__(self):
139207
try:
140-
value = self._semlock._get_value()
208+
value = self.get_value()
141209
except Exception:
142210
value = 'unknown'
143211
return '<%s(value=%s)>' % (self.__class__.__name__, value)
@@ -149,11 +217,11 @@ def __repr__(self):
149217
class BoundedSemaphore(Semaphore):
150218

151219
def __init__(self, value=1, *, ctx):
152-
SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)
220+
_SemClass.__init__(self, SEMAPHORE, value, value, ctx=ctx)
153221

154222
def __repr__(self):
155223
try:
156-
value = self._semlock._get_value()
224+
value = self.get_value()
157225
except Exception:
158226
value = 'unknown'
159227
return '<%s(value=%s, maxvalue=%s)>' % \

Lib/test/_test_multiprocessing.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,10 +1410,26 @@ def test_semaphore(self):
14101410
def test_bounded_semaphore(self):
14111411
sem = self.BoundedSemaphore(2)
14121412
self._test_semaphore(sem)
1413-
# Currently fails on OS/X
1414-
#if HAVE_GETVALUE:
1415-
# self.assertRaises(ValueError, sem.release)
1416-
# self.assertReturnsIfImplemented(2, get_value, sem)
1413+
self.assertRaises(ValueError, sem.release)
1414+
self.assertReturnsIfImplemented(2, get_value, sem)
1415+
1416+
@unittest.skipIf(sys.platform != 'darwin', 'Darwin only')
1417+
def test_detect_macosx_semaphore(self):
1418+
if self.TYPE != 'processes':
1419+
self.skipTest('test not appropriate for {}'.format(self.TYPE))
1420+
1421+
sem = self.Semaphore(2)
1422+
mro = sem.__class__.mro()
1423+
self.assertTrue(any('_MacOSXSemaphore' in cls.__name__ for cls in mro))
1424+
1425+
@unittest.skipIf(sys.platform != 'darwin', 'Darwin only')
1426+
def test_detect_macosx_boundedsemaphore(self):
1427+
if self.TYPE != 'processes':
1428+
self.skipTest('test not appropriate for {}'.format(self.TYPE))
1429+
1430+
sem = self.BoundedSemaphore(2)
1431+
mro = sem.__class__.mro()
1432+
self.assertTrue(any('_MacOSXSemaphore' in cls.__name__ for cls in mro))
14171433

14181434
def test_timeout(self):
14191435
if self.TYPE != 'processes':

0 commit comments

Comments
 (0)