Commit 421c1d14 authored by Jason Madden's avatar Jason Madden

Fix python bug 13502

http://bugs.python.org/issue13502

A race condition in Event that caused an improper return value if
greenlets were waiting while the event was set, but then immediately
cleared.

This was fixed in Python 3 but not (yet?) backported to Python 2.7, even
though the bug identifies that as a target.

Not only does this behaviour seem more obviously correct to me, it's
more future-proof: user share of Python 2 should be diminishing while
Python 3 share (with the correct behaviour) grows.
parent 8a256c7c
...@@ -13,6 +13,13 @@ ...@@ -13,6 +13,13 @@
None). The ``acquire`` method also raises the same :exc:`ValueError` None). The ``acquire`` method also raises the same :exc:`ValueError`
exceptions that the standard library does for invalid parameters. exceptions that the standard library does for invalid parameters.
Reported in :issue:`750` by Joy Zheng. Reported in :issue:`750` by Joy Zheng.
- Fix a race condition in :class:`~gevent.event.Event` that
made it return ``False`` when the event was set and cleared by the
same greenlet before allowing a switch to the waiting greenlets.
(Found by the 3.4 and 3.5 standard library test suites; the same as
Python `bug 13502`_).
.. _bug 13502: http://bugs.python.org/issue13502
1.1rc5 (Feb 24, 2016) 1.1rc5 (Feb 24, 2016)
===================== =====================
......
...@@ -77,6 +77,7 @@ class _AbstractLinkable(object): ...@@ -77,6 +77,7 @@ class _AbstractLinkable(object):
# The core of the wait implementation, handling # The core of the wait implementation, handling
# switching and linking. If *catch* is set to (), # switching and linking. If *catch* is set to (),
# a timeout that elapses will be allowed to be raised. # a timeout that elapses will be allowed to be raised.
# Returns a true value if the wait succeeded without timing out.
switch = getcurrent().switch switch = getcurrent().switch
self.rawlink(switch) self.rawlink(switch)
try: try:
...@@ -86,22 +87,27 @@ class _AbstractLinkable(object): ...@@ -86,22 +87,27 @@ class _AbstractLinkable(object):
result = self.hub.switch() result = self.hub.switch()
if result is not self: # pragma: no cover if result is not self: # pragma: no cover
raise InvalidSwitchError('Invalid switch into Event.wait(): %r' % (result, )) raise InvalidSwitchError('Invalid switch into Event.wait(): %r' % (result, ))
return True
except catch as ex: except catch as ex:
if ex is not timer: if ex is not timer:
raise raise
# test_set_and_clear and test_timeout in test_threading
# rely on the exact return values, not just truthish-ness
return False
finally: finally:
timer.cancel() timer.cancel()
finally: finally:
self.unlink(switch) self.unlink(switch)
_wait_return_value = None def _wait_return_value(self, waited, wait_success):
return None
def _wait(self, timeout=None): def _wait(self, timeout=None):
if self.ready(): if self.ready():
return getattr(self, self._wait_return_value) return self._wait_return_value(False, False)
self._wait_core(timeout) gotit = self._wait_core(timeout)
return getattr(self, self._wait_return_value) return self._wait_return_value(True, gotit)
class Event(_AbstractLinkable): class Event(_AbstractLinkable):
...@@ -153,7 +159,17 @@ class Event(_AbstractLinkable): ...@@ -153,7 +159,17 @@ class Event(_AbstractLinkable):
""" """
self._flag = False self._flag = False
_wait_return_value = '_flag' def _wait_return_value(self, waited, gotit):
# To avoid the race condition outlined in http://bugs.python.org/issue13502,
# if we had to wait, then we need to return whether or not
# the condition got changed. Otherwise we simply echo
# the current state of the flag (which should be true)
if not waited:
flag = self._flag
assert flag, "if we didn't wait we should already be set"
return flag
return gotit
def wait(self, timeout=None): def wait(self, timeout=None):
""" """
...@@ -167,8 +183,10 @@ class Event(_AbstractLinkable): ...@@ -167,8 +183,10 @@ class Event(_AbstractLinkable):
floating point number specifying a timeout for the operation in seconds floating point number specifying a timeout for the operation in seconds
(or fractions thereof). (or fractions thereof).
:return: The value of the internal flag (``True`` or ``False``). :return: This method returns true if and only if the internal flag has been set to
(If no timeout was given, the only possible return value is ``True``.) true, either before the wait call or after the wait starts, so it will
always return ``True`` except if a timeout is given and the operation
times out.
""" """
return self._wait(timeout) return self._wait(timeout)
...@@ -340,7 +358,10 @@ class AsyncResult(_AbstractLinkable): ...@@ -340,7 +358,10 @@ class AsyncResult(_AbstractLinkable):
""" """
return self.get(block=False) return self.get(block=False)
_wait_return_value = 'value' def _wait_return_value(self, waited, gotit):
# Always return the value. Since this is a one-shot event,
# no race condition should reset it.
return self.value
def wait(self, timeout=None): def wait(self, timeout=None):
"""Block until the instance is ready. """Block until the instance is ready.
......
...@@ -1082,10 +1082,6 @@ class CRLockTests(lock_tests.RLockTests): ...@@ -1082,10 +1082,6 @@ class CRLockTests(lock_tests.RLockTests):
class EventTests(lock_tests.EventTests): class EventTests(lock_tests.EventTests):
eventtype = staticmethod(threading.Event) eventtype = staticmethod(threading.Event)
@unittest.skip("gevent may/not suffer from Python bug 13502")
def test_set_and_clear(self):
pass
@unittest.skip("not on gevent") @unittest.skip("not on gevent")
def test_reset_internal_locks(self): def test_reset_internal_locks(self):
pass pass
......
...@@ -1090,10 +1090,6 @@ class CRLockTests(lock_tests.RLockTests): ...@@ -1090,10 +1090,6 @@ class CRLockTests(lock_tests.RLockTests):
class EventTests(lock_tests.EventTests): class EventTests(lock_tests.EventTests):
eventtype = staticmethod(threading.Event) eventtype = staticmethod(threading.Event)
@unittest.skip("gevent may/not suffer from Python bug 13502")
def test_set_and_clear(self):
pass
@unittest.skip("not on gevent") @unittest.skip("not on gevent")
def test_reset_internal_locks(self): def test_reset_internal_locks(self):
pass pass
......
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