From: Ben Darnell Date: Sun, 26 Jun 2011 21:21:02 +0000 (-0700) Subject: Clean up shutdown process for IOLoop and HTTPClient. X-Git-Tag: v2.1.0~146 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d1af05323f9a7941e2336ce50972de65e18bcdd;p=thirdparty%2Ftornado.git Clean up shutdown process for IOLoop and HTTPClient. This fixes an exception on exit from tornado.httpclient.main(). --- diff --git a/tornado/httpclient.py b/tornado/httpclient.py index e42a7265e..1b1336c90 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -54,9 +54,17 @@ class HTTPClient(object): self._io_loop = IOLoop() self._async_client = AsyncHTTPClient(self._io_loop) self._response = None + self._closed = False def __del__(self): - self._async_client.close() + self.close() + + def close(self): + """Closes the HTTPClient, freeing any resources used.""" + if not self._closed: + self._async_client.close() + self._io_loop.close() + self._closed = True def fetch(self, request, **kwargs): """Executes a request, returning an `HTTPResponse`. @@ -136,7 +144,7 @@ class AsyncHTTPClient(object): create and destroy http clients. No other methods may be called on the AsyncHTTPClient after close(). """ - if self._async_clients[self.io_loop] is self: + if self._async_clients.get(self.io_loop) is self: del self._async_clients[self.io_loop] def fetch(self, request, callback, **kwargs): @@ -383,6 +391,7 @@ def main(): print response.headers if options.print_body: print response.body + client.close() if __name__ == "__main__": main() diff --git a/tornado/ioloop.py b/tornado/ioloop.py index a08afe22a..1609c2cb5 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -158,6 +158,28 @@ class IOLoop(object): """Returns true if the singleton instance has been created.""" return hasattr(cls, "_instance") + def close(self, all_fds=False): + """Closes the IOLoop, freeing any resources used. + + If ``all_fds`` is true, all file descriptors registered on the + IOLoop will be closed (not just the ones created by the IOLoop itself. + """ + if all_fds: + for fd in self._handlers.keys()[:]: + if fd in (self._waker_reader.fileno(), + self._waker_writer.fileno()): + # Close these through the file objects that wrap them, + # or else the destructor will try to close them later + # and log a warning + continue + try: + os.close(fd) + except Exception: + logging.debug("error closing fd %d", fd, exc_info=True) + self._waker_reader.close() + self._waker_writer.close() + self._impl.close() + def add_handler(self, fd, handler, events): """Registers the given handler to receive the given events for fd.""" self._handlers[fd] = stack_context.wrap(handler) @@ -472,6 +494,9 @@ class _EPoll(object): def fileno(self): return self._epoll_fd + def close(self): + os.close(self._epoll_fd) + def register(self, fd, events): epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_ADD, fd, events) @@ -494,6 +519,9 @@ class _KQueue(object): def fileno(self): return self._kqueue.fileno() + def close(self): + self._kqueue.close() + def register(self, fd, events): self._control(fd, events, select.KQ_EV_ADD) self._active[fd] = events @@ -552,6 +580,9 @@ class _Select(object): self.error_fds = set() self.fd_sets = (self.read_fds, self.write_fds, self.error_fds) + def close(self): + pass + def register(self, fd, events): if events & IOLoop.READ: self.read_fds.add(fd) if events & IOLoop.WRITE: self.write_fds.add(fd) diff --git a/tornado/testing.py b/tornado/testing.py index e5cee1629..cb724d4f4 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -101,19 +101,7 @@ class AsyncTestCase(unittest.TestCase): # This avoids leaks, especially when tests are run repeatedly # in the same process with autoreload (because curl does not # set FD_CLOEXEC on its file descriptors) - for fd in self.io_loop._handlers.keys()[:]: - if (fd == self.io_loop._waker_reader.fileno() or - fd == self.io_loop._waker_writer.fileno()): - # Close these through the file objects that wrap - # them, or else the destructor will try to close - # them later and log a warning - continue - try: - os.close(fd) - except: - logging.debug("error closing fd %d", fd, exc_info=True) - self.io_loop._waker_reader.close() - self.io_loop._waker_writer.close() + self.io_loop.close(all_fds=True) super(AsyncTestCase, self).tearDown() def get_new_ioloop(self): diff --git a/website/sphinx/releases/next.rst b/website/sphinx/releases/next.rst index b3ac42a20..a7961da48 100644 --- a/website/sphinx/releases/next.rst +++ b/website/sphinx/releases/next.rst @@ -8,6 +8,9 @@ New features ~~~~~~~~~~~~ * New method `tornado.iostream.IOStream.read_until_close` +* `tornado.ioloop.IOLoop` and `tornado.httpclient.HTTPClient` now have + ``close()`` methods that should be used in applications that create + and destroy many of these objects. Bug fixes ~~~~~~~~~