sbcl-generations saves a running SBCL image, keeps the saved images as
numbered generations, and lets a process select one to be booted next. It is
the whole-heap counterpart to sbcl-workers: that library runs work in a
fresh child; this one preserves the exact live state of the process you are
already in.
(defparameter *store*
(sbcl-generations:make-generation-store
:root #p"/var/lib/example/generations/"
:current-pathname #p"/var/lib/example/current-generation.sexp"))
(defparameter *backend*
(sbcl-generations:make-checkpoint-backend
:store *store*
:toplevel-function #'example-main))
(sbcl-generations:checkpoint-create *backend*)
;; => #<GENERATION "..." :PENDING>, and this process keeps runningsave-lisp-and-die ends the process that calls it. A checkpoint uses two
forks so the caller can keep running:
- The caller forks a coordinator and returns immediately. The returned
generation is
:pending, and a watcher thread in the caller updates it. - The coordinator forks a saver, which is the process that calls
save-lisp-and-die. - The coordinator waits for the saver, publishes the result, and exits.
Only step 1 is exclusive, and only briefly. Everything expensive happens in processes that were always going to exit.
A forked child inherits only the forking thread, so a checkpoint requires the
caller to be the only live Lisp thread. This is checked. Hosts with other
fork-and-save paths can use checkpoint-single-threaded-p for the same
exact-Boolean preflight while holding their own exclusion across the check and
fork. The predicate is a snapshot.
A core is an opaque heap written by a process that no longer exists. Before publication the unpublished core is booted with a private argument and must print the exact identity it was saved with.
That is also why the library owns the saved core’s toplevel:
checkpoint-resume-toplevel answers the probe, and otherwise calls the host
entry point you supplied with the command-line arguments.
The core is renamed over its final name, then described by its manifest, then named by the selection pointer, each written atomically. An interruption at any point leaves the previous selection intact.
The library owns the envelope:
- the identifier
- the core path
- the creation time
- the runtime identity that decides whether a core can be booted here
(
:sbcl-version,:operating-system,:operating-system-version,:architecture)
generation-compatible-p refuses a core saved by a different SBCL build, and
generation-select will not choose it.
Everything else belongs to the host. :metadata is spliced into the manifest
flat rather than nested, and :manifest-version is the host’s number, so a
host replacing its own reader with this library keeps reading the manifests it
has already written. Supply :manifest-validator to check your own fields and
:accepted-manifest-versions to keep loading older ones.
Checkpointing a live image means the host has to be given control at four moments. Each hook may be omitted.
| Hook | Runs | For |
|---|---|---|
:around-function | wrapping everything | holding whatever dynamic context the others assume |
:precheck-function | before anything is exclusive | slow validation, such as checking a source tree |
:fork-guard-function | wrapping the checks and fork | making that region exclusive |
:validate-function | inside that region | re-checking, and suspending what must not be saved |
:metadata-function | inside that region | the manifest properties, and state that must not drift before the fork |
:prepare-function | inside the saver child | detaching descriptors and clearing secrets |
:resume-function | in the parent afterwards | restoring what :validate-function suspended |
:prepare-function is the security-relevant one. Anything the process is
holding when the saver runs is written into the core, so clear credentials and
detach inherited descriptors there.
Whatever :validate-function returns is handed to :resume-function, which
runs whether or not the fork succeeded. If the fork succeeded but resuming
failed, the library signals a checkpoint-resume-warning rather than an
error: the coordinator is already publishing, and failing would report a
checkpoint that did in fact happen.
generation-request-rollback selects a generation durably and then signals
rollback-requested. Establish a handler where exiting is safe.
Every refusal signals checkpoint-error with a stable stage: :backend,
:validation, :fork, :save, :saver-exit, :coordinator, :probe,
:publish, :manifest, or :selection. A failure inside the coordinator or
saver cannot signal into the caller, so it is recorded as failure.sexp
beside the generation instead.
(asdf:test-system :sbcl-generations)The suite includes one real checkpoint: it forks, saves a genuine image, boots
it to confirm its identity, and publishes it. That test needs a
single-threaded image and an sbcl on PATH able to boot the core it just
wrote; set SBCL_GENERATIONS_SBCL when the right runtime is somewhere else.
Licensed under COLL-Attribution. See LICENSE.lisp for the authoritative
terms.
Part of the Lambda Symbolics library shelf.