Memory allocation and lifetimes — DedSec Project
Memory and resource lifetimes — cleanup on exceptional paths Lifecycle contract An object being reachable, an object owning a resource, and a resource remaining open are different…
Memory and resource lifetimes — cleanup on exceptional paths
Lifecycle contract
An object being reachable, an object owning a resource, and a resource remaining open are different properties. Python can reclaim ordinary memory through reference counting or garbage collection, but a file handle or socket has an external lifetime that should not depend on a future collection cycle. In C, returning a pointer to an automatic local object produces a dangling reference; in Rust the borrow checker instead enforces ownership constraints before execution. Do not assume that a higher-level language removes the need to close resources.
Executable cleanup fixture (Python 3)
Save as lifetime_lab.py and run python3 lifetime_lab.py. Contextlib.ExitStack registers the resources and releases them in reverse order even when parsing raises a simulated exception.
from contextlib import ExitStack
from io import StringIO
closed = []
class Tracked(StringIO):
def __init__(self, name):
super().__init__(name)
self.name = name
def close(self):
if not self.closed:
closed.append(self.name)
super().close()
first, second = Tracked("first"), Tracked("second")
try:
with ExitStack() as stack:
a = stack.enter_context(first)
b = stack.enter_context(second)
assert a.read() == "first" and b.read() == "second"
raise RuntimeError("simulated parse failure")
except RuntimeError:
pass
assert first.closed and second.closed
assert closed == ["second", "first"]
print("PASS: reverse-order cleanup on failure")
What the assertions establish
Both text handles are closed after the exception. Because contexts unwind in reverse registration order, second closes before first. An ExitStack offers a useful mechanism when a variable number of resources are opened gradually; if the third acquisition fails, the first two must still be closed. The test checks logical resource ownership; it does not measure garbage-collector timings or prove leak freedom for arbitrary native allocations.
Bounded investigation and diagnostics
- Add a third Tracked resource, trigger a failure before entering it, and check exactly which handles close.
- Temporarily remove ExitStack. Capture the difference between an explicit cleanup guarantee and eventual garbage collection, then restore context management.
- Build a cache holding a file path rather than an open handle. Explain the changed resource lifetime and stale-data tradeoff.
- Sketch the corresponding ownership rules for C malloc/free and Rust moves/borrows without executing unsafe C on personal files. If you have a compiler, exercise sanitizers on a disposable demonstration only.
Evidence: PASS, cleanup order, exception trace and an ownership diagram. Distinguish memory reclamation from operating-system handle cleanup.