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.
This commit is contained in:
Serhiy Storchaka 2015-04-10 13:29:28 +03:00
commit 2116b12da5
27 changed files with 315 additions and 209 deletions

View File

@ -356,7 +356,10 @@ class Aifc_read:
self._soundpos = 0 self._soundpos = 0
def close(self): def close(self):
self._file.close() file = self._file
if file is not None:
self._file = None
file.close()
def tell(self): def tell(self):
return self._soundpos return self._soundpos

View File

@ -32,7 +32,8 @@ class Error(Exception):
pass pass
# States (what have we written) # States (what have we written)
[_DID_HEADER, _DID_DATA, _DID_RSRC] = range(3) _DID_HEADER = 0
_DID_DATA = 1
# Various constants # Various constants
REASONABLY_LARGE = 32768 # Minimal amount we pass the rle-coder REASONABLY_LARGE = 32768 # Minimal amount we pass the rle-coder
@ -213,6 +214,9 @@ class BinHex:
self._write(data) self._write(data)
def close(self): def close(self):
if self.state is None:
return
try:
if self.state < _DID_DATA: if self.state < _DID_DATA:
self.close_data() self.close_data()
if self.state != _DID_DATA: if self.state != _DID_DATA:
@ -220,9 +224,11 @@ class BinHex:
if self.rlen != 0: if self.rlen != 0:
raise Error("Incorrect resource-datasize, diff=%r" % (self.rlen,)) raise Error("Incorrect resource-datasize, diff=%r" % (self.rlen,))
self._writecrc() self._writecrc()
self.ofp.close() finally:
self.state = None self.state = None
ofp = self.ofp
del self.ofp del self.ofp
ofp.close()
def binhex(inp, out): def binhex(inp, out):
"""binhex(infilename, outfilename): create binhex-encoded copy of a file""" """binhex(infilename, outfilename): create binhex-encoded copy of a file"""
@ -435,10 +441,14 @@ class HexBin:
return self._read(n) return self._read(n)
def close(self): def close(self):
if self.state is None:
return
try:
if self.rlen: if self.rlen:
dummy = self.read_rsrc(self.rlen) dummy = self.read_rsrc(self.rlen)
self._checkcrc() self._checkcrc()
self.state = _DID_RSRC finally:
self.state = None
self.ifp.close() self.ifp.close()
def hexbin(inp, out): def hexbin(inp, out):

View File

@ -85,7 +85,9 @@ class Chunk:
def close(self): def close(self):
if not self.closed: if not self.closed:
try:
self.skip() self.skip()
finally:
self.closed = True self.closed = True
def isatty(self): def isatty(self):

View File

@ -258,7 +258,9 @@ class _Database(collections.MutableMapping):
raise error('DBM object has already been closed') from None raise error('DBM object has already been closed') from None
def close(self): def close(self):
try:
self._commit() self._commit()
finally:
self._index = self._datfile = self._dirfile = self._bakfile = None self._index = self._datfile = self._dirfile = self._bakfile = None
__del__ = close __del__ = close

View File

@ -118,10 +118,11 @@ class TextFile:
def close(self): def close(self):
"""Close the current file and forget everything we know about it """Close the current file and forget everything we know about it
(filename, current line number).""" (filename, current line number)."""
self.file.close() file = self.file
self.file = None self.file = None
self.filename = None self.filename = None
self.current_line = None self.current_line = None
file.close()
def gen_error(self, msg, line=None): def gen_error(self, msg, line=None):
outmsg = [] outmsg = []

View File

@ -238,7 +238,9 @@ class FileInput:
self.close() self.close()
def close(self): def close(self):
try:
self.nextfile() self.nextfile()
finally:
self._files = () self._files = ()
def __enter__(self): def __enter__(self):
@ -275,22 +277,24 @@ class FileInput:
def nextfile(self): def nextfile(self):
savestdout = self._savestdout savestdout = self._savestdout
self._savestdout = 0 self._savestdout = None
if savestdout: if savestdout:
sys.stdout = savestdout sys.stdout = savestdout
output = self._output output = self._output
self._output = 0 self._output = None
try:
if output: if output:
output.close() output.close()
finally:
file = self._file file = self._file
self._file = 0 self._file = None
try:
if file and not self._isstdin: if file and not self._isstdin:
file.close() file.close()
finally:
backupfilename = self._backupfilename backupfilename = self._backupfilename
self._backupfilename = 0 self._backupfilename = None
if backupfilename and not self._backup: if backupfilename and not self._backup:
try: os.unlink(backupfilename) try: os.unlink(backupfilename)
except OSError: pass except OSError: pass

View File

@ -667,11 +667,16 @@ class FTP:
def close(self): def close(self):
'''Close the connection without assuming anything about it.''' '''Close the connection without assuming anything about it.'''
if self.file is not None: try:
self.file.close() file = self.file
if self.sock is not None: self.file = None
self.sock.close() if file is not None:
self.file = self.sock = None file.close()
finally:
sock = self.sock
self.sock = None
if sock is not None:
sock.close()
try: try:
import ssl import ssl

View File

@ -503,19 +503,21 @@ class GzipFile(io.BufferedIOBase):
return self.fileobj is None return self.fileobj is None
def close(self): def close(self):
if self.fileobj is None: fileobj = self.fileobj
if fileobj is None:
return return
self.fileobj = None
try:
if self.mode == WRITE: if self.mode == WRITE:
self.fileobj.write(self.compress.flush()) fileobj.write(self.compress.flush())
write32u(self.fileobj, self.crc) write32u(fileobj, self.crc)
# self.size may exceed 2GB, or even 4GB # self.size may exceed 2GB, or even 4GB
write32u(self.fileobj, self.size & 0xffffffff) write32u(fileobj, self.size & 0xffffffff)
self.fileobj = None finally:
elif self.mode == READ: myfileobj = self.myfileobj
self.fileobj = None if myfileobj:
if self.myfileobj:
self.myfileobj.close()
self.myfileobj = None self.myfileobj = None
myfileobj.close()
def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH): def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
self._check_closed() self._check_closed()

View File

@ -388,7 +388,9 @@ class HTTPResponse(io.BufferedIOBase):
fp.close() fp.close()
def close(self): def close(self):
try:
super().close() # set "closed" flag super().close() # set "closed" flag
finally:
if self.fp: if self.fp:
self._close_conn() self._close_conn()
@ -829,13 +831,17 @@ class HTTPConnection:
def close(self): def close(self):
"""Close the connection to the HTTP server.""" """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 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): def send(self, data):
"""Send `data' to the server. """Send `data' to the server.

View File

@ -1012,12 +1012,17 @@ class FileHandler(StreamHandler):
Closes the stream. Closes the stream.
""" """
self.acquire() self.acquire()
try:
try: try:
if self.stream: if self.stream:
try:
self.flush() self.flush()
if hasattr(self.stream, "close"): finally:
self.stream.close() stream = self.stream
self.stream = None self.stream = None
if hasattr(stream, "close"):
stream.close()
finally:
# Issue #19523: call unconditionally to # Issue #19523: call unconditionally to
# prevent a handler leak when delay is set # prevent a handler leak when delay is set
StreamHandler.close(self) StreamHandler.close(self)

View File

@ -627,9 +627,10 @@ class SocketHandler(logging.Handler):
""" """
self.acquire() self.acquire()
try: try:
if self.sock: sock = self.sock
self.sock.close() if sock:
self.sock = None self.sock = None
sock.close()
logging.Handler.close(self) logging.Handler.close(self)
finally: finally:
self.release() self.release()
@ -1213,7 +1214,9 @@ class BufferingHandler(logging.Handler):
This version just flushes and chains to the parent class' close(). This version just flushes and chains to the parent class' close().
""" """
try:
self.flush() self.flush()
finally:
logging.Handler.close(self) logging.Handler.close(self)
class MemoryHandler(BufferingHandler): class MemoryHandler(BufferingHandler):
@ -1268,7 +1271,9 @@ class MemoryHandler(BufferingHandler):
""" """
Flush, set the target to None and lose the buffer. Flush, set the target to None and lose the buffer.
""" """
try:
self.flush() self.flush()
finally:
self.acquire() self.acquire()
try: try:
self.target = None self.target = None

View File

@ -722,9 +722,13 @@ class _singlefileMailbox(Mailbox):
def close(self): def close(self):
"""Flush and close the mailbox.""" """Flush and close the mailbox."""
try:
self.flush() self.flush()
finally:
try:
if self._locked: if self._locked:
self.unlock() self.unlock()
finally:
self._file.close() # Sync has been done by self.flush() above. self._file.close() # Sync has been done by self.flush() above.
def _lookup(self, key=None): def _lookup(self, key=None):
@ -1966,8 +1970,10 @@ class _ProxyFile:
def close(self): def close(self):
"""Close the file.""" """Close the file."""
if hasattr(self, '_file'): if hasattr(self, '_file'):
try:
if hasattr(self._file, 'close'): if hasattr(self._file, 'close'):
self._file.close() self._file.close()
finally:
del self._file del self._file
def _read(self, size, read_method): def _read(self, size, read_method):

View File

@ -460,9 +460,10 @@ class Listener(object):
''' '''
Close the bound socket or named pipe of `self`. Close the bound socket or named pipe of `self`.
''' '''
if self._listener is not None: listener = self._listener
self._listener.close() if listener is not None:
self._listener = None self._listener = None
listener.close()
address = property(lambda self: self._listener._address) address = property(lambda self: self._listener._address)
last_accepted = property(lambda self: self._listener._last_accepted) last_accepted = property(lambda self: self._listener._last_accepted)
@ -594,9 +595,13 @@ class SocketListener(object):
return Connection(s.detach()) return Connection(s.detach())
def close(self): def close(self):
try:
self._socket.close() self._socket.close()
if self._unlink is not None: finally:
self._unlink() unlink = self._unlink
if unlink is not None:
self._unlink = None
unlink()
def SocketClient(address): def SocketClient(address):

View File

@ -130,9 +130,13 @@ class Queue(object):
def close(self): def close(self):
self._closed = True self._closed = True
try:
self._reader.close() self._reader.close()
if self._close: finally:
self._close() close = self._close
if close:
self._close = None
close()
def join_thread(self): def join_thread(self):
debug('Queue.join_thread()') debug('Queue.join_thread()')

View File

@ -276,18 +276,23 @@ class POP3:
def close(self): def close(self):
"""Close the connection without assuming anything about it.""" """Close the connection without assuming anything about it."""
if self.file is not None:
self.file.close()
if self.sock is not None:
try: try:
self.sock.shutdown(socket.SHUT_RDWR) 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: except OSError as e:
# The server might already have closed the connection # The server might already have closed the connection
if e.errno != errno.ENOTCONN: if e.errno != errno.ENOTCONN:
raise raise
finally: finally:
self.sock.close() sock.close()
self.file = self.sock = None
#__del__ = quit #__del__ = quit

View File

@ -439,7 +439,9 @@ if hasattr(select, 'epoll'):
return ready return ready
def close(self): def close(self):
try:
self._epoll.close() self._epoll.close()
finally:
super().close() super().close()
@ -496,7 +498,9 @@ if hasattr(select, 'devpoll'):
return ready return ready
def close(self): def close(self):
try:
self._devpoll.close() self._devpoll.close()
finally:
super().close() super().close()
@ -566,7 +570,9 @@ if hasattr(select, 'kqueue'):
return ready return ready
def close(self): def close(self):
try:
self._kqueue.close() self._kqueue.close()
finally:
super().close() super().close()

View File

@ -138,16 +138,20 @@ class Shelf(collections.MutableMapping):
self.close() self.close()
def close(self): def close(self):
if self.dict is None:
return
try:
self.sync() self.sync()
try: try:
self.dict.close() self.dict.close()
except AttributeError: except AttributeError:
pass pass
finally:
# Catch errors that may happen when close is called from __del__ # Catch errors that may happen when close is called from __del__
# because CPython is in interpreter shutdown. # because CPython is in interpreter shutdown.
try: try:
self.dict = _ClosedDict() self.dict = _ClosedDict()
except (NameError, TypeError): except:
self.dict = None self.dict = None
def __del__(self): def __del__(self):

View File

@ -880,12 +880,16 @@ class SMTP:
def close(self): def close(self):
"""Close the connection to the SMTP server.""" """Close the connection to the SMTP server."""
if self.file: try:
self.file.close() file = self.file
self.file = None self.file = None
if self.sock: if file:
self.sock.close() file.close()
finally:
sock = self.sock
self.sock = None self.sock = None
if sock:
sock.close()
def quit(self): def quit(self):
"""Terminate the SMTP session.""" """Terminate the SMTP session."""

View File

@ -295,9 +295,11 @@ class Au_read:
self._soundpos = pos self._soundpos = pos
def close(self): def close(self):
if self._opened and self._file: file = self._file
self._file.close() if file:
self._file = None self._file = None
if self._opened:
file.close()
class Au_write: class Au_write:
@ -438,9 +440,10 @@ class Au_write:
self._patchheader() self._patchheader()
self._file.flush() self._file.flush()
finally: finally:
if self._opened and self._file: file = self._file
self._file.close()
self._file = None self._file = None
if self._opened:
file.close()
# #
# private methods # private methods

View File

@ -449,6 +449,8 @@ class _Stream:
if self.closed: if self.closed:
return return
self.closed = True
try:
if self.mode == "w" and self.comptype != "tar": if self.mode == "w" and self.comptype != "tar":
self.buf += self.cmp.flush() self.buf += self.cmp.flush()
@ -464,12 +466,10 @@ class _Stream:
# it to look positive on all boxes. # it to look positive on all boxes.
self.fileobj.write(struct.pack("<L", self.crc & 0xffffffff)) self.fileobj.write(struct.pack("<L", self.crc & 0xffffffff))
self.fileobj.write(struct.pack("<L", self.pos & 0xffffFFFF)) self.fileobj.write(struct.pack("<L", self.pos & 0xffffFFFF))
finally:
if not self._extfileobj: if not self._extfileobj:
self.fileobj.close() self.fileobj.close()
self.closed = True
def _init_read_gz(self): def _init_read_gz(self):
"""Initialize for reading a gzip compressed fileobj. """Initialize for reading a gzip compressed fileobj.
""" """
@ -1714,6 +1714,8 @@ class TarFile(object):
if self.closed: if self.closed:
return return
self.closed = True
try:
if self.mode in "aw": if self.mode in "aw":
self.fileobj.write(NUL * (BLOCKSIZE * 2)) self.fileobj.write(NUL * (BLOCKSIZE * 2))
self.offset += (BLOCKSIZE * 2) self.offset += (BLOCKSIZE * 2)
@ -1722,10 +1724,9 @@ class TarFile(object):
blocks, remainder = divmod(self.offset, RECORDSIZE) blocks, remainder = divmod(self.offset, RECORDSIZE)
if remainder > 0: if remainder > 0:
self.fileobj.write(NUL * (RECORDSIZE - remainder)) self.fileobj.write(NUL * (RECORDSIZE - remainder))
finally:
if not self._extfileobj: if not self._extfileobj:
self.fileobj.close() self.fileobj.close()
self.closed = True
def getmember(self, name): def getmember(self, name):
"""Return a TarInfo object for member `name'. If `name' can not be """Return a TarInfo object for member `name'. If `name' can not be

View File

@ -261,12 +261,13 @@ class Telnet:
def close(self): def close(self):
"""Close the connection.""" """Close the connection."""
if self.sock: sock = self.sock
self.sock.close() self.sock = None
self.sock = 0 self.eof = True
self.eof = 1
self.iacseq = b'' self.iacseq = b''
self.sb = 0 self.sb = 0
if sock:
sock.close()
def get_socket(self): def get_socket(self):
"""Return the socket object used internally.""" """Return the socket object used internally."""

View File

@ -357,7 +357,9 @@ class _TemporaryFileCloser:
def close(self, unlink=_os.unlink): def close(self, unlink=_os.unlink):
if not self.close_called and self.file is not None: if not self.close_called and self.file is not None:
self.close_called = True self.close_called = True
try:
self.file.close() self.file.close()
finally:
if self.delete: if self.delete:
unlink(self.name) unlink(self.name)

View File

@ -43,10 +43,14 @@ class addclosehook(addbase):
self.hookargs = hookargs self.hookargs = hookargs
def close(self): def close(self):
if self.closehook: try:
self.closehook(*self.hookargs) closehook = self.closehook
hookargs = self.hookargs
if closehook:
self.closehook = None self.closehook = None
self.hookargs = None self.hookargs = None
closehook(*hookargs)
finally:
super(addclosehook, self).close() super(addclosehook, self).close()

View File

@ -186,10 +186,11 @@ class Wave_read:
self._soundpos = 0 self._soundpos = 0
def close(self): def close(self):
if self._i_opened_the_file:
self._i_opened_the_file.close()
self._i_opened_the_file = None
self._file = None self._file = None
file = self._i_opened_the_file
if file:
self._i_opened_the_file = None
file.close()
def tell(self): def tell(self):
return self._soundpos return self._soundpos
@ -428,17 +429,18 @@ class Wave_write:
self._patchheader() self._patchheader()
def close(self): def close(self):
if self._file:
try: try:
if self._file:
self._ensure_header_written(0) self._ensure_header_written(0)
if self._datalength != self._datawritten: if self._datalength != self._datawritten:
self._patchheader() self._patchheader()
self._file.flush() self._file.flush()
finally: finally:
self._file = None self._file = None
if self._i_opened_the_file: file = self._i_opened_the_file
self._i_opened_the_file.close() if file:
self._i_opened_the_file = None self._i_opened_the_file = None
file.close()
# #
# Internal methods. # Internal methods.

View File

@ -211,11 +211,13 @@ class ExpatParser(xmlreader.IncrementalParser, xmlreader.Locator):
self._err_handler.fatalError(exc) self._err_handler.fatalError(exc)
def close(self): 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 # If we are completing an external entity, do nothing here
return return
try:
self.feed("", isFinal = 1) self.feed("", isFinal = 1)
self._cont_handler.endDocument() self._cont_handler.endDocument()
finally:
self._parsing = 0 self._parsing = 0
# break cycle created by expat handlers pointing to our methods # break cycle created by expat handlers pointing to our methods
self._parser = None self._parser = None

View File

@ -438,8 +438,13 @@ class ExpatParser:
self._parser.Parse(data, 0) self._parser.Parse(data, 0)
def close(self): def close(self):
self._parser.Parse("", 1) # end of data try:
parser = self._parser
except AttributeError:
pass
else:
del self._target, self._parser # get rid of circular references del self._target, self._parser # get rid of circular references
parser.Parse(b"", True) # end of data
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# XML-RPC marshalling and unmarshalling code # XML-RPC marshalling and unmarshalling code
@ -1065,7 +1070,9 @@ class GzipDecodedResponse(gzip.GzipFile if gzip else object):
gzip.GzipFile.__init__(self, mode="rb", fileobj=self.io) gzip.GzipFile.__init__(self, mode="rb", fileobj=self.io)
def close(self): def close(self):
try:
gzip.GzipFile.close(self) gzip.GzipFile.close(self)
finally:
self.io.close() self.io.close()
@ -1221,9 +1228,10 @@ class Transport:
# Used in the event of socket errors. # Used in the event of socket errors.
# #
def close(self): def close(self):
if self._connection[1]: host, connection = self._connection
self._connection[1].close() if connection:
self._connection = (None, None) self._connection = (None, None)
connection.close()
## ##
# Send HTTP request. # Send HTTP request.

View File

@ -19,6 +19,10 @@ Core and Builtins
Library 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 #23400: Raise same exception on both Python 2 and 3 if sem_open is not - Issue #23400: Raise same exception on both Python 2 and 3 if sem_open is not
available. Patch by Davin Potts. available. Patch by Davin Potts.