| 
 | 1 | +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.  | 
 | 2 | +# SPDX-License-Identifier: Apache-2.0  | 
 | 3 | + | 
 | 4 | +import os  | 
 | 5 | +from importlib.metadata import PackageNotFoundError, version  | 
 | 6 | +from logging import Logger, getLogger  | 
 | 7 | + | 
 | 8 | +from packaging.requirements import Requirement  | 
 | 9 | + | 
 | 10 | +_logger: Logger = getLogger(__name__)  | 
 | 11 | + | 
 | 12 | +# Env variable to control Gevent monkey patching behavior in ADOT.  | 
 | 13 | +# Read more about the Gevent monkey patching: https://www.gevent.org/intro.html#monkey-patching  | 
 | 14 | +# Possible values are 'all', 'none', and  | 
 | 15 | +# comma separated list 'os, thread, time, sys, socket, select, ssl, subprocess, builtins, signal, queue, contextvars'.  | 
 | 16 | +# When set to 'none', gevent's monkey patching is skipped.  | 
 | 17 | +# When set to 'all' (default behavior), gevent patch is executed for all modules as per  | 
 | 18 | +# https://www.gevent.org/api/gevent.monkey.html#gevent.monkey.patch_all.  | 
 | 19 | +# When set to a comma separated list of modules, only those are processed for gevent's patch.  | 
 | 20 | +AWS_GEVENT_PATCH_MODULES = "AWS_GEVENT_PATCH_MODULES"  | 
 | 21 | + | 
 | 22 | + | 
 | 23 | +def _is_gevent_installed() -> bool:  | 
 | 24 | +    """Is the gevent package installed?"""  | 
 | 25 | +    req = Requirement("gevent")  | 
 | 26 | +    try:  | 
 | 27 | +        dist_version = version(req.name)  | 
 | 28 | +        _logger.debug("Gevent is installed: %s", dist_version)  | 
 | 29 | +    except PackageNotFoundError as exc:  | 
 | 30 | +        _logger.debug("Gevent is not installed. %s", exc)  | 
 | 31 | +        return False  | 
 | 32 | +    return True  | 
 | 33 | + | 
 | 34 | + | 
 | 35 | +def apply_gevent_monkey_patch():  | 
 | 36 | +    # This patch differs from other instrumentation patches in this directory as it addresses  | 
 | 37 | +    # application compatibility rather than telemetry functionality. It prevents breaking user  | 
 | 38 | +    # applications that run on Gevent and use libraries like boto3, requests, or urllib3 when  | 
 | 39 | +    # instrumented with ADOT.  | 
 | 40 | +    #  | 
 | 41 | +    # Without this patch, users encounter "RecursionError: maximum recursion depth exceeded"  | 
 | 42 | +    # because by the time Gevent monkey-patches modules (such as ssl), those modules have already  | 
 | 43 | +    # been imported by ADOT. Specifically, aws_xray_remote_sampler imports requests, which  | 
 | 44 | +    # transitively imports ssl, leaving these modules in an inconsistent state for Gevent.  | 
 | 45 | +    #  | 
 | 46 | +    # Gevent recommends monkey-patching as early as possible:  | 
 | 47 | +    # https://www.gevent.org/intro.html#monkey-patching  | 
 | 48 | +    #  | 
 | 49 | +    # Since ADOT initialization occurs before user application code, we perform the monkey-patch  | 
 | 50 | +    # here to ensure proper module state for Gevent-based applications.  | 
 | 51 | + | 
 | 52 | +    # Only apply the gevent monkey patch if gevent is installed is user application space.  | 
 | 53 | +    if _is_gevent_installed():  | 
 | 54 | +        try:  | 
 | 55 | +            gevent_patch_module = os.environ.get(AWS_GEVENT_PATCH_MODULES, "all")  | 
 | 56 | + | 
 | 57 | +            if gevent_patch_module != "none":  | 
 | 58 | +                # pylint: disable=import-outside-toplevel  | 
 | 59 | +                # Delay import to only occur if monkey patch is needed (e.g. gevent is used to run application).  | 
 | 60 | +                from gevent import monkey  | 
 | 61 | + | 
 | 62 | +                if gevent_patch_module == "all":  | 
 | 63 | +                    monkey.patch_all()  | 
 | 64 | +                else:  | 
 | 65 | +                    module_list = [module.strip() for module in gevent_patch_module.split(",")]  | 
 | 66 | + | 
 | 67 | +                    monkey.patch_all(  | 
 | 68 | +                        socket="socket" in module_list,  | 
 | 69 | +                        time="time" in module_list,  | 
 | 70 | +                        select="select" in module_list,  | 
 | 71 | +                        thread="thread" in module_list,  | 
 | 72 | +                        os="os" in module_list,  | 
 | 73 | +                        ssl="ssl" in module_list,  | 
 | 74 | +                        subprocess="subprocess" in module_list,  | 
 | 75 | +                        sys="sys" in module_list,  | 
 | 76 | +                        builtins="builtins" in module_list,  | 
 | 77 | +                        signal="signal" in module_list,  | 
 | 78 | +                        queue="queue" in module_list,  | 
 | 79 | +                        contextvars="contextvars" in module_list,  | 
 | 80 | +                    )  | 
 | 81 | +        except Exception as exc:  # pylint: disable=broad-except  | 
 | 82 | +            _logger.error("Failed to monkey patch gevent, exception: %s", exc)  | 
0 commit comments