|
| 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