Handle case where Deferred fires after timeout
This commit is contained in:
@@ -153,6 +153,7 @@ class Spinner(object):
|
|||||||
self._saved_signals = []
|
self._saved_signals = []
|
||||||
self._junk = []
|
self._junk = []
|
||||||
self._debug = debug
|
self._debug = debug
|
||||||
|
self._spinning = False
|
||||||
|
|
||||||
def _cancel_timeout(self):
|
def _cancel_timeout(self):
|
||||||
if self._timeout_call:
|
if self._timeout_call:
|
||||||
@@ -186,7 +187,10 @@ class Spinner(object):
|
|||||||
|
|
||||||
def _stop_reactor(self, ignored=None):
|
def _stop_reactor(self, ignored=None):
|
||||||
"""Stop the reactor!"""
|
"""Stop the reactor!"""
|
||||||
self._reactor.crash()
|
# XXX: Would like to emit a warning when called when *not* spinning.
|
||||||
|
if self._spinning:
|
||||||
|
self._reactor.crash()
|
||||||
|
self._spinning = False
|
||||||
|
|
||||||
def _timed_out(self, function, timeout):
|
def _timed_out(self, function, timeout):
|
||||||
e = TimeoutError(function, timeout)
|
e = TimeoutError(function, timeout)
|
||||||
@@ -287,6 +291,7 @@ class Spinner(object):
|
|||||||
d.addBoth(self._stop_reactor)
|
d.addBoth(self._stop_reactor)
|
||||||
try:
|
try:
|
||||||
self._reactor.callWhenRunning(run_function)
|
self._reactor.callWhenRunning(run_function)
|
||||||
|
self._spinning = True
|
||||||
self._reactor.run()
|
self._reactor.run()
|
||||||
finally:
|
finally:
|
||||||
self._reactor.stop = real_stop
|
self._reactor.stop = real_stop
|
||||||
|
|||||||
@@ -297,6 +297,39 @@ class TestRunInReactor(NeedsTwistedTestCase):
|
|||||||
def test_fast_sigint_raises_no_result_error_second_time(self):
|
def test_fast_sigint_raises_no_result_error_second_time(self):
|
||||||
self.test_fast_sigint_raises_no_result_error()
|
self.test_fast_sigint_raises_no_result_error()
|
||||||
|
|
||||||
|
def test_fires_after_timeout(self):
|
||||||
|
# If we timeout, but the Deferred actually ends up firing after the
|
||||||
|
# time out (perhaps because Spinner's clean-up code is buggy, or
|
||||||
|
# perhaps because the code responsible for the callback is in a
|
||||||
|
# thread), then the next run of a spinner works as intended,
|
||||||
|
# completely isolated from the previous run.
|
||||||
|
|
||||||
|
# Ensure we've timed out, and that we have a handle on the Deferred
|
||||||
|
# that didn't fire.
|
||||||
|
reactor = self.make_reactor()
|
||||||
|
spinner1 = self.make_spinner(reactor)
|
||||||
|
timeout = self.make_timeout()
|
||||||
|
deferred1 = defer.Deferred()
|
||||||
|
self.expectThat(
|
||||||
|
lambda: spinner1.run(timeout, lambda: deferred1),
|
||||||
|
Raises(MatchesException(_spinner.TimeoutError)))
|
||||||
|
|
||||||
|
# Make a Deferred that will fire *after* deferred1 as long as the
|
||||||
|
# reactor keeps spinning. We don't care that it's a callback of
|
||||||
|
# deferred1 per se, only that it strictly fires afterwards.
|
||||||
|
marker = object()
|
||||||
|
deferred2 = defer.Deferred()
|
||||||
|
deferred1.addCallback(
|
||||||
|
lambda ignored: reactor.callLater(0, deferred2.callback, marker))
|
||||||
|
|
||||||
|
def fire_other():
|
||||||
|
"""Fire Deferred from the last spin while waiting for this one."""
|
||||||
|
deferred1.callback(object())
|
||||||
|
return deferred2
|
||||||
|
|
||||||
|
spinner2 = self.make_spinner(reactor)
|
||||||
|
self.assertThat(spinner2.run(3 * timeout, fire_other), Is(marker))
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
from unittest import TestLoader
|
from unittest import TestLoader
|
||||||
|
|||||||
Reference in New Issue
Block a user