sapling/edenscm/mercurial/EdenThriftClient.py

158 lines
5.6 KiB
Python
Raw Normal View History

# Copyright (c) 2016-present, Facebook, Inc.
# All Rights Reserved.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
"""
Mercurial extension for supporting eden client checkouts.
This overrides the dirstate to check with the eden daemon for modifications,
instead of doing a normal scan of the filesystem.
"""
import errno
import os
import random
import sys
import time
import toml
from . import demandimport, node, pycompat
if sys.version_info < (2, 7, 6):
# 2.7.6 was the first version to allow unicode format strings in
# struct.{pack,unpack}; our devservers have 2.7.5, so let's
# monkey patch in support for unicode format strings.
eden: make EdenThriftClient reloadable Summary: The current code is unsafe to execute twice (in rare situations): orig_pack = struct.pack def wrap_pack(...): return orig_pack(...) # will lookup 'orig_pack' from globals() struct.pack = wrap_pack # If executed again orig_pack = struct.pack # globals()['orig_pack'] = globals()['wrap_pack'] def wrap_pack(...): # globals()['wrap_pack'] = wrap_pack return orig_pack(...) # stack overflow (!) struct.pack = wrap_pack This issue can be demonstrated in `hg debugshell`: In [1]: import edenscm.mercurial.EdenThriftClient In [2]: import struct In [3]: struct.unpack('','') Out[3]: () In [4]: reload( edenscm.mercurial.EdenThriftClient) Out[4]: <module 'edenscm.mercurial.EdenThriftClient' from '/usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc'> In [5]: struct.unpack('','') RuntimeErrorTraceback (most recent call last) /usr/lib64/python2.7/site-packages/edenscm/hgext/debugshell.pyc in <module>() ----> 1 struct.unpack('','') /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack ... last 1 frames repeated, from the frame below ... /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack RuntimeError: maximum recursion depth exceeded while calling a Python object Fix it by avoiding looking up variables from globals(). This was discovered when I was experimenting with other stuff related to the Python import logic. I also fixed the import logic so it can handle this case. Reviewed By: fanzeyi Differential Revision: D18087097 fbshipit-source-id: 472d80260ddb3d2c795b2ede5d5f1af19f3e6f2f
2019-10-24 00:54:48 +03:00
import functools
import struct
# We disable F821 below because we know we are in Python 2.x based on the
# sys.version_info check above.
eden: make EdenThriftClient reloadable Summary: The current code is unsafe to execute twice (in rare situations): orig_pack = struct.pack def wrap_pack(...): return orig_pack(...) # will lookup 'orig_pack' from globals() struct.pack = wrap_pack # If executed again orig_pack = struct.pack # globals()['orig_pack'] = globals()['wrap_pack'] def wrap_pack(...): # globals()['wrap_pack'] = wrap_pack return orig_pack(...) # stack overflow (!) struct.pack = wrap_pack This issue can be demonstrated in `hg debugshell`: In [1]: import edenscm.mercurial.EdenThriftClient In [2]: import struct In [3]: struct.unpack('','') Out[3]: () In [4]: reload( edenscm.mercurial.EdenThriftClient) Out[4]: <module 'edenscm.mercurial.EdenThriftClient' from '/usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc'> In [5]: struct.unpack('','') RuntimeErrorTraceback (most recent call last) /usr/lib64/python2.7/site-packages/edenscm/hgext/debugshell.pyc in <module>() ----> 1 struct.unpack('','') /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack ... last 1 frames repeated, from the frame below ... /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack RuntimeError: maximum recursion depth exceeded while calling a Python object Fix it by avoiding looking up variables from globals(). This was discovered when I was experimenting with other stuff related to the Python import logic. I also fixed the import logic so it can handle this case. Reviewed By: fanzeyi Differential Revision: D18087097 fbshipit-source-id: 472d80260ddb3d2c795b2ede5d5f1af19f3e6f2f
2019-10-24 00:54:48 +03:00
def pack(orig, fmt, *args):
if isinstance(fmt, unicode): # noqa: F821
fmt = fmt.encode("utf-8")
eden: make EdenThriftClient reloadable Summary: The current code is unsafe to execute twice (in rare situations): orig_pack = struct.pack def wrap_pack(...): return orig_pack(...) # will lookup 'orig_pack' from globals() struct.pack = wrap_pack # If executed again orig_pack = struct.pack # globals()['orig_pack'] = globals()['wrap_pack'] def wrap_pack(...): # globals()['wrap_pack'] = wrap_pack return orig_pack(...) # stack overflow (!) struct.pack = wrap_pack This issue can be demonstrated in `hg debugshell`: In [1]: import edenscm.mercurial.EdenThriftClient In [2]: import struct In [3]: struct.unpack('','') Out[3]: () In [4]: reload( edenscm.mercurial.EdenThriftClient) Out[4]: <module 'edenscm.mercurial.EdenThriftClient' from '/usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc'> In [5]: struct.unpack('','') RuntimeErrorTraceback (most recent call last) /usr/lib64/python2.7/site-packages/edenscm/hgext/debugshell.pyc in <module>() ----> 1 struct.unpack('','') /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack ... last 1 frames repeated, from the frame below ... /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack RuntimeError: maximum recursion depth exceeded while calling a Python object Fix it by avoiding looking up variables from globals(). This was discovered when I was experimenting with other stuff related to the Python import logic. I also fixed the import logic so it can handle this case. Reviewed By: fanzeyi Differential Revision: D18087097 fbshipit-source-id: 472d80260ddb3d2c795b2ede5d5f1af19f3e6f2f
2019-10-24 00:54:48 +03:00
return orig(fmt, *args)
eden: make EdenThriftClient reloadable Summary: The current code is unsafe to execute twice (in rare situations): orig_pack = struct.pack def wrap_pack(...): return orig_pack(...) # will lookup 'orig_pack' from globals() struct.pack = wrap_pack # If executed again orig_pack = struct.pack # globals()['orig_pack'] = globals()['wrap_pack'] def wrap_pack(...): # globals()['wrap_pack'] = wrap_pack return orig_pack(...) # stack overflow (!) struct.pack = wrap_pack This issue can be demonstrated in `hg debugshell`: In [1]: import edenscm.mercurial.EdenThriftClient In [2]: import struct In [3]: struct.unpack('','') Out[3]: () In [4]: reload( edenscm.mercurial.EdenThriftClient) Out[4]: <module 'edenscm.mercurial.EdenThriftClient' from '/usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc'> In [5]: struct.unpack('','') RuntimeErrorTraceback (most recent call last) /usr/lib64/python2.7/site-packages/edenscm/hgext/debugshell.pyc in <module>() ----> 1 struct.unpack('','') /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack ... last 1 frames repeated, from the frame below ... /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack RuntimeError: maximum recursion depth exceeded while calling a Python object Fix it by avoiding looking up variables from globals(). This was discovered when I was experimenting with other stuff related to the Python import logic. I also fixed the import logic so it can handle this case. Reviewed By: fanzeyi Differential Revision: D18087097 fbshipit-source-id: 472d80260ddb3d2c795b2ede5d5f1af19f3e6f2f
2019-10-24 00:54:48 +03:00
def unpack(orig, fmt, data):
if isinstance(fmt, unicode): # noqa: F821
fmt = fmt.encode("utf-8")
eden: make EdenThriftClient reloadable Summary: The current code is unsafe to execute twice (in rare situations): orig_pack = struct.pack def wrap_pack(...): return orig_pack(...) # will lookup 'orig_pack' from globals() struct.pack = wrap_pack # If executed again orig_pack = struct.pack # globals()['orig_pack'] = globals()['wrap_pack'] def wrap_pack(...): # globals()['wrap_pack'] = wrap_pack return orig_pack(...) # stack overflow (!) struct.pack = wrap_pack This issue can be demonstrated in `hg debugshell`: In [1]: import edenscm.mercurial.EdenThriftClient In [2]: import struct In [3]: struct.unpack('','') Out[3]: () In [4]: reload( edenscm.mercurial.EdenThriftClient) Out[4]: <module 'edenscm.mercurial.EdenThriftClient' from '/usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc'> In [5]: struct.unpack('','') RuntimeErrorTraceback (most recent call last) /usr/lib64/python2.7/site-packages/edenscm/hgext/debugshell.pyc in <module>() ----> 1 struct.unpack('','') /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack ... last 1 frames repeated, from the frame below ... /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack RuntimeError: maximum recursion depth exceeded while calling a Python object Fix it by avoiding looking up variables from globals(). This was discovered when I was experimenting with other stuff related to the Python import logic. I also fixed the import logic so it can handle this case. Reviewed By: fanzeyi Differential Revision: D18087097 fbshipit-source-id: 472d80260ddb3d2c795b2ede5d5f1af19f3e6f2f
2019-10-24 00:54:48 +03:00
return orig(fmt, data)
eden: make EdenThriftClient reloadable Summary: The current code is unsafe to execute twice (in rare situations): orig_pack = struct.pack def wrap_pack(...): return orig_pack(...) # will lookup 'orig_pack' from globals() struct.pack = wrap_pack # If executed again orig_pack = struct.pack # globals()['orig_pack'] = globals()['wrap_pack'] def wrap_pack(...): # globals()['wrap_pack'] = wrap_pack return orig_pack(...) # stack overflow (!) struct.pack = wrap_pack This issue can be demonstrated in `hg debugshell`: In [1]: import edenscm.mercurial.EdenThriftClient In [2]: import struct In [3]: struct.unpack('','') Out[3]: () In [4]: reload( edenscm.mercurial.EdenThriftClient) Out[4]: <module 'edenscm.mercurial.EdenThriftClient' from '/usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc'> In [5]: struct.unpack('','') RuntimeErrorTraceback (most recent call last) /usr/lib64/python2.7/site-packages/edenscm/hgext/debugshell.pyc in <module>() ----> 1 struct.unpack('','') /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack ... last 1 frames repeated, from the frame below ... /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack RuntimeError: maximum recursion depth exceeded while calling a Python object Fix it by avoiding looking up variables from globals(). This was discovered when I was experimenting with other stuff related to the Python import logic. I also fixed the import logic so it can handle this case. Reviewed By: fanzeyi Differential Revision: D18087097 fbshipit-source-id: 472d80260ddb3d2c795b2ede5d5f1af19f3e6f2f
2019-10-24 00:54:48 +03:00
struct.pack = functools.partial(pack, struct.pack)
struct.unpack = functools.partial(unpack, struct.unpack)
# Disable demandimport while importing thrift files.
#
# The thrift modules try importing modules which may or may not exist, and they
# handle the ImportError generated if the modules aren't present. demandimport
# breaks this behavior by making it appear like the modules were successfully
# loaded, and only throwing ImportError later when you actually try to use
# them.
with demandimport.deactivated():
import eden.thrift as eden_thrift_module
import facebook.eden.ttypes as eden_ttypes
create_thrift_client = eden_thrift_module.create_thrift_client
ScmFileStatus = eden_ttypes.ScmFileStatus
CheckoutMode = eden_ttypes.CheckoutMode
ConflictType = eden_ttypes.ConflictType
FileInformationOrError = eden_ttypes.FileInformationOrError
ManifestEntry = eden_ttypes.ManifestEntry
NoValueForKeyError = eden_ttypes.NoValueForKeyError
def readlink_retry_estale(path):
attempts = 10
while True:
try:
return os.readlink(path)
except OSError as ex:
if attempts == 0 or ex.errno != errno.ESTALE:
raise
attempts -= 1
time.sleep(random.uniform(0.001, 0.01))
class EdenThriftClient(object):
def __init__(self, repo):
self._repo = repo
self._root = repo.root
if pycompat.iswindows:
eden: make EdenThriftClient reloadable Summary: The current code is unsafe to execute twice (in rare situations): orig_pack = struct.pack def wrap_pack(...): return orig_pack(...) # will lookup 'orig_pack' from globals() struct.pack = wrap_pack # If executed again orig_pack = struct.pack # globals()['orig_pack'] = globals()['wrap_pack'] def wrap_pack(...): # globals()['wrap_pack'] = wrap_pack return orig_pack(...) # stack overflow (!) struct.pack = wrap_pack This issue can be demonstrated in `hg debugshell`: In [1]: import edenscm.mercurial.EdenThriftClient In [2]: import struct In [3]: struct.unpack('','') Out[3]: () In [4]: reload( edenscm.mercurial.EdenThriftClient) Out[4]: <module 'edenscm.mercurial.EdenThriftClient' from '/usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc'> In [5]: struct.unpack('','') RuntimeErrorTraceback (most recent call last) /usr/lib64/python2.7/site-packages/edenscm/hgext/debugshell.pyc in <module>() ----> 1 struct.unpack('','') /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack ... last 1 frames repeated, from the frame below ... /usr/lib64/python2.7/site-packages/edenscm/mercurial/EdenThriftClient.pyc in wrap_unpack(fmt, data) 43 if isinstance(fmt, unicode): # noqa: F821 44 fmt = fmt.encode("utf-8") ---> 45 return orig_unpack(fmt, data) 46 47 struct.pack = wrap_pack RuntimeError: maximum recursion depth exceeded while calling a Python object Fix it by avoiding looking up variables from globals(). This was discovered when I was experimenting with other stuff related to the Python import logic. I also fixed the import logic so it can handle this case. Reviewed By: fanzeyi Differential Revision: D18087097 fbshipit-source-id: 472d80260ddb3d2c795b2ede5d5f1af19f3e6f2f
2019-10-24 00:54:48 +03:00
tomlconfig = toml.load(os.path.join(self._root, ".eden", "config"))
self._eden_root = tomlconfig["Config"]["root"]
self._socket_path = tomlconfig["Config"]["socket"]
else:
self._socket_path = readlink_retry_estale(
os.path.join(self._root, ".eden", "socket")
)
# Read the .eden/root symlink to see what eden thinks the name of this
# mount point is. This might not match self._root in some cases. In
# particular, a parent directory of the eden mount might be bind
# mounted somewhere else, resulting in it appearing at multiple
# separate locations.
self._eden_root = readlink_retry_estale(
os.path.join(self._root, ".eden", "root")
)
def _get_client(self):
"""
Create a new client instance for each call because we may be idle
(from the perspective of the server) between calls and have our
connection snipped by the server.
We could potentially try to speculatively execute a call and
reconnect on transport failure, but for the moment this strategy
is a reasonable compromise.
"""
return create_thrift_client(socket_path=self._socket_path)
def getManifestEntry(self, relativePath):
with self._get_client() as client:
return client.getManifestEntry(self._eden_root, relativePath)
def setHgParents(self, p1, p2, need_flush=True):
if p2 == node.nullid:
p2 = None
if need_flush:
self._flushPendingTransactions()
parents = eden_ttypes.WorkingDirectoryParents(parent1=p1, parent2=p2)
with self._get_client() as client:
client.resetParentCommits(self._eden_root, parents)
def getStatus(self, parent, list_ignored): # noqa: C901
# type(str, bool) -> Dict[str, int]
# If we are in a pending transaction the parent commit we are querying against
# might not have been stored to disk yet. Flush the pending transaction state
# before asking Eden about the status.
self._flushPendingTransactions()
with self._get_client() as client:
return client.getScmStatus(self._eden_root, list_ignored, parent)
def checkout(self, node, checkout_mode, need_flush=True):
if need_flush:
self._flushPendingTransactions()
with self._get_client() as client:
return client.checkOutRevision(self._eden_root, node, checkout_mode)
def glob(self, globs):
with self._get_client() as client:
return client.glob(self._eden_root, globs)
def getFileInformation(self, files):
with self._get_client() as client:
return client.getFileInformation(self._eden_root, files)
def _flushPendingTransactions(self):
# If a transaction is currently in progress, make sure it has flushed
# pending commit data to disk so that eden will be able to access it.
txn = self._repo.currenttransaction()
if txn is not None:
txn.writepending()