hgext/fsmonitor/pywatchman/compat.py
author Gregory Szorc <gregory.szorc@gmail.com>
Mon, 20 Jan 2020 23:51:25 -0800
changeset 44118 f81c17ec303c
parent 43385 6469c23a40a2
child 48966 6000f5b25c9b
permissions -rw-r--r--
hgdemandimport: apply lazy module loading to sys.meta_path finders Python's `sys.meta_path` finders are the primary objects whose job it is to find a module at import time. When `import` is called, Python iterates objects in this list and calls `o.find_spec(...)` to find a `ModuleSpec` (or None if the module couldn't be found by that finder). If no meta path finder can find a module, import fails. One of the default meta path finders is `PathFinder`. Its job is to import modules from the filesystem and is probably the most important importer. This finder looks at `sys.path` and `sys.path_hooks` to do its job. The `ModuleSpec` returned by `MetaPathImporter.find_spec()` has a `loader` attribute, which defines the concrete module loader to use. `sys.path_hooks` is a hook point for teaching `PathFinder` to instantiate custom loader types. Previously, we injected a custom `sys.path_hook` that told `PathFinder` to wrap the default loaders with a loader that creates a module object that is lazy. This approach worked. But its main limitation was that it only applied to the `PathFinder` meta path importer. There are other meta path importers that are registered. And in the case of PyOxidizer loading modules from memory, `PathFinder` doesn't come into play since PyOxidizer's own meta path importer was handling all imports. This commit changes our approach to lazy module loading by proxying all meta path importers. Specifically, we overload the `find_spec()` method to swap in a wrapped loader on the `ModuleSpec` before it is returned. The end result of this is all meta path importers should be lazy. As much as I would have loved to utilize .__class__ manipulation to achieve this, some meta path importers are implemented in C/Rust in such a way that they cannot be monkeypatched. This is why we use __getattribute__ to define a proxy. Also, this change could theoretically open us up to regressions in meta path importers whose loader is creating module objects which can't be monkeypatched. But I'm not aware of any of these in the wild. So I think we'll be safe. According to hyperfine, this change yields a decent startup time win of 5-6ms: ``` Benchmark #1: ~/.pyenv/versions/3.6.10/bin/python ./hg version Time (mean ± σ): 86.8 ms ± 0.5 ms [User: 78.0 ms, System: 8.7 ms] Range (min … max): 86.0 ms … 89.1 ms 50 runs Time (mean ± σ): 81.1 ms ± 2.7 ms [User: 74.5 ms, System: 6.5 ms] Range (min … max): 77.8 ms … 90.5 ms 50 runs Benchmark #2: ~/.pyenv/versions/3.7.6/bin/python ./hg version Time (mean ± σ): 78.9 ms ± 0.6 ms [User: 70.2 ms, System: 8.7 ms] Range (min … max): 78.1 ms … 81.2 ms 50 runs Time (mean ± σ): 73.4 ms ± 0.6 ms [User: 65.3 ms, System: 8.0 ms] Range (min … max): 72.4 ms … 75.7 ms 50 runs Benchmark #3: ~/.pyenv/versions/3.8.1/bin/python ./hg version Time (mean ± σ): 78.1 ms ± 0.6 ms [User: 70.2 ms, System: 7.9 ms] Range (min … max): 77.4 ms … 80.9 ms 50 runs Time (mean ± σ): 72.1 ms ± 0.4 ms [User: 64.4 ms, System: 7.6 ms] Range (min … max): 71.4 ms … 74.1 ms 50 runs ``` Differential Revision: https://phab.mercurial-scm.org/D7954
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
30659
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     1
# Copyright 2016-present Facebook, Inc.
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     2
# All rights reserved.
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     3
#
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     4
# Redistribution and use in source and binary forms, with or without
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     5
# modification, are permitted provided that the following conditions are met:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     6
#
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     7
#  * Redistributions of source code must retain the above copyright notice,
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     8
#    this list of conditions and the following disclaimer.
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
     9
#
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    10
#  * Redistributions in binary form must reproduce the above copyright notice,
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    11
#    this list of conditions and the following disclaimer in the documentation
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    12
#    and/or other materials provided with the distribution.
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    13
#
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    14
#  * Neither the name Facebook nor the names of its contributors may be used to
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    15
#    endorse or promote products derived from this software without specific
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    16
#    prior written permission.
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    17
#
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    19
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    22
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    23
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    24
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    25
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    26
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    28
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    29
# no unicode literals
43385
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    30
from __future__ import absolute_import, division, print_function
30659
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    31
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    32
import sys
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    33
43385
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    34
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    35
"""Compatibility module across Python 2 and 3."""
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    36
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    37
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    38
PYTHON2 = sys.version_info < (3, 0)
30659
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    39
PYTHON3 = sys.version_info >= (3, 0)
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    40
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    41
# This is adapted from https://bitbucket.org/gutworth/six, and used under the
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    42
# MIT license. See LICENSE for a full copyright notice.
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    43
if PYTHON3:
43385
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    44
30659
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    45
    def reraise(tp, value, tb=None):
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    46
        try:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    47
            if value is None:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    48
                value = tp()
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    49
            if value.__traceback__ is not tb:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    50
                raise value.with_traceback(tb)
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    51
            raise value
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    52
        finally:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    53
            value = None
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    54
            tb = None
43385
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    55
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    56
30659
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    57
else:
43385
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    58
    exec(
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    59
        """
30659
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    60
def reraise(tp, value, tb=None):
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    61
    try:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    62
        raise tp, value, tb
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    63
    finally:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    64
        tb = None
43385
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    65
""".strip()
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    66
    )
30659
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    67
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    68
if PYTHON3:
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    69
    UNICODE = str
16f4b341288d fsmonitor: refresh pywatchman to upstream
Zack Hricz <zphricz@fb.com>
parents:
diff changeset
    70
else:
43385
6469c23a40a2 fsmonitor: refresh pywatchman with upstream
Gregory Szorc <gregory.szorc@gmail.com>
parents: 30659
diff changeset
    71
    UNICODE = unicode  # noqa: F821 We handled versioning above