]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Clean up shutdown process for IOLoop and HTTPClient.
authorBen Darnell <ben@bendarnell.com>
Sun, 26 Jun 2011 21:21:02 +0000 (14:21 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 26 Jun 2011 21:21:02 +0000 (14:21 -0700)
This fixes an exception on exit from tornado.httpclient.main().

tornado/httpclient.py
tornado/ioloop.py
tornado/testing.py
website/sphinx/releases/next.rst

index e42a7265ef9beeecd49851363bb1664ed3b88b6b..1b1336c90c221a7ce1d4133e5a69e066d256db64 100644 (file)
@@ -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()
index a08afe22a5cd0f8c522414e202c521dc85dc077f..1609c2cb5f47ab9fbf3a561581a8edddc43d1b0f 100644 (file)
@@ -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)
index e5cee16299d34b754b8704e603c474b60bb3b4b9..cb724d4f40c35e1dffdbbeb900fca2a7ea02e2af 100644 (file)
@@ -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):
index b3ac42a20c81ab76c8b0b3e76cc2d10ec650da3c..a7961da48983796e4a4d46442782cfcb2b3a636d 100644 (file)
@@ -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
 ~~~~~~~~~