7.8 KiB
Session persistence
Lime saves the desk, separately from your Lisp configuration: Activities, named Views and Viewsets, current and cached drafts, hidden/shown membership, home Activity and shared eligibility, layout order/options/weights, floating rectangles, focus, dirty flags, partial View state, and logical Head selections. It does not serialize CLX objects, fonts, key bindings, callbacks, running processes, or application contents.
lime/dev enables this automatically. The normal location is
$XDG_STATE_HOME/lime/sessions/, falling back to ~/.local/state/lime/sessions/.
Files are separate for each DISPLAY (for example display-_3A0.sexp for :0,
display-_3A99.sexp for :99). Your nested test desk does not overwrite your
normal desktop's desk. A lifetime writer lock prevents two backends from using
one explicit file simultaneously.
Select a different file or disable persistence with launcher keywords—not an additional Lime environment variable:
(lime.dev:run-development-session
:config-file #p"/home/adan/.config/lime/config.lisp"
:session-file #p"/home/adan/.local/state/lime/my-desk.sexp")
;; For a disposable test:
(lime.dev:start-development-session :display-name ":99" :session-file nil)
The lower-level lime.x11:start-lime / start-backend APIs default to no
persistence; pass :session-file :default or a pathname to enable it. An
explicit :world and a session file are mutually exclusive startup sources.
Configuration reload does not change the selected session file.
Configured startup hooks still run normally; hooks that create named Activities
should tolerate those Activities already existing in a restored desk.
scripts/run-xephyr-dev supports --session-file PATH and --no-session.
Without either option it uses the display-specific default.
What a restart restores
On a Lime-only restart within the same X server, surviving clients reconnect to their Activities, checkpoints, and drafts—including unmapped hidden clients. The adapter verifies a random server token plus each client's XID and random window token. An XID, title, WM_CLASS, or PID alone is never enough: reused ids must not attach an unrelated application's window to an old checkpoint.
On logout, reboot, or X-server replacement, the names and non-client layout settings survive, but the old windows do not. Missing clients are retired from the saved arrangements; an empty View remains a named checkpoint. This first version does not relaunch applications, restore terminal/browser contents, reserve placeholder slots for dead windows, or guess how newly launched windows should fill an old arrangement. Application/session-relaunch rules are a separate feature.
Current physical Heads remain authoritative. Matching logical Head ids resume their saved selections. Drafts from removed Heads are suspended, not moved to an arbitrary other monitor or deleted; new Heads get ordinary blank drafts. Viewsets retain their logical mappings even when an output is absent. Leases are reconstructed and validated, never accepted as serialized ownership objects.
Saving and failures
Autosave observes model/focus changes on the owner thread. After 750 ms of quiet, or five seconds of ongoing edits, it captures a validated plain-data snapshot. Active pointer drags postpone capture until release. A single worker writes snapshots off the X event thread, keeping only the latest pending one. An unchanged desk produces no disk writes. A crash can lose edits since the last completed autosave; this is a snapshot system, not a per-operation journal.
Normal shutdown flushes the final valid desk. A normal developer restart saves
before closing X; if that save fails, restart aborts with the old backend
still available for repair. Restart after an event-loop fault instead uses the
last good disk snapshot, avoiding an automatic save of partially mutated state.
resume-development-session still uses the retained in-memory world, not disk.
For an explicit flush or inspection from SLY:
(lime.dev:save-development-session)
(lime.dev:with-development-backend (backend)
(lime.x11:backend-session-status backend))
The status includes the file, restore/missing-client report, last-save time, submitted/completed write generations, and restore/snapshot/write errors. Disk write failures are nonfatal to ordinary operation and retry with five-second backoff; explicit flush reports the error to its caller. Invalid in-memory snapshots block automatic saving until repaired and explicitly saved again.
Files are mode 0600. Writes use a private temporary file in the same
directory, file fsync, atomic rename, and directory fsync on Linux. A failed
write before rename leaves the old file untouched; readers cannot see a partial
replacement. The format is versioned, bounded plain data. Reading never calls
load/eval, disables read-time evaluation, and rejects reader dispatch,
circular structures, excessive nesting, and trailing data. Unknown versions,
invalid relationships, duplicate leases, or malformed files are rejected before
replacing the startup world.
A rejected file is not overwritten: Lime starts a fresh usable desk with autosave blocked and reports the error. Inspect/back up the file first. To deliberately replace it with the current desk:
(lime.dev:save-development-session :replace-invalid-p t)
This does not override another session's file lock. Stop that other session or choose a different path. Back up important files separately; atomic writes are not a backup/history system. Names and window associations can be private even though the file contains no application buffers or process contents.
Installing and testing
Membership support adds a client structure slot and ASDF components. Relaunch Lime in a fresh Lisp process; configuration reload alone cannot install it. World snapshots use schema version 3. The X11 session envelope uses version 3, with ordered Activity positions, stable local-View shortcut slots, last-arrangement history, and portable/extended Activity navigation policy. Version-1/2 envelopes remain readable. Old Activities with one substantive arrangement become portable; genuinely split arrangements remain extended. This inference happens before missing-client pruning, preserving multi-monitor intent across reboot. Old empty drafts are retained, not recalled or deleted. Version-1 envelopes also receive initial shortcut assignments. Migration does not move a window or alter what the Heads display. Navigation metadata is validated before a saved world is installed. An explicit extended choice stays extended when all its windows close; it is never re-inferred from window count. Version 1 worlds load with all clients private. Version 1/2 worlds consolidate historical drafts into one current arrangement per Activity/Head. Active state wins, then saved navigation history, then the newest generated Presentation id. Actual leases win duplicated window locations; missing checkpoint slots are not live locations. Windows and saved Views are retained, and old source labels are detached where a checkpoint was recalled elsewhere. Migration is performed on a candidate world before installation; version 3 rejects duplicate live locations rather than silently repairing them. Keep a backup before upgrading or downgrading; older Lime builds may not read these schema versions. The same-X-server identity rules above still apply to moved/shared clients.
(asdf:test-system "lime/persistence")
sbcl --non-interactive --load test/x11-persistence-smoke.lisp
sbcl --non-interactive --load test/x11-membership-smoke.lisp
The smoke test uses its own Xvfb, temporary state files, and fake client windows. It tests graceful/abrupt restart, hidden clients, dirty checkpoints, partial leases, changed topology/identity, corrupt files, disk failures, and the actual developer restart path. It does not connect to your running desktop.