Summary #
Automatic release of an idle kernel without a loaded session actor ignores variables that the snapshot could not save, and can destroy the only copy of those values.
Reviewed commit: 154c6911d6a491aeeecacf26a06b6ae8c4bc70e1.
Code path #
In src/albedo/daemon/maintenance.gleam:175–195, release_unowned calls session_namespace.save_state and calls runtime.forget_session for any Ok(_) result.
However, Ok(Saved(...)) can contain a nonempty missed list. src/albedo/harness/extensions/python/kernel.gleam:238–325 maps the Python snapshot's skipped and failed entries into Saved.missed without making that a top-level error.
The actor-owned automatic release path already handles this correctly: src/albedo/daemon/session.gleam:2489 retains the kernel on Released(_, Ok(python.Saved(missed: [_, ..], ..)), None).
Failure scenario #
- A session retains an unserializable value, such as a generator, without any running cells/jobs or other resources keeping it non-quiescent.
- Restart the daemon, preserving its detached kernel.
- Leave the session unopened so the reattached kernel has no session actor.
- Once idle maintenance releases it, the snapshot succeeds with the value listed as skipped, but maintenance stops the kernel anyway.
- The next restore cannot recover that value.
Validation #
The snapshot behavior was reproduced using the actual Python functions, without starting a daemon:
import os
import tempfile
from albedo_state import save_state, load_state
with tempfile.TemporaryDirectory() as home:
path = os.path.join(home, 'session.state')
result = save_state(path, {'g': (i for i in range(3)), 'plain': 42}, {}, set())
restored = {}
load_state(path, restored, {})
print(result.get('error')) # None
print(result['skipped']) # g: TypeError: cannot pickle 'generator' object
print('g' in restored) # False
Run from the checkout with PYTHONPATH=priv/python. The complete daemon-restart/maintenance scenario is source-traced, not end-to-end reproduced.
Expected behavior #
Automatic release should retain unowned kernels whose snapshot reports missed names, just as actor-owned automatic release does. A regression scenario should cover restart, reattachment without opening the session, and an idle sweep with an unserializable variable.
Fixed on main (up to 0e4b0c30): idle maintenance now keeps a kernel whose snapshot missed names (owned sweep, unowned/reattached, and unload alike), via one rule, session_namespace.may_release. It's released lossy only under memory pressure (unowned kernels now count toward the budget) or once the session has idled past 24 h; the restore notice names what was lost. A kept kernel that runs nothing no longer rewrites its state file each sweep (measured: 0 bytes/h idle, was ~2.9 MB/h for a tiny namespace). Tests in test/e2e/idle_reap_test.py fail on the old code.