Commit 3c624d73 authored by Jason Madden's avatar Jason Madden

Merge branch 'RLock-acquire-timeout'

parents a362a560 9b2108be
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
1.5a5 (unreleased) 1.5a5 (unreleased)
================== ==================
- Nothing changed yet. - Make `gevent.lock.RLock.acquire` accept the *timeout* parameter.
1.5a4 (2020-03-23) 1.5a4 (2020-03-23)
......
...@@ -17,10 +17,15 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable ...@@ -17,10 +17,15 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
""" """
Semaphore(value=1) -> Semaphore Semaphore(value=1) -> Semaphore
A semaphore manages a counter representing the number of release() .. seealso:: :class:`BoundedSemaphore` for a safer version that prevents
calls minus the number of acquire() calls, plus an initial value. some classes of bugs. If unsure, most users should opt for `BoundedSemaphore`.
The acquire() method blocks if necessary until it can return
without making the counter negative. 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. If not given, ``value`` defaults to 1.
...@@ -29,16 +34,12 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable ...@@ -29,16 +34,12 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
This Semaphore's ``__exit__`` method does not call the trace function This Semaphore's ``__exit__`` method does not call the trace function
on CPython, but does under PyPy. on CPython, but does under PyPy.
.. seealso:: :class:`BoundedSemaphore` for a safer version that prevents
some classes of bugs.
.. versionchanged:: 1.4.0 .. versionchanged:: 1.4.0
Document that the order in which waiters are awakened is not specified. It was not
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.
specified previously, but usually went in FIFO order.
.. versionchanged:: 1.5a3 .. versionchanged:: 1.5a3
Waiting greenlets are now awakened in the order in which they waited. Waiting greenlets are now awakened in the order in which they waited.
.. versionchanged:: 1.5a3 .. versionchanged:: 1.5a3
The low-level ``rawlink`` method (most users won't use this) now automatically The low-level ``rawlink`` method (most users won't use this) now automatically
unlinks waiters before calling them. unlinks waiters before calling them.
...@@ -56,19 +57,43 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable ...@@ -56,19 +57,43 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
return '<%s counter=%s _links[%s]>' % params return '<%s counter=%s _links[%s]>' % params
def locked(self): 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 return self.counter <= 0
def release(self): 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.counter += 1
self._check_and_notify() self._check_and_notify()
return self.counter return self.counter
def ready(self): 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 return self.counter > 0
def _start_notify(self): def _start_notify(self):
...@@ -84,18 +109,18 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable ...@@ -84,18 +109,18 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
def wait(self, timeout=None): def wait(self, timeout=None):
""" """
wait(timeout=None) -> int
Wait until it is possible to acquire this semaphore, or until the optional Wait until it is possible to acquire this semaphore, or until the optional
*timeout* elapses. *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. this method will block forever if no timeout is given.
:keyword float timeout: If given, specifies the maximum amount of seconds :keyword float timeout: If given, specifies the maximum amount of seconds
this method will block. this method will block.
:return: A number indicating how many times the semaphore can be acquired :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: if self.counter > 0:
return self.counter return self.counter
...@@ -109,15 +134,16 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable ...@@ -109,15 +134,16 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
Acquire the semaphore. 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 this method will block forever (unless a timeout is given or blocking is
set to false). set to false).
:keyword bool blocking: If True (the default), this function will block :keyword bool blocking: If True (the default), this function will block
until the semaphore is acquired. 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. 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 If ``blocking`` is True and ``timeout`` is None (the default), then
(so long as this semaphore was initialized with a size greater than 0) (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 this will always return True. If a timeout was given, and it expired before
...@@ -172,9 +198,13 @@ class BoundedSemaphore(Semaphore): ...@@ -172,9 +198,13 @@ class BoundedSemaphore(Semaphore):
self._initial_value = self.counter self._initial_value = self.counter
def release(self): def release(self):
"""
Like :meth:`Semaphore.release`, but raises :class:`ValueError`
if the semaphore is being over-released.
"""
if self.counter >= self._initial_value: if self.counter >= self._initial_value:
raise self._OVER_RELEASE_ERROR("Semaphore released too many times") raise self._OVER_RELEASE_ERROR("Semaphore released too many times")
Semaphore.release(self) return Semaphore.release(self)
......
# Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details. # Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
"""Locking primitives""" """
Locking primitives.
These include semaphores with arbitrary bounds (:class:`Semaphore` and
its safer subclass :class:`BoundedSemaphore`) and a semaphore with
infinite bounds (:class:`DummySemaphore`), along with a reentrant lock
(:class:`RLock`) with the same API as :class:`threading.RLock`.
"""
from __future__ import absolute_import from __future__ import absolute_import
from gevent.hub import getcurrent from gevent.hub import getcurrent
...@@ -9,8 +16,8 @@ from gevent._semaphore import Semaphore, BoundedSemaphore # pylint:disable=no-na ...@@ -9,8 +16,8 @@ from gevent._semaphore import Semaphore, BoundedSemaphore # pylint:disable=no-na
__all__ = [ __all__ = [
'Semaphore', 'Semaphore',
'DummySemaphore',
'BoundedSemaphore', 'BoundedSemaphore',
'DummySemaphore',
'RLock', 'RLock',
] ]
...@@ -129,7 +136,8 @@ class DummySemaphore(object): ...@@ -129,7 +136,8 @@ class DummySemaphore(object):
""" """
DummySemaphore(value=None) -> DummySemaphore 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. methods ever block.
This can be used to parameterize on whether or not to actually This can be used to parameterize on whether or not to actually
...@@ -166,6 +174,10 @@ class DummySemaphore(object): ...@@ -166,6 +174,10 @@ class DummySemaphore(object):
"""A DummySemaphore is never locked so this always returns False.""" """A DummySemaphore is never locked so this always returns False."""
return False return False
def ready(self):
"""A DummySemaphore is never locked so this always returns True."""
return True
def release(self): def release(self):
"""Releasing a dummy semaphore does nothing.""" """Releasing a dummy semaphore does nothing."""
...@@ -200,8 +212,24 @@ class DummySemaphore(object): ...@@ -200,8 +212,24 @@ class DummySemaphore(object):
class RLock(object): class RLock(object):
""" """
A mutex that can be acquired more than once by the same greenlet. 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',
'__weakref__',
)
def __init__(self): def __init__(self):
self._block = Semaphore(1) self._block = Semaphore(1)
self._owner = None self._owner = None
...@@ -215,12 +243,21 @@ class RLock(object): ...@@ -215,12 +243,21 @@ class RLock(object):
self._count, self._count,
self._owner) 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() me = getcurrent()
if self._owner is me: if self._owner is me:
self._count = self._count + 1 self._count = self._count + 1
return 1 return 1
rc = self._block.acquire(blocking) rc = self._block.acquire(blocking, timeout)
if rc: if rc:
self._owner = me self._owner = me
self._count = 1 self._count = 1
...@@ -230,6 +267,12 @@ class RLock(object): ...@@ -230,6 +267,12 @@ class RLock(object):
return self.acquire() return self.acquire()
def release(self): def release(self):
"""
Release the mutex.
Only the greenlet that originally acquired the mutex can
release it.
"""
if self._owner is not getcurrent(): if self._owner is not getcurrent():
raise RuntimeError("cannot release un-acquired lock") raise RuntimeError("cannot release un-acquired lock")
self._count = count = self._count - 1 self._count = count = self._count - 1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment