@@ -32,15 +32,15 @@ import (
3232
3333// CheckpointWorkload suspends the actor and writes a portable CH snapshot.
3434//
35- // Contract with atelet (mirrors ateom-gvisor): after we return, atelet uploads
36- // the checkpoint dir to object storage, then tears down bundles and resets the
37- // actor dir.
35+ // Contract with atelet: after we return, atelet uploads the checkpoint dir to object
36+ // storage, then tears down bundles and resets the actor dir.
3837//
39- // ateom drives the ateom-owned CH's REST api-socket: pause -> snapshot
40- // file://<CheckpointStateDir> (config.json + state.json + sparse memory-ranges) ->
41- // tear the VMM down. The actor's rootfs lives on the host-backed /dev/vdb, not a
42- // guest tmpfs overlay-upper, so the snapshot is naturally memory-only and small —
43- // no RAM-backed upper to wipe and no balloon to inflate before snapshot.
38+ // ateom drives the CH REST api-socket: pause -> snapshot file://<CheckpointStateDir>
39+ // (config.json + state.json + sparse memory-ranges) -> tear the VMM down. Each
40+ // container's rootfs is overlay(virtio-fs RO lower + guest-tmpfs upper), so the
41+ // writable upper lives in guest RAM and is captured by the memory snapshot — process
42+ // memory and rootfs writes both persist across suspend/resume. The RO lower is
43+ // reconstructed from the OCI image at restore, so nothing rootfs-related ships here.
4444func (s * AteomService ) CheckpointWorkload (ctx context.Context , req * ateompb.CheckpointWorkloadRequest ) (* ateompb.CheckpointWorkloadResponse , error ) {
4545 s .lock .Lock ()
4646 defer s .lock .Unlock ()
@@ -79,9 +79,9 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec
7979 }
8080
8181 // Record the FROZEN base id (the id the guest's virtio-fs find-paths are pinned
82- // to, <baseID>/rootfs). For a cold (owned-boot) actor this is its own id; for a
83- // restored actor it is the golden id propagated via ra.baseID (set from the
84- // snapshot we restored from). RestoreWorkload reads this to lay the
82+ // to, <baseID>/rootfs). For a cold-run actor this is its own id; for a restored
83+ // actor it is the golden id propagated via ra.baseID (set from the snapshot we
84+ // restored from). RestoreWorkload reads this to lay the
8585 // reconstructed-from-image base at the path the guest expects. We can NOT derive
8686 // it from config.json (its socket paths get rewritten to the current id on every
8787 // restore, losing the invariant golden id).
@@ -120,25 +120,13 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec
120120 slog .String ("id" , id ), slog .Duration ("merge" , time .Since (tMerge )))
121121 }
122122
123- // reset-to-golden support: save the actor's /dev/vdb AS-OF this (paused,
124- // consistent) snapshot as a verbatim golden template, so future restores can
125- // recreate the disk byte-identical to what the snapshot's guest RAM expects
126- // while discarding the actor's later rootfs writes. Saved once (the first/golden
127- // checkpoint) and kept; best-effort (without it, restore reopens the live disk =
128- // continuity). TODO: ship the template with the snapshot for cross-node restore
129- // (it's golden, shipped once per template, like the OCI base).
130- actorDir := ateompath .ActorPath (ns , name , id )
131- if tmpl := filepath .Join (actorDir , goldenRootfsDiskName ); fileMissing (tmpl ) {
132- if cerr := copyDiskFile (ctx , filepath .Join (actorDir , actorRootfsDiskName ), tmpl ); cerr != nil {
133- slog .WarnContext (ctx , "Failed to save golden rootfs template; restore will reopen live disk" , slog .Any ("err" , cerr ))
134- } else {
135- slog .InfoContext (ctx , "Saved golden rootfs disk template" , slog .String ("id" , id ))
136- }
137- }
123+ // Nothing rootfs-related ships: the overlay's writable upper is a guest tmpfs, so
124+ // the actor's rootfs writes are already in the memory snapshot above, and the RO
125+ // lower is reconstructed from the OCI image at restore (it never changes).
138126
139127 // Report exactly the files we wrote so atelet ships precisely the CH snapshot
140- // (config.json + state.json + memory-ranges + base-id), not gVisor's fixed set.
141- // Memory-only: the RO base is reconstructed from the OCI image at restore.
128+ // (config.json + state.json + memory-ranges + base-id). The RO base is
129+ // reconstructed from the OCI image at restore.
142130 snapshotFiles , err := listFiles (checkpointDir )
143131 if err != nil {
144132 return nil , fmt .Errorf ("while listing snapshot files: %w" , err )
@@ -151,7 +139,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec
151139 dTeardown := time .Since (tTeardown )
152140 delete (s .running , id )
153141
154- // Tear down the per-activation actor network (mirrors gVisor) .
142+ // Tear down the per-activation actor network.
155143 if err := s .cleanupActorNetwork (ctx ); err != nil {
156144 slog .WarnContext (ctx , "Failed to clean up actor network after checkpoint" , slog .Any ("err" , err ))
157145 }
@@ -207,6 +195,11 @@ func (s *AteomService) teardownActor(ctx context.Context, id string, ra *running
207195 _ = ra .chCmd .Process .Kill ()
208196 _ , _ = ra .chCmd .Process .Wait ()
209197 }
198+ // Kill the virtiofsd serving the overlay RO lower (after CH, its only client).
199+ if ra .vfsdCmd != nil && ra .vfsdCmd .Process != nil {
200+ _ = ra .vfsdCmd .Process .Kill ()
201+ _ , _ = ra .vfsdCmd .Process .Wait ()
202+ }
210203 }
211204
212205 // Sweep any leftover per-sandbox host-side state + orphaned per-sandbox
0 commit comments