Split out from #61 as a pre-existing bug that predates PR #96 and is out-of-scope for the coverage work.
Symptom
Observed during the test_coverage CI run (e.g. https://github.com/ChengLabResearch/ouroboros/actions/runs/28622389980) as a PytestUnraisableExceptionWarning:
test/helpers/test_mem.py::test_direct_create
.../unraisableexception.py: PytestUnraisableExceptionWarning: Exception ignored in: <function SharedNPArray.__del__ at 0x...>
Traceback (most recent call last):
File ".../python/ouroboros/helpers/mem.py", line 76, in __del__
if not self.__shutdown:
^^^^^^^^^^^^^^^
AttributeError: 'SharedNPArray' object has no attribute '_SharedNPArray__shutdown'
Cause
SharedNPArray.__init__ (mem.py:24) sets self.__shutdown = False at line 36 — after several other attribute assignments that can raise (SharedMemory(name), np.dtype(dtype), etc.). When any of those raises, the partially-constructed instance still gets __del__'d during GC, and __del__ at line 76 references self.__shutdown before it was assigned. Python swallows the AttributeError into an unraisable warning.
Reproduction
The warning also fires without an exception if a SharedNPArray is created via object.__new__(SharedNPArray) (e.g. some pickle round-trips or object reconstruction paths) — but the more common trigger is an exception mid-__init__.
Suggested fix
Two options, either works:
-
Class-level default (preferred — minimal diff):
class SharedNPArray:
_SharedNPArray__shutdown = True # or set at class body scope as: __shutdown = True
def __init__(self, ...):
...
self.__shutdown = False # flip to False after all mandatory attrs are set
Set the class-level default to True so __del__ treats an unconstructed instance as already-shut-down.
-
Guard __del__:
def __del__(self):
if not getattr(self, "_SharedNPArray__shutdown", True):
self.shutdown()
Option 1 is cleaner; option 2 is safer if there might be other name-mangling edge cases.
Acceptance
Out of scope
- Pydantic v2.11
model_fields-on-instance deprecation in models.py:117 — tracked separately.
Split out from #61 as a pre-existing bug that predates PR #96 and is out-of-scope for the coverage work.
Symptom
Observed during the
test_coverageCI run (e.g. https://github.com/ChengLabResearch/ouroboros/actions/runs/28622389980) as aPytestUnraisableExceptionWarning:Cause
SharedNPArray.__init__(mem.py:24) setsself.__shutdown = Falseat line 36 — after several other attribute assignments that can raise (SharedMemory(name),np.dtype(dtype), etc.). When any of those raises, the partially-constructed instance still gets__del__'d during GC, and__del__at line 76 referencesself.__shutdownbefore it was assigned. Python swallows theAttributeErrorinto an unraisable warning.Reproduction
The warning also fires without an exception if a
SharedNPArrayis created viaobject.__new__(SharedNPArray)(e.g. some pickle round-trips or object reconstruction paths) — but the more common trigger is an exception mid-__init__.Suggested fix
Two options, either works:
Class-level default (preferred — minimal diff):
Set the class-level default to
Trueso__del__treats an unconstructed instance as already-shut-down.Guard
__del__:Option 1 is cleaner; option 2 is safer if there might be other name-mangling edge cases.
Acceptance
SharedNPArray.__del__no longer raisesAttributeErroron partially-constructed instances.SharedNPArraywith a missing/invalidSharedMemoryname and letting it be GC'd — confirms noPytestUnraisableExceptionWarningin the pytest run.test_coverageworkflow output shows the two-warning count drop to 1 (the Pydantic deprecation, tracked separately) or 0 if that's also fixed in the same window.Out of scope
model_fields-on-instance deprecation inmodels.py:117— tracked separately.