Commit 90a4c18c authored by Jason Madden's avatar Jason Madden

iwait is tolerant of having the greenlets switch while it yields. Fixes #467.

parent dc39cee4
......@@ -42,6 +42,9 @@ Unreleased
module no longer leaks file descriptors. Reported in :pr:`374` by 陈小玉.
- The example ``echoserver.py`` no longer binds to the standard X11
TCP port. Reported in :issue:`485` by minusf.
- ``gevent.iwait`` no longer throws ``LoopExit`` if the caller
switches greenlets between return values. Reported and initial patch
in :pr:`467` by Alexey Borzenkov.
1.1a1 (Jun 29, 2015)
====================
......
......@@ -598,6 +598,41 @@ class Waiter(object):
# and unwraps it in wait() thus checking that switch() was indeed called
class _MultipleWaiter(Waiter):
"""
An internal extension of Waiter that can be used if multiple objects
must be waited on, and there is a chance that in between waits greenlets
might be switched out. All greenlets that switch to this waiter
will have their value returned.
This does not handle exceptions or throw methods.
"""
_DEQUE = None
__slots__ = ['_values']
def __init__(self, *args, **kwargs):
Waiter.__init__(self, *args, **kwargs)
self._values = self._deque()
@classmethod
def _deque(cls):
if cls._DEQUE is None:
from collections import deque
cls._DEQUE = deque
return cls._DEQUE()
def switch(self, value):
self._values.append(value)
Waiter.switch(self, True)
def get(self):
if not self._values:
Waiter.get(self)
Waiter.clear(self)
return self._values.popleft()
def iwait(objects, timeout=None, count=None):
"""
Yield objects as they are ready, until all (or `count`) are ready or `timeout` expired.
......@@ -612,14 +647,13 @@ def iwait(objects, timeout=None, count=None):
yield get_hub().join(timeout=timeout)
return
waiter = Waiter()
count = len(objects) if count is None else min(count, len(objects))
waiter = _MultipleWaiter()
switch = waiter.switch
if timeout is not None:
timer = get_hub().loop.timer(timeout, priority=-1)
timer.start(waiter.switch, _NONE)
count = len(objects) if count is None else min(count, len(objects))
timer.start(switch, _NONE)
try:
for obj in objects:
......
......@@ -12,9 +12,8 @@ def make_exec_test(path, module):
def test(self):
#sys.stderr.write('%s %s\n' % (module, path))
f = open(path)
src = f.read()
f.close()
with open(path, 'rb') as f:
src = f.read()
six.exec_(src, {})
name = "test_" + module.replace(".", "_")
......
import gevent
#import socket # on windows
# iwait should not raise `LoopExit: This operation would block forever`
# or `AssertionError: Invalid switch into ...`
# if the caller of iwait causes greenlets to switch in between
# return values
def worker(i):
# Have one of them raise an exception to test that case
if i == 2:
raise ValueError(i)
return i
def main():
finished = 0
# Wait on a group that includes one that will already be
# done, plus some that will finish as we watch
done_worker = gevent.spawn(worker, "done")
gevent.joinall((done_worker,))
workers = [gevent.spawn(worker, i) for i in range(3)]
workers.append(done_worker)
for g in gevent.iwait(workers):
finished += 1
# Simulate doing something that causes greenlets to switch;
# a non-zero timeout is crucial
gevent.sleep(0.01)
assert finished == 4
if __name__ == '__main__':
main()
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