Skip to content

fix(di): dynamic function discovery fallback #13947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions ddtrace/debugging/_function/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,43 @@ def at_line(self, line: int) -> List[FullyNamedFunction]:

return []

def _resolve_pair(self, pair: _FunctionCodePair, fullname: str) -> FullyNamedFunction:
try:
return pair.resolve()
except ValueError as e:
# We failed to lookup the function by its code object via the GC.
# This should not happen, unless the target application is doing
# something unusual with the GC. In this case we try our best to
# look up the function dynamically from the module. If the function
# is decorated, there are chances this might fail.
target = (module := self._module)
part = None
for part in fullname.replace(f"{module.__name__}.", "", 1).split("."):
try:
target = getattr(target, part)
except AttributeError:
raise e

if target is module:
# We didn't move from the module so we don't have a function.
raise e

code = pair.code
assert code is not None # nosec
f = undecorated(cast(FunctionType, target), cast(str, part), Path(code.co_filename).resolve())
if not (isinstance(f, FunctionType) and f.__code__ is code):
raise e

# Cache the lookup result
pair.function = f

return cast(FullyNamedFunction, f)

def by_name(self, qualname: str) -> FullyNamedFunction:
"""Get the function by its qualified name."""
fullname = f"{self._module.__name__}.{qualname}"
try:
return self._fullname_index[fullname].resolve()
return self._resolve_pair(self._fullname_index[fullname], fullname)
except ValueError:
pass
except KeyError:
Expand All @@ -342,11 +374,11 @@ def by_name(self, qualname: str) -> FullyNamedFunction:
if qualname == name or qualname.endswith(f".{name}"):
for fcp in list(fcps):
try:
f = fcp.resolve()
f = self._resolve_pair(fcp, fullname)

# We have resolved the function so we can now
# get its full name
self._fullname_index[f"{self._module.__name__}.{f.__qualname__}"] = fcp
self._fullname_index[fullname] = fcp

# We can remove the entry from the name index
fcps.pop(0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
dynamic instrumentation: improve support for function probes with frameworks
and applications that interact with the Python garbage collector (e.g.
synapse).
Loading