added coros.Semaphore and coros.BoundedSemaphore; made coros.semaphore a factory function; this fixes semaphore(0, 0) deadlock

This commit is contained in:
Denis Bilenko
2008-12-24 13:38:00 +06:00
parent 63bcdc5668
commit 961419045b

View File

@@ -530,104 +530,147 @@ class multievent(object):
return len(self.items) > 0
class semaphore(object):
"""Classic semaphore implemented with a counter and an event.
class Semaphore(object):
"""An unbounded semaphore.
Optionally initialize with a resource count, then acquire() and release()
resources as needed. Attempting to acquire() when count is zero suspends
the calling coroutine until count becomes nonzero again.
>>> from eventlet import coros, api
>>> sem = coros.semaphore(2, limit=3)
>>> sem.acquire()
True
>>> sem.acquire()
True
>>> def releaser(sem):
... print "releasing one"
... sem.release()
...
>>> _ = api.spawn(releaser, sem)
>>> sem.acquire()
releasing one
True
>>> sem.counter
0
>>> for x in xrange(3):
... sem.release()
...
>>> def acquirer(sem):
... print "acquiring one"
... sem.acquire()
...
>>> _ = api.spawn(acquirer, sem)
>>> sem.release()
acquiring one
>>> sem.counter
3
"""
def __init__(self, count=0, limit=None):
if limit is not None and count > limit:
# Prevent initializing with inconsistent values
count = limit
def __init__(self, count=0):
self.counter = count
self.limit = limit
self.acqevent = event()
self.relevent = event()
if self.counter > 0:
# If we initially have items, then don't block acquire()s.
self.acqevent.send()
if self.limit is None or self.counter < self.limit:
# If either there's no limit or we're below it, don't block on
# release()s.
self.relevent.send()
self._waiters = {}
def __str__(self):
params = (self.__class__.__name__, hex(id(self)), self.counter, self.limit, self.acqevent, self.relevent)
return '<%s at %s %r/%r acq=%s rel=%s>' % params
params = (self.__class__.__name__, hex(id(self)), self.counter)
return '<%s at %s counter=%r>' % params
def locked(self):
return self.counter <= 0
def acquire(self, blocking=1):
if blocking==0 and self.locked():
def bounded(self):
# for consistency with BoundedSemaphore
return False
# This logic handles the self.limit is None case because None != any integer.
while self.counter == 0:
# Loop until there are resources to acquire. We loop because we
# could be one of several coroutines waiting for a single item. If
# we all get notified, only one is going to claim it, and the rest
# of us must continue waiting.
self.acqevent.wait()
# claim the resource
def acquire(self, blocking=True):
if not blocking and self.locked():
return False
if self.counter<=0:
self._waiters[api.getcurrent()] = None
try:
api.get_hub().switch()
finally:
self._waiters.pop(api.getcurrent(), None)
self.counter -= 1
if self.counter == 0:
# If we just transitioned from having a resource to having none,
# make anyone else's wait() actually wait.
self.acqevent.reset()
if self.counter + 1 == self.limit:
# If we just transitioned from being full to having room for one
# more resource, notify whoever was waiting to release one.
self.relevent.send()
return True
__enter__ = acquire
def release(self):
# This logic handles the self.limit is None case because None != any integer.
while self.counter == self.limit:
self.relevent.wait()
def release(self, blocking=True):
# `blocking' parameter is for consistency with BoundedSemaphore and is ignored
self.counter += 1
if self.counter == self.limit:
self.relevent.reset()
if self.counter == 1:
# If self.counter was 0 before we incremented it, then wake up
# anybody who was waiting
self.acqevent.send()
if self._waiters:
api.get_hub().schedule_call_global(0, self._do_acquire)
return True
def _do_acquire(self):
if self._waiters and self.counter>0:
waiter, _unused = self._waiters.popitem()
waiter.switch()
def __exit__(self, typ, val, tb):
self.release()
class BoundedSemaphore(object):
"""A bounded semaphore.
Optionally initialize with a resource count, then acquire() and release()
resources as needed. Attempting to acquire() when count is zero suspends
the calling coroutine until count becomes nonzero again. Attempting to
release() after count has reached limit suspends the calling coroutine until
count becomes less than limit again.
"""
def __init__(self, count, limit):
if count > limit:
# accidentally, this also catches the case when limit is None
raise ValueError("'count' cannot be more than 'limit'")
self.counter = count
self.limit = limit
self._acquire_waiters = {}
self._release_waiters = {}
def __str__(self):
params = (self.__class__.__name__, hex(id(self)), self.counter, self.limit)
return '<%s at %s %r/%r>' % params
def locked(self):
return self.counter <= 0
def bounded(self):
return self.counter >= self.limit
def acquire(self, blocking=True):
if not blocking and self.locked():
return False
if self.counter<=0:
if self._release_waiters:
api.get_hub().schedule_call(0, self._do_unlock)
self._acquire_waiters[api.getcurrent()] = None
try:
api.get_hub().switch()
finally:
self._acquire_waiters.pop(api.getcurrent(), None)
self.counter -= 1
if self._release_waiters and self.counter < self.limit:
api.get_hub().schedule_call(0, self._do_release)
return True
__enter__ = acquire
def _do_unlock(self):
if self._release_waiters and self._acquire_waiters:
api.get_hub().schedule_call(0, self._do_acquire)
waiter, _unused = self._release_waiters.popitem()
waiter.switch()
def _do_release(self):
if self._release_waiters and self.counter<self.limit:
waiter, _unused = self._release_waiters.popitem()
waiter.switch()
def _do_acquire(self):
if self._acquire_waiters and self.counter>0:
waiter, _unused = self._acquire_waiters.popitem()
waiter.switch()
def release(self, blocking=True):
if not blocking and self.bounded():
return False
if self.counter>=self.limit:
if self._acquire_waiters:
api.get_hub().schedule_call(0, self._do_unlock)
self._release_waiters[api.getcurrent()] = None
try:
api.get_hub().switch()
finally:
self._release_waiters.pop(api.getcurrent(), None)
self.counter += 1
if self._acquire_waiters and self.counter > 0:
api.get_hub().schedule_call(0, self._do_acquire)
return True
def __exit__(self, typ, val, tb):
self.release()
def semaphore(count=0, limit=None):
if limit is None:
return Semaphore(count)
else:
return BoundedSemaphore(count, limit)
class metaphore(object):
"""This is sort of an inverse semaphore: a counter that starts at 0 and
waits only if nonzero. It's used to implement a "wait for all" scenario.