Fix: release _ChipWorker handle in Worker.close() L2 branch#1222
Conversation
The L2 teardown branch called self._chip_worker.finalize() but never dropped the Python reference, so the _ChipWorker nanobind instance stayed alive on the closed Worker. This is inconsistent with the L>=3 branch (self._worker = None) and the error path (self._chip_worker = None). When a pytest case fails, pytest retains its traceback, which pins the failing frame's worker local and through it the _ChipWorker instance until interpreter exit — where nanobind's leak check fires and dumps the "leaked instance ... _ChipWorker" / leaked types / leaked functions warning. Dropping the handle on close releases the instance so it no longer outlives the module. Closes hw-native-sys#1221
There was a problem hiding this comment.
Code Review
This pull request updates the close method in python/simpler/worker.py to set self._chip_worker to None after calling its finalize method when self.level is 2. There are no review comments, and I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughIn ChangesStale reference fix in Worker.close()
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~2 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
How to Trigger a Genuine Local Dump
To reproduce a real leak dump, the target instance must survive the entire finalization workflow. The tracebacks generated by failed test cases can trigger this leak because they pin stack frames and create persistent references that CPython cannot collect automatically. I use an equivalent method — Before the Fix (Buggy Version — This output exactly matches the leak dump attached to the issue ticket ( After the Fix (Corrected Version — Exact Code from PR HEAD) The
Further Experimental Test: Can Clearing Registries Eliminate the Dump Completely? if self.level == 2:
if self._chip_worker:
self._chip_worker.finalize()
self._chip_worker = None
self._callable_registry.clear() # Newly added line
self._identity_registry.clear() # Newly added line
self._live_handles.clear() # Newly added lineWhen re-running the identical Three-Way Comparison (Official nanobind Exit Leak Check)
In short: The module can only be unloaded cleanly with all leak noise eliminated if all nanobind handles retained by the Worker after
|
Summary
Worker.close()L2 branch calledself._chip_worker.finalize()but never dropped the Python reference, leaving the_ChipWorkernanobind instance alive on the closed Worker.self._chip_worker = Noneafterfinalize(), mirroring the L>=3 branch (self._worker = None) and the error path (which already does this).Why
When a pytest case fails, pytest retains its traceback, which strongly references the failing frame's
workerlocal and through it the_ChipWorkerinstance — surviving until interpreter exit, where nanobind's leak check fires:(nanobind dumps the whole types/functions list because it cannot cleanly unload the module while any one instance is live.) This is a benign teardown-ordering artifact, not a runtime C++ leak, but it is noisy in CI and masks future real refcount regressions. Dropping the handle on close releases the instance so it no longer outlives the module.
Testing
examples/workers/l2/vector_addona2a3sim(exercises L2 register/init/run/close), no leak dump.Closes #1221