Skip to content

Commit

Permalink
Make RLock.acquire accept the timeout parameter.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Mar 25, 2020
1 parent a362a56 commit ee964f8
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 24 deletions.
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
1.5a5 (unreleased)
==================

- Nothing changed yet.
- Make `gevent.lock.RLock.acquire` accept the *timeout* parameter.


1.5a4 (2020-03-23)
Expand Down
71 changes: 52 additions & 19 deletions src/gevent/_semaphore.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
"""
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.
.. note::
Most users should prefer :class:`BoundedSemaphore`, a safer
subclass of this class.
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. A semaphore does not track ownership
by greenlets; any greenlet can call `release`, whether or not it has previously
called `acquire`.
If not given, ``value`` defaults to 1.
Expand All @@ -33,12 +39,10 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
some classes of bugs.
.. versionchanged:: 1.4.0
The order in which waiters are awakened is not specified. It was not
specified previously, but usually went in FIFO order.
Document that the order in which waiters are awakened is not specified. It was not
specified previously, but due to CPython implementation quirks usually went in FIFO order.
.. versionchanged:: 1.5a3
Waiting greenlets are now awakened in the order in which they waited.
.. versionchanged:: 1.5a3
The low-level ``rawlink`` method (most users won't use this) now automatically
unlinks waiters before calling them.
Expand All @@ -56,19 +60,43 @@ def __str__(self):
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 a boolean indicating whether the semaphore can be
acquired (`False` if the semaphore *can* be acquired). Most
useful with binary semaphores (those with an initial value of 1).
:rtype: bool
"""
return self.counter <= 0

def release(self):
"""
Release the semaphore, notifying any waiters if needed.
Release the semaphore, notifying any waiters if needed. There
is no return value.
.. note::
This can be used to over-release the semaphore.
(Release more times than it has been acquired or was initially
created with.)
This is usually a sign of a bug, but under some circumstances it can be
used deliberately, for example, to model the arrival of additional
resources.
:rtype: None
"""
self.counter += 1
self._check_and_notify()
return self.counter

def ready(self):
"""
Return a boolean indicating whether the semaphore can be
acquired (`True` if the semaphore can be acquired).
:rtype: bool
"""
return self.counter > 0

def _start_notify(self):
Expand All @@ -84,18 +112,18 @@ def _wait_return_value(self, waited, wait_success):

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,
.. note:: If this semaphore was initialized with a *value* 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.
before blocking. *This could be 0,* if other waiters acquired
the semaphore.
:rtype: int
"""
if self.counter > 0:
return self.counter
Expand All @@ -109,15 +137,16 @@ def acquire(self, blocking=True, timeout=None):
Acquire the semaphore.
.. caution:: If this semaphore was initialized with a size of 0,
.. note:: If this semaphore was initialized with a *value* 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
:keyword float timeout: If given, and *blocking* is true,
specifies the maximum amount of seconds
this method will block.
:return: A boolean indicating whether the semaphore was acquired.
:return: A `bool` 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
Expand Down Expand Up @@ -172,9 +201,13 @@ def __init__(self, *args, **kwargs):
self._initial_value = self.counter

def release(self):
"""
Like :meth:`Semaphore.release`, but raises :class:`ValueError`
if the semaphore is being over-released.
"""
if self.counter >= self._initial_value:
raise self._OVER_RELEASE_ERROR("Semaphore released too many times")
Semaphore.release(self)
return Semaphore.release(self)



Expand Down
43 changes: 39 additions & 4 deletions src/gevent/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

__all__ = [
'Semaphore',
'DummySemaphore',
'BoundedSemaphore',
'DummySemaphore',
'RLock',
]

Expand Down Expand Up @@ -129,7 +129,8 @@ class DummySemaphore(object):
"""
DummySemaphore(value=None) -> DummySemaphore
A Semaphore initialized with "infinite" initial value. None of its
An object with the same API as :class:`Semaphore`,
initialized with "infinite" initial value. None of its
methods ever block.
This can be used to parameterize on whether or not to actually
Expand Down Expand Up @@ -166,6 +167,10 @@ def locked(self):
"""A DummySemaphore is never locked so this always returns False."""
return False

def ready(self):
"""A DummySemaphore is never locked so this always returns True."""
return True

def release(self):
"""Releasing a dummy semaphore does nothing."""

Expand Down Expand Up @@ -200,8 +205,23 @@ def __exit__(self, typ, val, tb):
class RLock(object):
"""
A mutex that can be acquired more than once by the same greenlet.
A mutex can only be locked by one greenlet at a time. A single greenlet
can `acquire` the mutex as many times as desired, though. Each call to
`acquire` must be paired with a matching call to `release`.
It is an error for a greenlet that has not acquired the mutex
to release it.
Instances are context managers.
"""

__slots__ = (
'_block',
'_owner',
'_count',
)

def __init__(self):
self._block = Semaphore(1)
self._owner = None
Expand All @@ -215,12 +235,21 @@ def __repr__(self):
self._count,
self._owner)

def acquire(self, blocking=1):
def acquire(self, blocking=True, timeout=None):
"""
Acquire the mutex, blocking if *blocking* is true, for up to
*timeout* seconds.
.. versionchanged:: 1.5a4
Added the *timeout* parameter.
:return: A boolean indicating whether the mutex was acquired.
"""
me = getcurrent()
if self._owner is me:
self._count = self._count + 1
return 1
rc = self._block.acquire(blocking)
rc = self._block.acquire(blocking, timeout)
if rc:
self._owner = me
self._count = 1
Expand All @@ -230,6 +259,12 @@ def __enter__(self):
return self.acquire()

def release(self):
"""
Release the mutex.
Only the greenlet that originally acquired the mutex can
release it.
"""
if self._owner is not getcurrent():
raise RuntimeError("cannot release un-acquired lock")
self._count = count = self._count - 1
Expand Down

0 comments on commit ee964f8

Please sign in to comment.