|
15 | 15 | # perform the correct initialisation for the library. All the actual |
16 | 16 | # initialisation logic should be placed in preload.py. |
17 | 17 | import ddtrace # isort:skip |
18 | | -import logging # noqa:I001 |
19 | 18 | import os # noqa:F401 |
20 | 19 | import sys |
21 | | -import warnings # noqa:F401 |
22 | 20 |
|
23 | | -from ddtrace import config # noqa:F401 |
| 21 | +import ddtrace.bootstrap.cloning as cloning |
24 | 22 | from ddtrace.internal.logger import get_logger # noqa:F401 |
25 | | -from ddtrace.internal.module import ModuleWatchdog # noqa:F401 |
26 | | -from ddtrace.internal.module import is_module_installed |
27 | 23 | from ddtrace.internal.telemetry import telemetry_writer |
28 | | -from ddtrace.internal.utils.formats import asbool # noqa:F401 |
29 | 24 |
|
30 | 25 |
|
31 | 26 | log = get_logger(__name__) |
32 | 27 |
|
33 | 28 |
|
34 | | -if "gevent" in sys.modules or "gevent.monkey" in sys.modules: |
35 | | - import gevent.monkey # noqa:F401 |
36 | | - |
37 | | - if gevent.monkey.is_module_patched("threading"): |
38 | | - warnings.warn( # noqa: B028 |
39 | | - "Loading ddtrace after gevent.monkey.patch_all() is not supported and is " |
40 | | - "likely to break the application. Use ddtrace-run to fix this, or " |
41 | | - "import ddtrace.auto before calling gevent.monkey.patch_all().", |
42 | | - RuntimeWarning, |
43 | | - ) |
44 | | - |
45 | | - |
46 | | -def cleanup_loaded_modules(): |
47 | | - def drop(module_name): |
48 | | - # type: (str) -> None |
49 | | - module = sys.modules.get(module_name) |
50 | | - # Don't delete modules that are currently being imported (they might be None or incomplete) |
51 | | - # or that don't exist. This can happen when pytest's assertion rewriter is importing modules |
52 | | - # that themselves import ddtrace.auto, which triggers this cleanup during the import process. |
53 | | - if module is None: |
54 | | - return |
55 | | - # Skip modules that don't have a __spec__ attribute yet (still being imported) |
56 | | - if not hasattr(module, "__spec__"): |
57 | | - return |
58 | | - # Check if the module is currently being initialized |
59 | | - # During import, __spec__._initializing is True |
60 | | - spec = getattr(module, "__spec__", None) |
61 | | - if spec is not None and getattr(spec, "_initializing", False): |
62 | | - return |
63 | | - del sys.modules[module_name] |
64 | | - |
65 | | - MODULES_REQUIRING_CLEANUP = ("gevent",) |
66 | | - do_cleanup = os.getenv("DD_UNLOAD_MODULES_FROM_SITECUSTOMIZE", default="auto").lower() |
67 | | - if do_cleanup == "auto": |
68 | | - do_cleanup = any(is_module_installed(m) for m in MODULES_REQUIRING_CLEANUP) |
69 | | - |
70 | | - if not asbool(do_cleanup): |
71 | | - return |
72 | | - |
73 | | - # We need to import these modules to make sure they grab references to the |
74 | | - # right modules before we start unloading stuff. |
75 | | - import ddtrace.internal.http # noqa |
76 | | - import ddtrace.internal.uds # noqa |
77 | | - |
78 | | - # Unload all the modules that we have imported, except for the ddtrace one. |
79 | | - # NB: this means that every `import threading` anywhere in `ddtrace/` code |
80 | | - # uses a copy of that module that is distinct from the copy that user code |
81 | | - # gets when it does `import threading`. The same applies to every module |
82 | | - # not in `KEEP_MODULES`. |
83 | | - KEEP_MODULES = frozenset( |
84 | | - [ |
85 | | - "atexit", |
86 | | - "copyreg", # pickling issues for tracebacks with gevent |
87 | | - "ddtrace", |
88 | | - "concurrent", |
89 | | - "typing", |
90 | | - "_operator", # pickling issues with typing module |
91 | | - "re", # referenced by the typing module |
92 | | - "sre_constants", # imported by re at runtime |
93 | | - "logging", |
94 | | - "attr", |
95 | | - "google", |
96 | | - "google.protobuf", # the upb backend in >= 4.21 does not like being unloaded |
97 | | - "wrapt", |
98 | | - "bytecode", # needed by before-fork hooks |
99 | | - ] |
100 | | - ) |
101 | | - for m in list(_ for _ in sys.modules if _ not in ddtrace.LOADED_MODULES): |
102 | | - if any(m == _ or m.startswith(_ + ".") for _ in KEEP_MODULES): |
103 | | - continue |
104 | | - |
105 | | - drop(m) |
106 | | - |
107 | | - # TODO: The better strategy is to identify the core modules in LOADED_MODULES |
108 | | - # that should not be unloaded, and then unload as much as possible. |
109 | | - UNLOAD_MODULES = frozenset( |
110 | | - [ |
111 | | - # imported in Python >= 3.10 and patched by gevent |
112 | | - "time", |
113 | | - # we cannot unload the whole concurrent hierarchy, but this |
114 | | - # submodule makes use of threading so it is critical to unload when |
115 | | - # gevent is used. |
116 | | - "concurrent.futures", |
117 | | - # We unload the threading module in case it was imported by |
118 | | - # CPython on boot. |
119 | | - "threading", |
120 | | - "_thread", |
121 | | - ] |
122 | | - ) |
123 | | - for u in UNLOAD_MODULES: |
124 | | - for m in list(sys.modules): |
125 | | - if m == u or m.startswith(u + "."): |
126 | | - drop(m) |
127 | | - |
128 | | - # Because we are not unloading it, the logging module requires a reference |
129 | | - # to the newly imported threading module to allow it to retrieve the correct |
130 | | - # thread object information, like the thread name. We register a post-import |
131 | | - # hook on the threading module to perform this update. |
132 | | - @ModuleWatchdog.after_module_imported("threading") |
133 | | - def _(threading): |
134 | | - logging.threading = threading |
135 | | - |
136 | | - |
137 | 29 | try: |
138 | 30 | import ddtrace.bootstrap.preload as preload # Perform the actual initialisation |
139 | 31 |
|
140 | | - cleanup_loaded_modules() |
| 32 | + cloning.cleanup_loaded_modules() |
141 | 33 |
|
142 | 34 | # Check for and import any sitecustomize that would have normally been used |
143 | 35 | # had ddtrace-run not been used. |
|
0 commit comments