[PATCH 1 of 2] prevent transient leaks of file handle by using new helper functions

Dan Villiom Podlaski Christiansen danchr at gmail.com
Sun May 1 05:32:39 CDT 2011


# HG changeset patch
# User Dan Villiom Podlaski Christiansen <danchr at gmail.com>
# Date 1304243231 -7200
# Node ID ddebfda06862e0cb975ccf69a12c5a27214ad41f
# Parent  52dadaaebd0f0f3de85a3e2d5829a4ac1399f1f9
prevent transient leaks of file handle by using new helper functions

These leaks may occur in environments that don't employ a reference
counting GC, i.e. PyPy.

This implies:
 - changing opener(...).read() calls to opener.read(...)
 - changing opener(...).write() calls to opener.write(...)
 - changing open(...).read(...) to util.readfile(...)
 - changing open(...).write(...) to util.writefile(...)

diff --git a/hgext/convert/darcs.py b/hgext/convert/darcs.py
--- a/hgext/convert/darcs.py
+++ b/hgext/convert/darcs.py
@@ -191,7 +191,7 @@ class darcs_source(converter_source, com
         if rev != self.lastrev:
             raise util.Abort(_('internal calling inconsistency'))
         path = os.path.join(self.tmppath, name)
-        data = open(path, 'rb').read()
+        data = util.readfile(path, 'rb')
         mode = os.lstat(path).st_mode
         mode = (mode & 0111) and 'x' or ''
         return data, mode
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -1025,7 +1025,7 @@ class svn_sink(converter_sink, commandli
                     os.unlink(filename)
             except OSError:
                 pass
-            self.wopener(filename, 'w').write(data)
+            self.wopener.write(data, filename, 'w')
 
             if self.is_exec:
                 was_exec = self.is_exec(self.wjoin(filename))
diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -97,7 +97,7 @@ def snapshot(ui, repo, files, node, tmpr
         if 'l' in fctx.flags():
             wopener.symlink(data, wfn)
         else:
-            wopener(wfn, 'w').write(data)
+            wopener.write(data, wfn, 'w')
             if 'x' in fctx.flags():
                 util.set_flags(dest, False, True)
         if node is None:
diff --git a/hgext/gpg.py b/hgext/gpg.py
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -234,7 +234,7 @@ def sign(ui, repo, *revs, **opts):
 
     # write it
     if opts['local']:
-        repo.opener("localsigs", "ab").write(sigmessage)
+        repo.opener.write(sigmessage, "localsigs", "ab")
         return
 
     msigs = match.exact(repo.root, '', ['.hgsigs'])
diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -413,7 +413,7 @@ def demo(ui, repo, *args, **opts):
     demoitems('keywordset', ui.configitems('keywordset'))
     demoitems('keywordmaps', kwmaps.iteritems())
     keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
-    repo.wopener(fn, 'w').write(keywords)
+    repo.wopener.write(keywords, fn, 'w')
     repo[None].add([fn])
     ui.note(_('\nkeywords written to %s:\n') % fn)
     ui.note(keywords)
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -291,14 +291,14 @@ class queue(object):
                     elif l.strip():
                         self.ui.warn(_('malformated mq status line: %s\n') % entry)
                     # else we ignore empty lines
-            lines = self.opener(self.status_path).read().splitlines()
+            lines = self.opener.read(self.status_path).splitlines()
             return list(parselines(lines))
         return []
 
     @util.propertycache
     def full_series(self):
         if os.path.exists(self.join(self.series_path)):
-            return self.opener(self.series_path).read().splitlines()
+            return self.opener.read(self.series_path).splitlines()
         return []
 
     @util.propertycache
@@ -412,7 +412,7 @@ class queue(object):
         if self.active_guards is None:
             self.active_guards = []
             try:
-                guards = self.opener(self.guards_path).read().split()
+                guards = self.opener.read(self.guards_path).split()
             except IOError, err:
                 if err.errno != errno.ENOENT:
                     raise
diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -39,7 +39,7 @@ class transplants(object):
     def read(self):
         abspath = os.path.join(self.path, self.transplantfile)
         if self.transplantfile and os.path.exists(abspath):
-            for line in self.opener(self.transplantfile).read().splitlines():
+            for line in self.opener.read(self.transplantfile).splitlines():
                 lnode, rnode = map(revlog.bin, line.split(':'))
                 list = self.transplants.setdefault(rnode, [])
                 list.append(transplantentry(lnode, rnode))
@@ -318,7 +318,7 @@ class transplanter(object):
         nodes = []
         merges = []
         cur = nodes
-        for line in self.opener('series').read().splitlines():
+        for line in self.opener.read('series').splitlines():
             if line.startswith('# Merges'):
                 cur = merges
                 continue
diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py
--- a/mercurial/bookmarks.py
+++ b/mercurial/bookmarks.py
@@ -72,12 +72,12 @@ def write(repo):
     refs = repo._bookmarks
 
     try:
-        bms = repo.opener('bookmarks').read()
+        bms = repo.opener.read('bookmarks')
     except IOError, inst:
         if inst.errno != errno.ENOENT:
             raise
         bms = ''
-    repo.opener('undo.bookmarks', 'w').write(bms)
+    repo.opener.write(bms, 'undo.bookmarks', 'w')
 
     if repo._bookmarkcurrent not in refs:
         setcurrent(repo, None)
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -91,7 +91,7 @@ def logmessage(opts):
             if logfile == '-':
                 message = sys.stdin.read()
             else:
-                message = open(logfile).read()
+                message = util.readfile(logfile)
         except IOError, inst:
             raise util.Abort(_("can't read commit message '%s': %s") %
                              (logfile, inst.strerror))
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1110,7 +1110,7 @@ def debugcomplete(ui, cmd='', **opts):
 
 def debugfsinfo(ui, path = "."):
     """show information detected about current filesystem"""
-    open('.debugfsinfo', 'w').write('')
+    util.writefile('.debugfsinfo', 'w', '')
     ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
     ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
     ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
@@ -2582,7 +2582,8 @@ def import_(ui, repo, patch1, *patches, 
                 raise util.Abort(_('no diffs found'))
 
         if msgs:
-            repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
+            repo.opener.write('\n* * *\n'.join(msgs),
+                              'last-message.txt', 'wb')
     finally:
         release(lock, wlock)
 
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -74,7 +74,7 @@ class dirstate(object):
     @propertycache
     def _branch(self):
         try:
-            return self._opener("branch").read().strip() or "default"
+            return self._opener.read("branch").strip() or "default"
         except IOError:
             return "default"
 
@@ -220,13 +220,13 @@ class dirstate(object):
         if branch in ['tip', '.', 'null']:
             raise util.Abort(_('the name \'%s\' is reserved') % branch)
         self._branch = encoding.fromlocal(branch)
-        self._opener("branch", "w").write(self._branch + '\n')
+        self._opener.write(self._branch + '\n', "branch", "w")
 
     def _read(self):
         self._map = {}
         self._copymap = {}
         try:
-            st = self._opener("dirstate").read()
+            st = self._opener.read("dirstate")
         except IOError, err:
             if err.errno != errno.ENOENT:
                 raise
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -113,14 +113,14 @@ def _eoltype(data):
 
 def _matcheol(file, origfile):
     "Convert EOL markers in a file to match origfile"
-    tostyle = _eoltype(open(origfile, "rb").read())
+    tostyle = _eoltype(util.readfile(origfile, "rb"))
     if tostyle:
-        data = open(file, "rb").read()
+        data = util.readfile(file, "rb")
         style = _eoltype(data)
         if style:
             newdata = data.replace(style, tostyle)
             if newdata != data:
-                open(file, "wb").write(newdata)
+                util.writefile(file, "wb", newdata)
 
 def filemerge(repo, mynode, orig, fcd, fco, fca):
     """perform a 3-way merge in the working directory
diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -8,6 +8,7 @@
 from i18n import gettext, _
 import sys, os
 import extensions
+import util
 
 
 def moduledoc(file):
@@ -79,7 +80,7 @@ def loaddoc(topic):
                 break
 
         path = os.path.join(docdir, topic + ".txt")
-        doc = gettext(open(path).read())
+        doc = gettext(util.readfile(path))
         for rewriter in helphooks.get(topic, []):
             doc = rewriter(topic, doc)
         return doc
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -137,14 +137,14 @@ def share(ui, source, dest=None, update=
 
     requirements = ''
     try:
-        requirements = srcrepo.opener('requires').read()
+        requirements = srcrepo.opener.read('requires')
     except IOError, inst:
         if inst.errno != errno.ENOENT:
             raise
 
     requirements += 'shared\n'
-    file(os.path.join(roothg, 'requires'), 'w').write(requirements)
-    file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
+    util.writefile(os.path.join(roothg, 'requires'), 'w', requirements)
+    util.writefile(os.path.join(roothg, 'sharedpath'), 'w', sharedpath)
 
     default = srcrepo.ui.config('paths', 'default')
     if default:
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -56,9 +56,10 @@ class localrepository(repo.repository):
                         if self.ui.configbool('format', 'dotencode', True):
                             requirements.append('dotencode')
                     # create an invalid changelog
-                    self.opener("00changelog.i", "a").write(
+                    self.opener.write(
                         '\0\0\0\2' # represents revlogv2
-                        ' dummy changelog to prevent using the old repo layout'
+                        ' dummy changelog to prevent using the old repo layout',
+                        "00changelog.i", "a"
                     )
                 if self.ui.configbool('format', 'parentdelta', False):
                     requirements.append("parentdelta")
@@ -70,7 +71,7 @@ class localrepository(repo.repository):
             # find requirements
             requirements = set()
             try:
-                requirements = set(self.opener("requires").read().splitlines())
+                requirements = set(self.opener.read("requires").splitlines())
             except IOError, inst:
                 if inst.errno != errno.ENOENT:
                     raise
@@ -80,7 +81,7 @@ class localrepository(repo.repository):
 
         self.sharedpath = self.path
         try:
-            s = os.path.realpath(self.opener("sharedpath").read())
+            s = os.path.realpath(self.opener.read("sharedpath"))
             if not os.path.exists(s):
                 raise error.RepoError(
                     _('.hg/sharedpath points to nonexistent directory %s') % s)
@@ -652,7 +653,7 @@ class localrepository(repo.repository):
         if self._link(filename):
             data = os.readlink(self.wjoin(filename))
         else:
-            data = self.wopener(filename, 'r').read()
+            data = self.wopener.read(filename, 'r')
         return self._filter(self._encodefilterpats, filename, data)
 
     def wwrite(self, filename, data, flags):
@@ -660,7 +661,7 @@ class localrepository(repo.repository):
         if 'l' in flags:
             self.wopener.symlink(data, filename)
         else:
-            self.wopener(filename, 'w').write(data)
+            fp = self.wopener.write(data, filename, 'w')
             if 'x' in flags:
                 util.set_flags(self.wjoin(filename), False, True)
 
@@ -679,13 +680,14 @@ class localrepository(repo.repository):
 
         # save dirstate for rollback
         try:
-            ds = self.opener("dirstate").read()
+            ds = self.opener.read("dirstate")
         except IOError:
             ds = ""
-        self.opener("journal.dirstate", "w").write(ds)
-        self.opener("journal.branch", "w").write(
-            encoding.fromlocal(self.dirstate.branch()))
-        self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
+        self.opener.write(ds, "journal.dirstate", "w")
+        self.opener.write(encoding.fromlocal(self.dirstate.branch()),
+                          "journal.branch", "w")
+        self.opener.write("%d\n%s\n" % (len(self), desc),
+                          "journal.desc", "w")
 
         renames = [(self.sjoin("journal"), self.sjoin("undo")),
                    (self.join("journal.dirstate"), self.join("undo.dirstate")),
@@ -720,7 +722,7 @@ class localrepository(repo.repository):
             lock = self.lock()
             if os.path.exists(self.sjoin("undo")):
                 try:
-                    args = self.opener("undo.desc", "r").read().splitlines()
+                    args = self.opener.read("undo.desc", "r").splitlines()
                     if len(args) >= 3 and self.ui.verbose:
                         desc = _("repository tip rolled back to revision %s"
                                  " (undo %s: %s)\n") % (
@@ -741,7 +743,7 @@ class localrepository(repo.repository):
                     util.rename(self.join('undo.bookmarks'),
                                 self.join('bookmarks'))
                 try:
-                    branch = self.opener("undo.branch").read()
+                    branch = self.opener.read("undo.branch")
                     self.dirstate.setbranch(branch)
                 except IOError:
                     self.ui.warn(_("named branch could not be reset, "
diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -275,7 +275,7 @@ def _normalize(names, default, root, cwd
         elif kind in ('listfile', 'listfile0'):
             delimiter = kind == 'listfile0' and '\0' or '\n'
             try:
-                files = open(name, 'r').read().split(delimiter)
+                files = readfile(name).split(delimiter)
                 files = [f for f in files if f]
             except EnvironmentError:
                 raise util.Abort(_("unable to read file list (%s)") % name)
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -47,7 +47,7 @@ class mergestate(object):
             self._dirty = False
     def add(self, fcl, fco, fca, fd, flags):
         hash = util.sha1(fcl.path()).hexdigest()
-        self._repo.opener("merge/" + hash, "w").write(fcl.data())
+        self._repo.opener.write(fcl.data(), "merge/" + hash, "w")
         self._state[fd] = ['u', hash, fcl.path(), fca.path(),
                            hex(fca.filenode()), fco.path(), flags]
         self._dirty = True
diff --git a/mercurial/minirst.py b/mercurial/minirst.py
--- a/mercurial/minirst.py
+++ b/mercurial/minirst.py
@@ -467,7 +467,7 @@ if __name__ == "__main__":
         print
         return blocks
 
-    text = open(sys.argv[1]).read()
+    text = util.readfile(sys.argv[1])
     blocks = debug(findblocks, text)
     blocks = debug(findliteralblocks, blocks)
     blocks, pruned = debug(prunecontainers, blocks, sys.argv[2:])
diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -93,7 +93,7 @@ class statichttprepository(localrepo.loc
 
         # find requirements
         try:
-            requirements = self.opener("requires").read().splitlines()
+            requirements = self.opener.read("requires").splitlines()
         except IOError, inst:
             if inst.errno != errno.ENOENT:
                 raise
diff --git a/mercurial/tags.py b/mercurial/tags.py
--- a/mercurial/tags.py
+++ b/mercurial/tags.py
@@ -60,7 +60,7 @@ def findglobaltags(ui, repo, alltags, ta
 def readlocaltags(ui, repo, alltags, tagtypes):
     '''Read local tags in repo.  Update alltags and tagtypes.'''
     try:
-        data = repo.opener("localtags").read()
+        data = repo.opener.read("localtags")
     except IOError, inst:
         if inst.errno != errno.ENOENT:
             raise
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -311,7 +311,7 @@ class templater(object):
         '''Get the template for the given template name. Use a local cache.'''
         if not t in self.cache:
             try:
-                self.cache[t] = open(self.map[t][1]).read()
+                self.cache[t] = util.readfile(self.map[t][1])
             except KeyError, inst:
                 raise util.Abort(_('"%s" not in template map') % inst.args[0])
             except IOError, inst:
diff --git a/tests/test-symlink-os-yes-fs-no.py b/tests/test-symlink-os-yes-fs-no.py
--- a/tests/test-symlink-os-yes-fs-no.py
+++ b/tests/test-symlink-os-yes-fs-no.py
@@ -1,5 +1,5 @@
 import os, sys, time
-from mercurial import hg, ui, commands
+from mercurial import hg, ui, commands, util
 
 TESTDIR = os.environ["TESTDIR"]
 
@@ -28,7 +28,7 @@ os.symlink = symlink_failure
 for f in 'test0/a.lnk', 'test0/d/b.lnk':
     os.unlink(f)
     fp = open(f, 'wb')
-    fp.write(open(f[:-4]).read())
+    fp.write(util.readfile(f[:-4]))
     fp.close()
 
 # reload repository


More information about the Mercurial-devel mailing list