]> granicus.if.org Git - python/commitdiff
Issue #23865: close() methods in multiple modules now are idempotent and more
authorSerhiy Storchaka <storchaka@gmail.com>
Fri, 10 Apr 2015 10:24:41 +0000 (13:24 +0300)
committerSerhiy Storchaka <storchaka@gmail.com>
Fri, 10 Apr 2015 10:24:41 +0000 (13:24 +0300)
robust at shutdown. If needs to release multiple resources, they are released
even if errors are occured.

27 files changed:
Lib/aifc.py
Lib/binhex.py
Lib/chunk.py
Lib/dbm/dumb.py
Lib/distutils/text_file.py
Lib/fileinput.py
Lib/ftplib.py
Lib/gzip.py
Lib/http/client.py
Lib/logging/__init__.py
Lib/logging/handlers.py
Lib/mailbox.py
Lib/multiprocessing/connection.py
Lib/multiprocessing/queues.py
Lib/poplib.py
Lib/selectors.py
Lib/shelve.py
Lib/smtplib.py
Lib/sunau.py
Lib/tarfile.py
Lib/telnetlib.py
Lib/tempfile.py
Lib/urllib/response.py
Lib/wave.py
Lib/xml/sax/expatreader.py
Lib/xmlrpc/client.py
Misc/NEWS

index 9e64de9c441f82e966200bedf616f399b90b717a..0b4f85a305021078cde85f0e4dc30c21c3c09bc6 100644 (file)
@@ -356,7 +356,10 @@ class Aifc_read:
         self._soundpos = 0
 
     def close(self):
-        self._file.close()
+        file = self._file
+        if file is not None:
+            self._file = None
+            file.close()
 
     def tell(self):
         return self._soundpos
index 7bf9278430fbfa71fa6ad4da6e9bf6efb4f74869..8272d5ca331abb4609d0f59a70b96021a6628140 100644 (file)
@@ -32,7 +32,8 @@ class Error(Exception):
     pass
 
 # States (what have we written)
-[_DID_HEADER, _DID_DATA, _DID_RSRC] = range(3)
+_DID_HEADER = 0
+_DID_DATA = 1
 
 # Various constants
 REASONABLY_LARGE = 32768  # Minimal amount we pass the rle-coder
@@ -213,16 +214,21 @@ class BinHex:
         self._write(data)
 
     def close(self):
-        if self.state < _DID_DATA:
-            self.close_data()
-        if self.state != _DID_DATA:
-            raise Error('Close at the wrong time')
-        if self.rlen != 0:
-            raise Error("Incorrect resource-datasize, diff=%r" % (self.rlen,))
-        self._writecrc()
-        self.ofp.close()
-        self.state = None
-        del self.ofp
+        if self.state is None:
+            return
+        try:
+            if self.state < _DID_DATA:
+                self.close_data()
+            if self.state != _DID_DATA:
+                raise Error('Close at the wrong time')
+            if self.rlen != 0:
+                raise Error("Incorrect resource-datasize, diff=%r" % (self.rlen,))
+            self._writecrc()
+        finally:
+            self.state = None
+            ofp = self.ofp
+            del self.ofp
+            ofp.close()
 
 def binhex(inp, out):
     """binhex(infilename, outfilename): create binhex-encoded copy of a file"""
@@ -436,11 +442,15 @@ class HexBin:
         return self._read(n)
 
     def close(self):
-        if self.rlen:
-            dummy = self.read_rsrc(self.rlen)
-        self._checkcrc()
-        self.state = _DID_RSRC
-        self.ifp.close()
+        if self.state is None:
+            return
+        try:
+            if self.rlen:
+                dummy = self.read_rsrc(self.rlen)
+            self._checkcrc()
+        finally:
+            self.state = None
+            self.ifp.close()
 
 def hexbin(inp, out):
     """hexbin(infilename, outfilename) - Decode binhexed file"""
index dc90a7522e3c6ce80714fe6d2c11f30521f5b741..98187b2c43ad7e5cfaabc72c8010b5ce57bc3937 100644 (file)
@@ -85,8 +85,10 @@ class Chunk:
 
     def close(self):
         if not self.closed:
-            self.skip()
-            self.closed = True
+            try:
+                self.skip()
+            finally:
+                self.closed = True
 
     def isatty(self):
         if self.closed:
index a9ead68ce45fb4498e2f7ff0dc23690359f5e3ce..63bc32942163e5e2306870cf839a62aaaaa11152 100644 (file)
@@ -248,8 +248,10 @@ class _Database(collections.MutableMapping):
             raise error('DBM object has already been closed') from None
 
     def close(self):
-        self._commit()
-        self._index = self._datfile = self._dirfile = self._bakfile = None
+        try:
+            self._commit()
+        finally:
+            self._index = self._datfile = self._dirfile = self._bakfile = None
 
     __del__ = close
 
index 40b8484a685d153a7c20f04fc3bcb6a1adb2b3f3..478336f0d2b8dccd9abd2080017ff0767963b39c 100644 (file)
@@ -118,10 +118,11 @@ class TextFile:
     def close(self):
         """Close the current file and forget everything we know about it
            (filename, current line number)."""
-        self.file.close()
+        file = self.file
         self.file = None
         self.filename = None
         self.current_line = None
+        file.close()
 
     def gen_error(self, msg, line=None):
         outmsg = []
index 87758ad82b3026e87e0391e86edd1f9fc6661a92..8af4a57f021fa5c191e8131ca0357f1bf3bebc6a 100644 (file)
@@ -238,8 +238,10 @@ class FileInput:
         self.close()
 
     def close(self):
-        self.nextfile()
-        self._files = ()
+        try:
+            self.nextfile()
+        finally:
+            self._files = ()
 
     def __enter__(self):
         return self
@@ -281,23 +283,25 @@ class FileInput:
 
         output = self._output
         self._output = 0
-        if output:
-            output.close()
-
-        file = self._file
-        self._file = 0
-        if file and not self._isstdin:
-            file.close()
-
-        backupfilename = self._backupfilename
-        self._backupfilename = 0
-        if backupfilename and not self._backup:
-            try: os.unlink(backupfilename)
-            except OSError: pass
-
-        self._isstdin = False
-        self._buffer = []
-        self._bufindex = 0
+        try:
+            if output:
+                output.close()
+        finally:
+            file = self._file
+            self._file = 0
+            try:
+                if file and not self._isstdin:
+                    file.close()
+            finally:
+                backupfilename = self._backupfilename
+                self._backupfilename = 0
+                if backupfilename and not self._backup:
+                    try: os.unlink(backupfilename)
+                    except OSError: pass
+
+                self._isstdin = False
+                self._buffer = []
+                self._bufindex = 0
 
     def readline(self):
         try:
index cd8c1a987eb6d633ec3805fc68a0e465608b8b23..4d92b86d5e46d24dd99059fcbc08c4c64da6221f 100644 (file)
@@ -667,11 +667,16 @@ class FTP:
 
     def close(self):
         '''Close the connection without assuming anything about it.'''
-        if self.file is not None:
-            self.file.close()
-        if self.sock is not None:
-            self.sock.close()
-        self.file = self.sock = None
+        try:
+            file = self.file
+            self.file = None
+            if file is not None:
+                file.close()
+        finally:
+            sock = self.sock
+            self.sock = None
+            if sock is not None:
+                sock.close()
 
 try:
     import ssl
index 8b122258095f72970b890c88b4b8f4be6cf32791..7ad00e1772c58bbcc0093f66e98d71bd13fd3467 100644 (file)
@@ -500,19 +500,21 @@ class GzipFile(io.BufferedIOBase):
         return self.fileobj is None
 
     def close(self):
-        if self.fileobj is None:
+        fileobj = self.fileobj
+        if fileobj is None:
             return
-        if self.mode == WRITE:
-            self.fileobj.write(self.compress.flush())
-            write32u(self.fileobj, self.crc)
-            # self.size may exceed 2GB, or even 4GB
-            write32u(self.fileobj, self.size & 0xffffffff)
-            self.fileobj = None
-        elif self.mode == READ:
-            self.fileobj = None
-        if self.myfileobj:
-            self.myfileobj.close()
-            self.myfileobj = None
+        self.fileobj = None
+        try:
+            if self.mode == WRITE:
+                fileobj.write(self.compress.flush())
+                write32u(fileobj, self.crc)
+                # self.size may exceed 2GB, or even 4GB
+                write32u(fileobj, self.size & 0xffffffff)
+        finally:
+            myfileobj = self.myfileobj
+            if myfileobj:
+                self.myfileobj = None
+                myfileobj.close()
 
     def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
         self._check_closed()
index b27aa5d0cf1758f0aa5026a6f785b4f8fa6904af..1c69dcb5c734af7a3e51e0206d85de500727800f 100644 (file)
@@ -492,9 +492,11 @@ class HTTPResponse(io.RawIOBase):
         fp.close()
 
     def close(self):
-        super().close() # set "closed" flag
-        if self.fp:
-            self._close_conn()
+        try:
+            super().close() # set "closed" flag
+        finally:
+            if self.fp:
+                self._close_conn()
 
     # These implementations are for the benefit of io.BufferedReader.
 
@@ -873,13 +875,17 @@ class HTTPConnection:
 
     def close(self):
         """Close the connection to the HTTP server."""
-        if self.sock:
-            self.sock.close()   # close it manually... there may be other refs
-            self.sock = None
-        if self.__response:
-            self.__response.close()
-            self.__response = None
         self.__state = _CS_IDLE
+        try:
+            sock = self.sock
+            if sock:
+                self.sock = None
+                sock.close()   # close it manually... there may be other refs
+        finally:
+            response = self.__response
+            if response:
+                self.__response = None
+                response.close()
 
     def send(self, data):
         """Send `data' to the server.
index e866b9623eb71c327b08dbf6503a25773fbb7e20..67d9d2ed76d17e9d42cd5ca3e4378576a4181068 100644 (file)
@@ -1011,14 +1011,19 @@ class FileHandler(StreamHandler):
         """
         self.acquire()
         try:
-            if self.stream:
-                self.flush()
-                if hasattr(self.stream, "close"):
-                    self.stream.close()
-                self.stream = None
-            # Issue #19523: call unconditionally to
-            # prevent a handler leak when delay is set
-            StreamHandler.close(self)
+            try:
+                if self.stream:
+                    try:
+                        self.flush()
+                    finally:
+                        stream = self.stream
+                        self.stream = None
+                        if hasattr(stream, "close"):
+                            stream.close()
+            finally:
+                # Issue #19523: call unconditionally to
+                # prevent a handler leak when delay is set
+                StreamHandler.close(self)
         finally:
             self.release()
 
index c67ac99cbf95619efaab0196d4d0263b6da8ed9b..fda809334b1f524d4da518b1f9ba5eadd24f25a6 100644 (file)
@@ -627,9 +627,10 @@ class SocketHandler(logging.Handler):
         """
         self.acquire()
         try:
-            if self.sock:
-                self.sock.close()
+            sock = self.sock
+            if sock:
                 self.sock = None
+                sock.close()
             logging.Handler.close(self)
         finally:
             self.release()
@@ -1213,8 +1214,10 @@ class BufferingHandler(logging.Handler):
 
         This version just flushes and chains to the parent class' close().
         """
-        self.flush()
-        logging.Handler.close(self)
+        try:
+            self.flush()
+        finally:
+            logging.Handler.close(self)
 
 class MemoryHandler(BufferingHandler):
     """
@@ -1268,13 +1271,15 @@ class MemoryHandler(BufferingHandler):
         """
         Flush, set the target to None and lose the buffer.
         """
-        self.flush()
-        self.acquire()
         try:
-            self.target = None
-            BufferingHandler.close(self)
+            self.flush()
         finally:
-            self.release()
+            self.acquire()
+            try:
+                self.target = None
+                BufferingHandler.close(self)
+            finally:
+                self.release()
 
 
 class QueueHandler(logging.Handler):
index 2eee76cfe50799a0b646ec387b8e1f5fff9eded4..4e42ad24063fa1fda73972717ed035a813b369a9 100644 (file)
@@ -722,10 +722,14 @@ class _singlefileMailbox(Mailbox):
 
     def close(self):
         """Flush and close the mailbox."""
-        self.flush()
-        if self._locked:
-            self.unlock()
-        self._file.close()  # Sync has been done by self.flush() above.
+        try:
+            self.flush()
+        finally:
+            try:
+                if self._locked:
+                    self.unlock()
+            finally:
+                self._file.close()  # Sync has been done by self.flush() above.
 
     def _lookup(self, key=None):
         """Return (start, stop) or raise KeyError."""
index d2715c2d703955796e4780d9c4d4a517e35f51c4..1eb1a8d89be1d34d83c677dfa07cb008e3d36549 100644 (file)
@@ -469,9 +469,10 @@ class Listener(object):
         '''
         Close the bound socket or named pipe of `self`.
         '''
-        if self._listener is not None:
-            self._listener.close()
+        listener = self._listener
+        if listener is not None:
             self._listener = None
+            listener.close()
 
     address = property(lambda self: self._listener._address)
     last_accepted = property(lambda self: self._listener._last_accepted)
@@ -609,9 +610,13 @@ class SocketListener(object):
         return Connection(s.detach())
 
     def close(self):
-        self._socket.close()
-        if self._unlink is not None:
-            self._unlink()
+        try:
+            self._socket.close()
+        finally:
+            unlink = self._unlink
+            if unlink is not None:
+                self._unlink = None
+                unlink()
 
 
 def SocketClient(address):
index c91535dd543ac397595c20de28327ba1bb3dc173..293ad7673c603c4191d47123403344369415d2e0 100644 (file)
@@ -133,9 +133,13 @@ class Queue(object):
 
     def close(self):
         self._closed = True
-        self._reader.close()
-        if self._close:
-            self._close()
+        try:
+            self._reader.close()
+        finally:
+            close = self._close
+            if close:
+                self._close = None
+                close()
 
     def join_thread(self):
         debug('Queue.join_thread()')
index 1224eacbefc6143cbf4c46a8743a613b6f1f5372..4915628b03febbc578ceafc371f0d301c6e80f38 100644 (file)
@@ -276,18 +276,23 @@ class POP3:
 
     def close(self):
         """Close the connection without assuming anything about it."""
-        if self.file is not None:
-            self.file.close()
-        if self.sock is not None:
-            try:
-                self.sock.shutdown(socket.SHUT_RDWR)
-            except OSError as e:
-                # The server might already have closed the connection
-                if e.errno != errno.ENOTCONN:
-                    raise
-            finally:
-                self.sock.close()
-        self.file = self.sock = None
+        try:
+            file = self.file
+            self.file = None
+            if file is not None:
+                file.close()
+        finally:
+            sock = self.sock
+            self.sock = None
+            if sock is not None:
+                try:
+                    sock.shutdown(socket.SHUT_RDWR)
+                except OSError as e:
+                    # The server might already have closed the connection
+                    if e.errno != errno.ENOTCONN:
+                        raise
+                finally:
+                    sock.close()
 
     #__del__ = quit
 
index beb7ef7741142c71bfcbcd118dc2171cc4375ca8..7b6da298639977fc7a7f12a75817cd9a23b4703f 100644 (file)
@@ -445,8 +445,10 @@ if hasattr(select, 'epoll'):
             return ready
 
         def close(self):
-            self._epoll.close()
-            super().close()
+            try:
+                self._epoll.close()
+            finally:
+                super().close()
 
 
 if hasattr(select, 'kqueue'):
@@ -517,8 +519,10 @@ if hasattr(select, 'kqueue'):
             return ready
 
         def close(self):
-            self._kqueue.close()
-            super().close()
+            try:
+                self._kqueue.close()
+            finally:
+                super().close()
 
 
 # Choose the best implementation: roughly, epoll|kqueue > poll > select.
index cef580e5cdca8a7f81d3c8a88a9415ce77ab087f..581baf1e6f3fb451fb15e8c300784a4eb358927d 100644 (file)
@@ -138,17 +138,21 @@ class Shelf(collections.MutableMapping):
         self.close()
 
     def close(self):
-        self.sync()
-        try:
-            self.dict.close()
-        except AttributeError:
-            pass
-        # Catch errors that may happen when close is called from __del__
-        # because CPython is in interpreter shutdown.
+        if self.dict is None:
+            return
         try:
-            self.dict = _ClosedDict()
-        except (NameError, TypeError):
-            self.dict = None
+            self.sync()
+            try:
+                self.dict.close()
+            except AttributeError:
+                pass
+        finally:
+            # Catch errors that may happen when close is called from __del__
+            # because CPython is in interpreter shutdown.
+            try:
+                self.dict = _ClosedDict()
+            except:
+                self.dict = None
 
     def __del__(self):
         if not hasattr(self, 'writeback'):
index 99ffdeeb358ff2e826d765e83b9a3ee8e2398a2f..db23ff0d20d1b42aa7542e89f7ab8d237755ca79 100755 (executable)
@@ -855,12 +855,16 @@ class SMTP:
 
     def close(self):
         """Close the connection to the SMTP server."""
-        if self.file:
-            self.file.close()
-        self.file = None
-        if self.sock:
-            self.sock.close()
-        self.sock = None
+        try:
+            file = self.file
+            self.file = None
+            if file:
+                file.close()
+        finally:
+            sock = self.sock
+            self.sock = None
+            if sock:
+                sock.close()
 
     def quit(self):
         """Terminate the SMTP session."""
index 3da41b75e4eb1cbbef0ea2f50a6f2d0e7d9e1057..e760093dd2a94513f814605bdc34bb5f2e930ae3 100644 (file)
@@ -295,9 +295,11 @@ class Au_read:
         self._soundpos = pos
 
     def close(self):
-        if self._opened and self._file:
-            self._file.close()
-        self._file = None
+        file = self._file
+        if file:
+            self._file = None
+            if self._opened:
+                file.close()
 
 class Au_write:
 
@@ -438,9 +440,10 @@ class Au_write:
                     self._patchheader()
                 self._file.flush()
             finally:
-                if self._opened and self._file:
-                    self._file.close()
+                file = self._file
                 self._file = None
+                if self._opened:
+                    file.close()
 
     #
     # private methods
index f9e5c189a1c3d7b72aa53f321e3eafcbd6193097..e3a2bb338e1b9b67bc0e69d79b0d771d9a0e13e5 100755 (executable)
@@ -449,26 +449,26 @@ class _Stream:
         if self.closed:
             return
 
-        if self.mode == "w" and self.comptype != "tar":
-            self.buf += self.cmp.flush()
-
-        if self.mode == "w" and self.buf:
-            self.fileobj.write(self.buf)
-            self.buf = b""
-            if self.comptype == "gz":
-                # The native zlib crc is an unsigned 32-bit integer, but
-                # the Python wrapper implicitly casts that to a signed C
-                # long.  So, on a 32-bit box self.crc may "look negative",
-                # while the same crc on a 64-bit box may "look positive".
-                # To avoid irksome warnings from the `struct` module, force
-                # it to look positive on all boxes.
-                self.fileobj.write(struct.pack("<L", self.crc & 0xffffffff))
-                self.fileobj.write(struct.pack("<L", self.pos & 0xffffFFFF))
-
-        if not self._extfileobj:
-            self.fileobj.close()
-
         self.closed = True
+        try:
+            if self.mode == "w" and self.comptype != "tar":
+                self.buf += self.cmp.flush()
+
+            if self.mode == "w" and self.buf:
+                self.fileobj.write(self.buf)
+                self.buf = b""
+                if self.comptype == "gz":
+                    # The native zlib crc is an unsigned 32-bit integer, but
+                    # the Python wrapper implicitly casts that to a signed C
+                    # long.  So, on a 32-bit box self.crc may "look negative",
+                    # while the same crc on a 64-bit box may "look positive".
+                    # To avoid irksome warnings from the `struct` module, force
+                    # it to look positive on all boxes.
+                    self.fileobj.write(struct.pack("<L", self.crc & 0xffffffff))
+                    self.fileobj.write(struct.pack("<L", self.pos & 0xffffFFFF))
+        finally:
+            if not self._extfileobj:
+                self.fileobj.close()
 
     def _init_read_gz(self):
         """Initialize for reading a gzip compressed fileobj.
@@ -1705,18 +1705,19 @@ class TarFile(object):
         if self.closed:
             return
 
-        if self.mode in "aw":
-            self.fileobj.write(NUL * (BLOCKSIZE * 2))
-            self.offset += (BLOCKSIZE * 2)
-            # fill up the end with zero-blocks
-            # (like option -b20 for tar does)
-            blocks, remainder = divmod(self.offset, RECORDSIZE)
-            if remainder > 0:
-                self.fileobj.write(NUL * (RECORDSIZE - remainder))
-
-        if not self._extfileobj:
-            self.fileobj.close()
         self.closed = True
+        try:
+            if self.mode in "aw":
+                self.fileobj.write(NUL * (BLOCKSIZE * 2))
+                self.offset += (BLOCKSIZE * 2)
+                # fill up the end with zero-blocks
+                # (like option -b20 for tar does)
+                blocks, remainder = divmod(self.offset, RECORDSIZE)
+                if remainder > 0:
+                    self.fileobj.write(NUL * (RECORDSIZE - remainder))
+        finally:
+            if not self._extfileobj:
+                self.fileobj.close()
 
     def getmember(self, name):
         """Return a TarInfo object for member `name'. If `name' can not be
index 0cacac85fa0e074fdd16dc42e10c9366aa941769..51738f0d125d991ab31206993322dc1b03065274 100644 (file)
@@ -264,12 +264,13 @@ class Telnet:
 
     def close(self):
         """Close the connection."""
-        if self.sock:
-            self.sock.close()
+        sock = self.sock
         self.sock = 0
         self.eof = 1
         self.iacseq = b''
         self.sb = 0
+        if sock:
+            sock.close()
 
     def get_socket(self):
         """Return the socket object used internally."""
index 5eafc1fe7aaf46a10c59ed4b4bdb7625d9306e87..a2764d3e31460bb7e13bf760c1ce5bc77175b67f 100644 (file)
@@ -357,9 +357,11 @@ class _TemporaryFileCloser:
         def close(self, unlink=_os.unlink):
             if not self.close_called and self.file is not None:
                 self.close_called = True
-                self.file.close()
-                if self.delete:
-                    unlink(self.name)
+                try:
+                    self.file.close()
+                finally:
+                    if self.delete:
+                        unlink(self.name)
 
         # Need to ensure the file is deleted on __del__
         def __del__(self):
index 4a143b03e676b27fbba0d9cb0105d34df3ae2dd3..4778118dbb17681dd3b84f67cbe1687a48d39dc9 100644 (file)
@@ -43,11 +43,15 @@ class addclosehook(addbase):
         self.hookargs = hookargs
 
     def close(self):
-        if self.closehook:
-            self.closehook(*self.hookargs)
-            self.closehook = None
-            self.hookargs = None
-        super(addclosehook, self).close()
+        try:
+            closehook = self.closehook
+            hookargs = self.hookargs
+            if closehook:
+                self.closehook = None
+                self.hookargs = None
+                closehook(*hookargs)
+        finally:
+            super(addclosehook, self).close()
 
 
 class addinfo(addbase):
index b56395ead700732925ea538a56352ad3d0b44694..8fba70f02e37de377fc676985917fc68faef12fb 100644 (file)
@@ -186,10 +186,11 @@ class Wave_read:
         self._soundpos = 0
 
     def close(self):
-        if self._i_opened_the_file:
-            self._i_opened_the_file.close()
-            self._i_opened_the_file = None
         self._file = None
+        file = self._i_opened_the_file
+        if file:
+            self._i_opened_the_file = None
+            file.close()
 
     def tell(self):
         return self._soundpos
@@ -428,17 +429,18 @@ class Wave_write:
             self._patchheader()
 
     def close(self):
-        if self._file:
-            try:
+        try:
+            if self._file:
                 self._ensure_header_written(0)
                 if self._datalength != self._datawritten:
                     self._patchheader()
                 self._file.flush()
-            finally:
-                self._file = None
-        if self._i_opened_the_file:
-            self._i_opened_the_file.close()
-            self._i_opened_the_file = None
+        finally:
+            self._file = None
+            file = self._i_opened_the_file
+            if file:
+                self._i_opened_the_file = None
+                file.close()
 
     #
     # Internal methods.
index a227cdab572f66d031f482af08a929fed48de52f..29d75ab4ac45aa3840815cb2579c5c08dfddd1e1 100644 (file)
@@ -211,17 +211,19 @@ class ExpatParser(xmlreader.IncrementalParser, xmlreader.Locator):
             self._err_handler.fatalError(exc)
 
     def close(self):
-        if self._entity_stack:
+        if self._entity_stack or self._parser is None:
             # If we are completing an external entity, do nothing here
             return
-        self.feed("", isFinal = 1)
-        self._cont_handler.endDocument()
-        self._parsing = 0
-        # break cycle created by expat handlers pointing to our methods
-        self._parser = None
-        bs = self._source.getByteStream()
-        if bs is not None:
-            bs.close()
+        try:
+            self.feed("", isFinal = 1)
+            self._cont_handler.endDocument()
+        finally:
+            self._parsing = 0
+            # break cycle created by expat handlers pointing to our methods
+            self._parser = None
+            bs = self._source.getByteStream()
+            if bs is not None:
+                bs.close()
 
     def _reset_cont_handler(self):
         self._parser.ProcessingInstructionHandler = \
index e8c1944fdb94420d21632c47dde331e7b7b2d7b3..45213258d77507ca2105375e61fbf8b5b1bb6783 100644 (file)
@@ -446,8 +446,13 @@ class ExpatParser:
         self._parser.Parse(data, 0)
 
     def close(self):
-        self._parser.Parse("", 1) # end of data
-        del self._target, self._parser # get rid of circular references
+        try:
+            parser = self._parser
+        except AttributeError:
+            pass
+        else:
+            del self._target, self._parser # get rid of circular references
+            parser.Parse(b"", True) # end of data
 
 # --------------------------------------------------------------------
 # XML-RPC marshalling and unmarshalling code
@@ -1079,8 +1084,10 @@ class GzipDecodedResponse(gzip.GzipFile if gzip else object):
         gzip.GzipFile.__init__(self, mode="rb", fileobj=self.io)
 
     def close(self):
-        gzip.GzipFile.close(self)
-        self.io.close()
+        try:
+            gzip.GzipFile.close(self)
+        finally:
+            self.io.close()
 
 
 # --------------------------------------------------------------------
@@ -1235,9 +1242,10 @@ class Transport:
     # Used in the event of socket errors.
     #
     def close(self):
-        if self._connection[1]:
-            self._connection[1].close()
+        host, connection = self._connection
+        if connection:
             self._connection = (None, None)
+            connection.close()
 
     ##
     # Send HTTP request.
index ea222f94601733f13e7444bf100903b124aa7709..539252e6823a2eac1b8b1e546153037b3fcb8e7d 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -24,6 +24,10 @@ Core and Builtins
 Library
 -------
 
+- Issue #23865: close() methods in multiple modules now are idempotent and more
+  robust at shutdown. If needs to release multiple resources, they are released
+  even if errors are occured.
+
 - Issue #23881: urllib.request.ftpwrapper constructor now closes the socket if
   the FTP connection failed to fix a ResourceWarning.