Mercurial > hg
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: |