comparison mercurial/util.py @ 14007:d764463b433e

atomictempfile: avoid infinite recursion in __del__(). The problem is that a programmer using atomictempfile directly can make an innocent everyday mistake -- not enough args to the constructor -- which escalates badly. You would expect a simple TypeError crash in that case, but you actually get an infinite recursion that is surprisingly difficult to kill: it happens between __del__() and __getattr__(), and Python does not handle infinite recursion from __del__() well. The fix is to not implement __getattr__(), but instead assign instance attributes for the methods we wish to delegate to the builtin file type: write() and fileno(). I've audited mercurial.* and hgext.* and found no users of atomictempfile using methods other than write() and rename(). I audited third-party extensions and found one (snap) passing an atomictempfile to util.fstat(), so I also threw in fileno(). The last time I submitted a similar patch, Matt proposed that we make atomictempfile a subclass of file instead of wrapping it. Rejected on grounds of unnecessary complexity: for one thing, it would make the Windows implementation of posixfile quite a bit more complex. It would have to become a subclass of file rather than a simple function -- but since it's written in C, this is non-obvious and non-trivial. Furthermore, there's nothing wrong with wrapping objects and delegating methods: it's a well-established pattern that works just fine in many cases. Subclassing is not the answer to all of life's problems.
author Greg Ward <greg@gerg.ca>
date Sun, 24 Apr 2011 19:25:10 -0400
parents 97ed99d1f419
children da65edcac72a
comparison
equal deleted inserted replaced
14006:a395575691a6 14007:d764463b433e
724 All writes will be redirected to a temporary copy of the original 724 All writes will be redirected to a temporary copy of the original
725 file. When rename is called, the copy is renamed to the original 725 file. When rename is called, the copy is renamed to the original
726 name, making the changes visible. 726 name, making the changes visible.
727 """ 727 """
728 def __init__(self, name, mode='w+b', createmode=None): 728 def __init__(self, name, mode='w+b', createmode=None):
729 self.__name = name 729 self.__name = name # permanent name
730 self._fp = None 730 self._tempname = mktempcopy(name, emptyok=('w' in mode),
731 self.temp = mktempcopy(name, emptyok=('w' in mode), 731 createmode=createmode)
732 createmode=createmode) 732 self._fp = posixfile(self._tempname, mode)
733 self._fp = posixfile(self.temp, mode) 733
734 734 # delegated methods
735 def __getattr__(self, name): 735 self.write = self._fp.write
736 return getattr(self._fp, name) 736 self.fileno = self._fp.fileno
737 737
738 def rename(self): 738 def rename(self):
739 if not self._fp.closed: 739 if not self._fp.closed:
740 self._fp.close() 740 self._fp.close()
741 rename(self.temp, localpath(self.__name)) 741 rename(self._tempname, localpath(self.__name))
742 742
743 def close(self): 743 def close(self):
744 if not self._fp:
745 return
746 if not self._fp.closed: 744 if not self._fp.closed:
747 try: 745 try:
748 os.unlink(self.temp) 746 os.unlink(self._tempname)
749 except: pass 747 except OSError:
748 pass
750 self._fp.close() 749 self._fp.close()
751 750
752 def __del__(self): 751 def __del__(self):
753 self.close() 752 if hasattr(self, '_fp'): # constructor actually did something
753 self.close()
754 754
755 def makedirs(name, mode=None): 755 def makedirs(name, mode=None):
756 """recursive directory creation with parent mode inheritance""" 756 """recursive directory creation with parent mode inheritance"""
757 parent = os.path.abspath(os.path.dirname(name)) 757 parent = os.path.abspath(os.path.dirname(name))
758 try: 758 try: