File tree Expand file tree Collapse file tree 5 files changed +60
-2
lines changed Expand file tree Collapse file tree 5 files changed +60
-2
lines changed Original file line number Diff line number Diff line change @@ -139,7 +139,10 @@ def resolve(self) -> FullyNamedFunction:
139139 msg = "Cannot resolve pair with no code object"
140140 raise ValueError (msg )
141141
142- if self .function is not None :
142+ # Check that the function we have cached is not a wrapper layer that
143+ # has been unwrapped. In this case we need to resolve the new function
144+ # from the code object.
145+ if (_ := self .function ) is not None and _ .__name__ != "<unwrapped>" :
143146 return cast (FullyNamedFunction , self .function )
144147
145148 code = self .code
Original file line number Diff line number Diff line change @@ -322,6 +322,13 @@ def unwrap(wf, wrapper):
322322 # current one.
323323 f = cast (FunctionType , wf )
324324 f .__code__ = inner .__code__
325+
326+ # Mark the function as unwrapped via its name. There might be references
327+ # to this function elsewhere and this would signal that the function has
328+ # been unwrapped and that another function object is referencing the
329+ # original code object.
330+ inner .__name__ = "<unwrapped>"
331+
325332 try :
326333 # Update the link to the next layer.
327334 inner_wf = cast (WrappedFunction , inner )
Original file line number Diff line number Diff line change @@ -394,6 +394,12 @@ def wrap(self) -> None:
394394 if self ._trampoline is not None :
395395 return
396396
397+ # If the function is already universally wrapped so it's less expensive
398+ # to do the normal wrapping.
399+ if _UniversalWrappingContext .is_wrapped (self .__wrapped__ ):
400+ super ().wrap ()
401+ return
402+
397403 def trampoline (_ , args , kwargs ):
398404 with tl :
399405 f = t .cast (WrappedFunction , self .__wrapped__ )
Original file line number Diff line number Diff line change @@ -177,7 +177,7 @@ def assert_single_snapshot(self):
177177
178178 assert len (self .test_queue ) == 1
179179
180- yield self .test_queue [ 0 ]
180+ yield self .test_queue . pop ( 0 )
181181
182182
183183@contextmanager
Original file line number Diff line number Diff line change @@ -642,6 +642,48 @@ def test_debugger_line_probe_on_lazy_wrapped_function(stuff):
642642 assert snapshot .probe .probe_id == "line-probe-lazy-wrapping"
643643
644644
645+ def test_debugger_function_probe_on_lazy_wrapped_function (stuff ):
646+ from ddtrace .internal .wrapping .context import LazyWrappingContext
647+
648+ class LWC (LazyWrappingContext ):
649+ def __enter__ (self ):
650+ self .entered = True
651+ return super ().__enter__ ()
652+
653+ (c := LWC (stuff .Stuff .instancestuff )).wrap ()
654+
655+ with debugger () as d :
656+ d .add_probes (
657+ probe := create_snapshot_function_probe (
658+ probe_id = "function-probe-lazy-wrapping" ,
659+ module = "tests.submod.stuff" ,
660+ func_qname = "Stuff.instancestuff" ,
661+ rate = float ("inf" ),
662+ )
663+ )
664+
665+ stuff .Stuff ().instancestuff (42 )
666+
667+ d .remove_probes (probe )
668+
669+ assert c .entered
670+
671+ with d .assert_single_snapshot () as snapshot :
672+ assert snapshot .probe .probe_id == "function-probe-lazy-wrapping"
673+
674+ # Test that we can re-instrument the function correctly and that we
675+ # don't accidentally instrument the temporary trampoline instead.
676+
677+ d .add_probes (probe )
678+
679+ stuff .Stuff ().instancestuff (42 )
680+
681+ d .remove_probes (probe )
682+
683+ with d .assert_single_snapshot () as snapshot :
684+ assert snapshot .probe .probe_id == "function-probe-lazy-wrapping"
685+
686+
645687def test_probe_status_logging (remote_config_worker , stuff ):
646688 assert remoteconfig_poller .status == ServiceStatus .STOPPED
647689
You can’t perform that action at this time.
0 commit comments