[PATCH STABLE] posix: insert seek between reads and writes on Solaris (issue4943)

Gregory Szorc gregory.szorc at gmail.com
Sun Nov 15 22:21:12 UTC 2015


# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1447625312 28800
#      Sun Nov 15 14:08:32 2015 -0800
# Branch stable
# Node ID 52360879f54f28b774be7fc8f7e207768d5851b6
# Parent  8a256cee72c890c761918ec1d244b286694ac51b
posix: insert seek between reads and writes on Solaris (issue4943)

At least some versions of Solaris fail to properly write to file
descriptors opened for both reading and writing. When the descriptor
is seeked and read, subsequent writes effectively disappear into a
black hole.

The underlying cause of this is unknown. Some versions of Solaris
appear to work. Others don't. Read the issue comments for more
details.

The issue can be worked around by inserting an extra seek between
read and write operations. This exact workaround is already in place
for Windows, where the MSDN docs call out this requirement. See
3686fa2b8eee.

This patch copies the existing workaround code into posix.py and
makes it active for Solaris only.

Yes, we copied a large block of source code. This is not ideal.
However, posix.py and windows.py are very low level modules. There
is no obvious existing module we could add the function to without
introducing an import cycle. A new module will likely need to be
invented. I'm not comfortable doing that on the stable branch.
Once this patch is merged into @, we should clean up the duplicated
code.

diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -11,8 +11,9 @@ import errno
 import fcntl
 import getpass
 import grp
 import os
+import platform
 import pwd
 import re
 import select
 import socket
@@ -25,9 +26,8 @@ from .i18n import _
 from . import (
     encoding,
 )
 
-posixfile = open
 normpath = os.path.normpath
 samestat = os.path.samestat
 oslink = os.link
 unlink = os.unlink
@@ -37,8 +37,86 @@ expandglobs = False
 
 umask = os.umask(0)
 os.umask(umask)
 
+# Some Solaris versions don't properly handle reads() + writes() on the same
+# file descriptor. When running on Solaris, insert an extra seek() between
+# reads and writes on dual mode file descriptors to prevent this bug.
+# See also issue4943.
+if platform.system() == 'SunOS':
+    # TODO consolidate with duplicate code in windows.py.
+    class mixedfilemodewrapper(object):
+        """Insert seek between reads and writes.
+
+        This class wraps posixfile instances when the file is opened in
+        read/write mode and automatically adds checks or inserts appropriate
+        file positioning calls when necessary.
+        """
+        OPNONE = 0
+        OPREAD = 1
+        OPWRITE = 2
+
+        def __init__(self, fp):
+            object.__setattr__(self, '_fp', fp)
+            object.__setattr__(self, '_lastop', 0)
+
+        def __getattr__(self, name):
+            return getattr(self._fp, name)
+
+        def __setattr__(self, name, value):
+            return self._fp.__setattr__(name, value)
+
+        def _noopseek(self):
+            self._fp.seek(0, os.SEEK_CUR)
+
+        def seek(self, *args, **kwargs):
+            object.__setattr__(self, '_lastop', self.OPNONE)
+            return self._fp.seek(*args, **kwargs)
+
+        def write(self, d):
+            if self._lastop == self.OPREAD:
+                self._noopseek()
+
+            object.__setattr__(self, '_lastop', self.OPWRITE)
+            return self._fp.write(d)
+
+        def writelines(self, *args, **kwargs):
+            if self._lastop == self.OPREAD:
+                self._noopeseek()
+
+            object.__setattr__(self, '_lastop', self.OPWRITE)
+            return self._fp.writelines(*args, **kwargs)
+
+        def read(self, *args, **kwargs):
+            if self._lastop == self.OPWRITE:
+                self._noopseek()
+
+            object.__setattr__(self, '_lastop', self.OPREAD)
+            return self._fp.read(*args, **kwargs)
+
+        def readline(self, *args, **kwargs):
+            if self._lastop == self.OPWRITE:
+                self._noopseek()
+
+            object.__setattr__(self, '_lastop', self.OPREAD)
+            return self._fp.readline(*args, **kwargs)
+
+        def readlines(self, *args, **kwargs):
+            if self._lastop == self.OPWRITE:
+                self._noopseek()
+
+            object.__setattr__(self, '_lastop', self.OPREAD)
+            return self._fp.readlines(*args, **kwargs)
+
+    def posixfile(name, mode='r', buffering=-1):
+        fp = open(name, mode, buffering)
+        if '+' in mode:
+            return mixedfilemodewrapper(fp)
+
+        return fp
+else:
+    posixfile = open
+
 def split(p):
     '''Same as posixpath.split, but faster
 
     >>> import posixpath
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -26,8 +26,9 @@ testpid = win32.testpid
 unlink = win32.unlink
 
 umask = 0o022
 
+# TODO consolidate with duplicate code in posix.py.
 class mixedfilemodewrapper(object):
     """Wraps a file handle when it is opened in read/write mode.
 
     fopen() and fdopen() on Windows have a specific-to-Windows requirement


More information about the Mercurial-devel mailing list