aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/_semaphore.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/gevent/_semaphore.py')
-rw-r--r--python/gevent/_semaphore.py303
1 files changed, 0 insertions, 303 deletions
diff --git a/python/gevent/_semaphore.py b/python/gevent/_semaphore.py
deleted file mode 100644
index d7c67ce..0000000
--- a/python/gevent/_semaphore.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
-from __future__ import print_function, absolute_import, division
-import sys
-
-from gevent.timeout import Timeout
-
-
-__all__ = [
- 'Semaphore',
- 'BoundedSemaphore',
-]
-
-# In Cython, we define these as 'cdef [inline]' functions. The
-# compilation unit cannot have a direct assignment to them (import
-# is assignment) without generating a 'lvalue is not valid target'
-# error.
-locals()['getcurrent'] = __import__('greenlet').getcurrent
-locals()['greenlet_init'] = lambda: None
-locals()['get_hub'] = __import__('gevent').get_hub
-
-class Semaphore(object):
- """
- Semaphore(value=1) -> Semaphore
-
- A semaphore manages a counter representing the number of release()
- calls minus the number of acquire() calls, plus an initial value.
- The acquire() method blocks if necessary until it can return
- without making the counter negative.
-
- If not given, ``value`` defaults to 1.
-
- The semaphore is a context manager and can be used in ``with`` statements.
-
- This Semaphore's ``__exit__`` method does not call the trace function
- on CPython, but does under PyPy.
-
- .. seealso:: :class:`BoundedSemaphore` for a safer version that prevents
- some classes of bugs.
- """
-
- def __init__(self, value=1):
- if value < 0:
- raise ValueError("semaphore initial value must be >= 0")
- self.counter = value
- self._dirty = False
- # In PyPy 2.6.1 with Cython 0.23, `cdef public` or `cdef
- # readonly` or simply `cdef` attributes of type `object` can appear to leak if
- # a Python subclass is used (this is visible simply
- # instantiating this subclass if _links=[]). Our _links and
- # _notifier are such attributes, and gevent.thread subclasses
- # this class. Thus, we carefully manage the lifetime of the
- # objects we put in these attributes so that, in the normal
- # case of a semaphore used correctly (deallocated when it's not
- # locked and no one is waiting), the leak goes away (because
- # these objects are back to None). This can also be solved on PyPy
- # by simply not declaring these objects in the pxd file, but that doesn't work for
- # CPython ("No attribute...")
- # See https://github.com/gevent/gevent/issues/660
- self._links = None
- self._notifier = None
- # we don't want to do get_hub() here to allow defining module-level locks
- # without initializing the hub
-
- def __str__(self):
- params = (self.__class__.__name__, self.counter, len(self._links) if self._links else 0)
- return '<%s counter=%s _links[%s]>' % params
-
- def locked(self):
- """Return a boolean indicating whether the semaphore can be acquired.
- Most useful with binary semaphores."""
- return self.counter <= 0
-
- def release(self):
- """
- Release the semaphore, notifying any waiters if needed.
- """
- self.counter += 1
- self._start_notify()
- return self.counter
-
- def _start_notify(self):
- if self._links and self.counter > 0 and not self._notifier:
- # We create a new self._notifier each time through the loop,
- # if needed. (it has a __bool__ method that tells whether it has
- # been run; once it's run once---at the end of the loop---it becomes
- # false.)
- # NOTE: Passing the bound method will cause a memory leak on PyPy
- # with Cython <= 0.23.3. You must use >= 0.23.4.
- # See https://bitbucket.org/pypy/pypy/issues/2149/memory-leak-for-python-subclass-of-cpyext#comment-22371546
- hub = get_hub() # pylint:disable=undefined-variable
- self._notifier = hub.loop.run_callback(self._notify_links)
-
- def _notify_links(self):
- # Subclasses CANNOT override. This is a cdef method.
-
- # We release self._notifier here. We are called by it
- # at the end of the loop, and it is now false in a boolean way (as soon
- # as this method returns).
- # If we get acquired/released again, we will create a new one, but there's
- # no need to keep it around until that point (making it potentially climb
- # into older GC generations, notably on PyPy)
- notifier = self._notifier
- try:
- while True:
- self._dirty = False
- if not self._links:
- # In case we were manually unlinked before
- # the callback. Which shouldn't happen
- return
- for link in self._links:
- if self.counter <= 0:
- return
- try:
- link(self) # Must use Cython >= 0.23.4 on PyPy else this leaks memory
- except: # pylint:disable=bare-except
- getcurrent().handle_error((link, self), *sys.exc_info()) # pylint:disable=undefined-variable
- if self._dirty:
- # We mutated self._links so we need to start over
- break
- if not self._dirty:
- return
- finally:
- # We should not have created a new notifier even if callbacks
- # released us because we loop through *all* of our links on the
- # same callback while self._notifier is still true.
- assert self._notifier is notifier
- self._notifier = None
-
- def rawlink(self, callback):
- """
- rawlink(callback) -> None
-
- Register a callback to call when a counter is more than zero.
-
- *callback* will be called in the :class:`Hub <gevent.hub.Hub>`, so it must not use blocking gevent API.
- *callback* will be passed one argument: this instance.
-
- This method is normally called automatically by :meth:`acquire` and :meth:`wait`; most code
- will not need to use it.
- """
- if not callable(callback):
- raise TypeError('Expected callable:', callback)
- if self._links is None:
- self._links = [callback]
- else:
- self._links.append(callback)
- self._dirty = True
-
- def unlink(self, callback):
- """
- unlink(callback) -> None
-
- Remove the callback set by :meth:`rawlink`.
-
- This method is normally called automatically by :meth:`acquire` and :meth:`wait`; most
- code will not need to use it.
- """
- try:
- self._links.remove(callback)
- self._dirty = True
- except (ValueError, AttributeError):
- pass
- if not self._links:
- self._links = None
- # TODO: Cancel a notifier if there are no links?
-
- def _do_wait(self, timeout):
- """
- Wait for up to *timeout* seconds to expire. If timeout
- elapses, return the exception. Otherwise, return None.
- Raises timeout if a different timer expires.
- """
- switch = getcurrent().switch # pylint:disable=undefined-variable
- self.rawlink(switch)
- try:
- timer = Timeout._start_new_or_dummy(timeout)
- try:
- try:
- result = get_hub().switch() # pylint:disable=undefined-variable
- assert result is self, 'Invalid switch into Semaphore.wait/acquire(): %r' % (result, )
- except Timeout as ex:
- if ex is not timer:
- raise
- return ex
- finally:
- timer.cancel()
- finally:
- self.unlink(switch)
-
- def wait(self, timeout=None):
- """
- wait(timeout=None) -> int
-
- Wait until it is possible to acquire this semaphore, or until the optional
- *timeout* elapses.
-
- .. caution:: If this semaphore was initialized with a size of 0,
- this method will block forever if no timeout is given.
-
- :keyword float timeout: If given, specifies the maximum amount of seconds
- this method will block.
- :return: A number indicating how many times the semaphore can be acquired
- before blocking.
- """
- if self.counter > 0:
- return self.counter
-
- self._do_wait(timeout) # return value irrelevant, whether we got it or got a timeout
- return self.counter
-
- def acquire(self, blocking=True, timeout=None):
- """
- acquire(blocking=True, timeout=None) -> bool
-
- Acquire the semaphore.
-
- .. caution:: If this semaphore was initialized with a size of 0,
- this method will block forever (unless a timeout is given or blocking is
- set to false).
-
- :keyword bool blocking: If True (the default), this function will block
- until the semaphore is acquired.
- :keyword float timeout: If given, specifies the maximum amount of seconds
- this method will block.
- :return: A boolean indicating whether the semaphore was acquired.
- If ``blocking`` is True and ``timeout`` is None (the default), then
- (so long as this semaphore was initialized with a size greater than 0)
- this will always return True. If a timeout was given, and it expired before
- the semaphore was acquired, False will be returned. (Note that this can still
- raise a ``Timeout`` exception, if some other caller had already started a timer.)
- """
- if self.counter > 0:
- self.counter -= 1
- return True
-
- if not blocking:
- return False
-
- timeout = self._do_wait(timeout)
- if timeout is not None:
- # Our timer expired.
- return False
-
- # Neither our timer no another one expired, so we blocked until
- # awoke. Therefore, the counter is ours
- self.counter -= 1
- assert self.counter >= 0
- return True
-
- _py3k_acquire = acquire # PyPy needs this; it must be static for Cython
-
- def __enter__(self):
- self.acquire()
-
- def __exit__(self, t, v, tb):
- self.release()
-
-
-class BoundedSemaphore(Semaphore):
- """
- BoundedSemaphore(value=1) -> BoundedSemaphore
-
- A bounded semaphore checks to make sure its current value doesn't
- exceed its initial value. If it does, :class:`ValueError` is
- raised. In most situations semaphores are used to guard resources
- with limited capacity. If the semaphore is released too many times
- it's a sign of a bug.
-
- If not given, *value* defaults to 1.
- """
-
- #: For monkey-patching, allow changing the class of error we raise
- _OVER_RELEASE_ERROR = ValueError
-
- def __init__(self, *args, **kwargs):
- Semaphore.__init__(self, *args, **kwargs)
- self._initial_value = self.counter
-
- def release(self):
- if self.counter >= self._initial_value:
- raise self._OVER_RELEASE_ERROR("Semaphore released too many times")
- Semaphore.release(self)
-
-
-def _init():
- greenlet_init() # pylint:disable=undefined-variable
-
-_init()
-
-# By building the semaphore with Cython under PyPy, we get
-# atomic operations (specifically, exiting/releasing), at the
-# cost of some speed (one trivial semaphore micro-benchmark put the pure-python version
-# at around 1s and the compiled version at around 4s). Some clever subclassing
-# and having only the bare minimum be in cython might help reduce that penalty.
-# NOTE: You must use version 0.23.4 or later to avoid a memory leak.
-# https://mail.python.org/pipermail/cython-devel/2015-October/004571.html
-# However, that's all for naught on up to and including PyPy 4.0.1 which
-# have some serious crashing bugs with GC interacting with cython.
-# It hasn't been tested since then, and PURE_PYTHON is assumed to be true
-# for PyPy in all cases anyway, so this does nothing.
-
-from gevent._util import import_c_accel
-import_c_accel(globals(), 'gevent.__semaphore')