@@ -194,6 +194,9 @@ def test_patch():
194194 assert collector ._original_lock == threading .Lock
195195
196196
197+ # Run in a subprocess: pops threading from sys.modules to simulate gevent's
198+ # cleanup_loaded_modules(), which would corrupt ModuleWatchdog state in-process.
199+ @pytest .mark .subprocess ()
197200def test_lock_patching_survives_module_reimport ():
198201 """Test that lock patches are re-applied when threading is re-imported.
199202
@@ -202,56 +205,63 @@ def test_lock_patching_survives_module_reimport():
202205 fresh, unpatched module. Without the fix, user code would get native locks.
203206 """
204207 import importlib
208+ import sys
209+ import threading
210+
211+ from ddtrace .profiling .collector ._lock import LockAllocatorWrapper
212+ from ddtrace .profiling .collector ._lock import _ProfiledLock
213+ from ddtrace .profiling .collector .threading import ThreadingLockCollector
205214
206215 collector = ThreadingLockCollector ()
207216 collector .start ()
208217
209- # Verify patching works on the current module
210218 assert isinstance (threading .Lock , LockAllocatorWrapper )
211219
212220 # Simulate cleanup_loaded_modules(): remove threading from sys.modules
213221 old_threading = sys .modules .pop ("threading" )
214- try :
215- # Re-import threading — this is what user code does after cloning
216- new_threading = importlib .import_module ("threading" )
217- assert new_threading is not old_threading , "Should be a different module object"
222+ # Re-import threading — this is what user code does after cloning
223+ new_threading = importlib .import_module ("threading" )
224+ assert new_threading is not old_threading , "Should be a different module object"
225+
226+ # The fix: patches should have been re-applied to the new module
227+ assert isinstance (new_threading .Lock , LockAllocatorWrapper ), (
228+ "Lock on re-imported threading module should be patched. "
229+ "This fails without the ModuleWatchdog re-import hook fix."
230+ )
218231
219- # The fix: patches should have been re-applied to the new module
220- assert isinstance (new_threading .Lock , LockAllocatorWrapper ), (
221- "Lock on re-imported threading module should be patched. "
222- "This fails without the ModuleWatchdog re-import hook fix."
223- )
232+ # User-created locks from the new module should be profiled
233+ lock = new_threading .Lock ()
234+ assert isinstance (lock , _ProfiledLock ), "Locks created from re-imported threading should be profiled"
224235
225- # User-created locks from the new module should be profiled
226- lock = new_threading .Lock ()
227- assert isinstance (lock , _ProfiledLock ), "Locks created from re-imported threading should be profiled"
228- finally :
229- # Restore original module to not break other tests
230- sys .modules ["threading" ] = old_threading
231- collector .stop ()
236+ collector .stop ()
232237
233238
239+ # Run in a subprocess: pops threading from sys.modules to simulate gevent's
240+ # cleanup_loaded_modules(), which would corrupt ModuleWatchdog state in-process.
241+ @pytest .mark .subprocess ()
234242def test_lock_unpatch_after_module_reimport ():
235243 """Test that stop/unpatch works correctly after module has been swapped."""
236244 import importlib
245+ import sys
246+ import threading
247+
248+ from ddtrace .profiling .collector ._lock import LockAllocatorWrapper
249+ from ddtrace .profiling .collector .threading import ThreadingLockCollector
237250
238251 collector = ThreadingLockCollector ()
239252 collector .start ()
240253 assert isinstance (threading .Lock , LockAllocatorWrapper )
241254
242255 # Simulate module swap
243- old_threading = sys .modules .pop ("threading" )
244- try :
245- new_threading = importlib .import_module ("threading" )
246- assert isinstance (new_threading .Lock , LockAllocatorWrapper )
256+ sys .modules .pop ("threading" )
257+ new_threading = importlib .import_module ("threading" )
258+ assert isinstance (new_threading .Lock , LockAllocatorWrapper )
247259
248- # Stop should unpatch the current (new) module
249- collector .stop ()
250- assert not isinstance (new_threading .Lock , LockAllocatorWrapper ), (
251- "After stop, the new threading module should be unpatched"
252- )
253- finally :
254- sys .modules ["threading" ] = old_threading
260+ # Stop should unpatch the current (new) module
261+ collector .stop ()
262+ assert not isinstance (new_threading .Lock , LockAllocatorWrapper ), (
263+ "After stop, the new threading module should be unpatched"
264+ )
255265
256266
257267@pytest .mark .parametrize (
0 commit comments