diff -r c5186045395e Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py Wed Jun 04 00:42:04 2014 +0200 +++ b/Lib/asyncio/base_events.py Wed Jun 04 01:38:49 2014 +0200 @@ -119,6 +119,7 @@ class Server(events.AbstractServer): class BaseEventLoop(events.AbstractEventLoop): def __init__(self): + self._closed = False self._ready = collections.deque() self._scheduled = [] self._default_executor = None @@ -128,6 +129,11 @@ class BaseEventLoop(events.AbstractEvent self._exception_handler = None self._debug = False + def __repr__(self): + return ('<%s running=%s closed=%s debug=%s>' + % (self.__class__.__name__, self.is_running(), + self._closed, self.get_debug())) + def _make_socket_transport(self, sock, protocol, waiter=None, *, extra=None, server=None): """Create socket transport.""" @@ -173,8 +179,13 @@ class BaseEventLoop(events.AbstractEvent """Process selector events.""" raise NotImplementedError + def _check_closed(self): + if self._closed: + raise RuntimeError('Event loop is closed') + def run_forever(self): """Run until stop() is called.""" + self._check_closed() if self._running: raise RuntimeError('Event loop is running.') self._running = True @@ -198,6 +209,7 @@ class BaseEventLoop(events.AbstractEvent Return the Future's result, or raise its exception. """ + self._check_closed() future = tasks.async(future, loop=self) future.add_done_callback(_raise_stop_error) self.run_forever() @@ -222,6 +234,9 @@ class BaseEventLoop(events.AbstractEvent This clears the queues and shuts down the executor, but does not wait for the executor to finish. """ + if self._closed: + return + self._closed = True self._ready.clear() self._scheduled.clear() executor = self._default_executor diff -r c5186045395e Lib/asyncio/proactor_events.py --- a/Lib/asyncio/proactor_events.py Wed Jun 04 00:42:04 2014 +0200 +++ b/Lib/asyncio/proactor_events.py Wed Jun 04 01:38:49 2014 +0200 @@ -354,12 +354,12 @@ class BaseProactorEventLoop(base_events. def close(self): if self._proactor is not None: + self._stop_accept_futures() self._close_self_pipe() self._proactor.close() self._proactor = None self._selector = None super().close() - self._accept_futures.clear() def sock_recv(self, sock, n): return self._proactor.recv(sock, n) @@ -428,6 +428,9 @@ class BaseProactorEventLoop(base_events. self._make_socket_transport( conn, protocol, extra={'peername': addr}, server=server) + if self._proactor is None: + # the event loop was closed + return f = self._proactor.accept(sock) except OSError as exc: if sock.fileno() != -1: @@ -448,8 +451,12 @@ class BaseProactorEventLoop(base_events. def _process_events(self, event_list): pass # XXX hard work currently done in poll - def _stop_serving(self, sock): + def _stop_accept_futures(self): for future in self._accept_futures.values(): future.cancel() + self._accept_futures.clear() + + def _stop_serving(self, sock): + self._stop_accept_futures() self._proactor._stop_serving(sock) sock.close() diff -r c5186045395e Lib/test/test_asyncio/test_base_events.py --- a/Lib/test/test_asyncio/test_base_events.py Wed Jun 04 00:42:04 2014 +0200 +++ b/Lib/test/test_asyncio/test_base_events.py Wed Jun 04 01:38:49 2014 +0200 @@ -52,6 +52,20 @@ class BaseEventLoopTests(unittest.TestCa gen = self.loop._make_subprocess_transport(m, m, m, m, m, m, m) self.assertRaises(NotImplementedError, next, iter(gen)) + def test_close(self): + self.assertFalse(self.loop._closed) + self.loop.close() + self.assertTrue(self.loop._closed) + + # it should be possible to call close() more than once + self.loop.close() + self.loop.close() + + # operation blocked when the loop is closed + f = asyncio.Future(loop=self.loop) + self.assertRaises(RuntimeError, self.loop.run_forever) + self.assertRaises(RuntimeError, self.loop.run_until_complete, f) + def test__add_callback_handle(self): h = asyncio.Handle(lambda: False, (), self.loop) diff -r c5186045395e Lib/test/test_asyncio/test_selector_events.py --- a/Lib/test/test_asyncio/test_selector_events.py Wed Jun 04 00:42:04 2014 +0200 +++ b/Lib/test/test_asyncio/test_selector_events.py Wed Jun 04 01:38:49 2014 +0200 @@ -80,6 +80,7 @@ class BaseSelectorEventLoopTests(unittes self.loop._selector.close() self.loop._selector = selector = mock.Mock() + self.loop.close() self.assertIsNone(self.loop._selector) self.assertIsNone(self.loop._csock) @@ -89,9 +90,20 @@ class BaseSelectorEventLoopTests(unittes csock.close.assert_called_with() remove_reader.assert_called_with(7) + # it should be possible to call close() more than once self.loop.close() self.loop.close() + # operation blocked when the loop is closed + f = asyncio.Future(loop=self.loop) + self.assertRaises(RuntimeError, self.loop.run_forever) + self.assertRaises(RuntimeError, self.loop.run_until_complete, f) + fd = 0 + def callback(): + pass + self.assertRaises(RuntimeError, self.loop.add_reader, fd, callback) + self.assertRaises(RuntimeError, self.loop.add_writer, fd, callback) + def test_close_no_selector(self): ssock = self.loop._ssock csock = self.loop._csock