Skip to content

Commit fb730ff

Browse files
fvoznikagvisor-bot
authored andcommitted
Remove checkpoint_count from runsc wait --checkpoint
This is done because external callers are not able to know the snapshot generation number from the outside. PiperOrigin-RevId: 707979556
1 parent f1656df commit fb730ff

File tree

9 files changed

+140
-83
lines changed

9 files changed

+140
-83
lines changed

pkg/sentry/fsimpl/proc/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ go_library(
119119
"//pkg/tcpip/header",
120120
"//pkg/tcpip/network/ipv4",
121121
"//pkg/usermem",
122+
"//pkg/waiter",
122123
],
123124
)
124125

pkg/sentry/kernel/kernel.go

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,6 @@ type Kernel struct {
352352
// checkpointMu is used to protect the checkpointing related fields below.
353353
checkpointMu sync.Mutex `state:"nosave"`
354354

355-
// checkpointCond is used to wait for a checkpoint to complete. It uses
356-
// checkpointMu as its mutex.
357-
checkpointCond sync.Cond `state:"nosave"`
358-
359355
// additionalCheckpointState stores additional state that needs
360356
// to be checkpointed. It's protected by checkpointMu.
361357
additionalCheckpointState map[any]any
@@ -364,19 +360,13 @@ type Kernel struct {
364360
// asynchronous checkpointing. It's protected by checkpointMu.
365361
saver Saver `state:"nosave"`
366362

367-
// checkpointCounter aims to track the number of times the kernel has been
368-
// successfully checkpointed. It's updated via calls to OnCheckpointAttempt()
369-
// and IncCheckpointCount(). Kernel checkpoint-ers must call these methods
370-
// appropriately so the counter is accurate. It's protected by checkpointMu.
371-
checkpointCounter uint32
363+
// CheckpointWait is used to wait for a checkpoint to complete.
364+
CheckpointWait CheckpointWaitable
372365

373-
// lastCheckpointStatus is the error value returned from the most recent
374-
// checkpoint attempt. If this value is nil, then the `checkpointCounter`-th
375-
// checkpoint attempt succeeded and no checkpoint attempt has completed since.
376-
// If this value is non-nil, then the `checkpointCounter`-th checkpoint
377-
// attempt succeeded, after which at least one more checkpoint attempt was
378-
// made and failed with this error. It's protected by checkpointMu.
379-
lastCheckpointStatus error `state:"nosave"`
366+
// checkpointGen aims to track the number of times the kernel has been
367+
// successfully checkpointed. Callers of checkpoint must notify the kernel
368+
// when checkpoint/restore are done. It's protected by checkpointMu.
369+
checkpointGen CheckpointGeneration
380370

381371
// UnixSocketOpts stores configuration options for management of unix sockets.
382372
UnixSocketOpts transport.UnixSocketOpts
@@ -466,7 +456,6 @@ func (k *Kernel) Init(args InitKernelArgs) error {
466456
k.rootNetworkNamespace = inet.NewRootNamespace(nil, nil, args.RootUserNamespace)
467457
}
468458
k.runningTasksCond.L = &k.runningTasksMu
469-
k.checkpointCond.L = &k.checkpointMu
470459
k.cpuClockTickerWakeCh = make(chan struct{}, 1)
471460
k.cpuClockTickerStopCond.L = &k.runningTasksMu
472461
k.applicationCores = args.ApplicationCores
@@ -495,6 +484,7 @@ func (k *Kernel) Init(args InitKernelArgs) error {
495484
}
496485
k.MaxFDLimit.Store(args.MaxFDLimit)
497486
k.containerNames = make(map[string]string)
487+
k.CheckpointWait.k = k
498488

499489
ctx := k.SupervisorContext()
500490
if err := k.vfs.Init(ctx); err != nil {
@@ -788,7 +778,6 @@ func (k *Kernel) LoadFrom(ctx context.Context, r, pagesMetadata io.Reader, pages
788778
}
789779

790780
k.runningTasksCond.L = &k.runningTasksMu
791-
k.checkpointCond.L = &k.checkpointMu
792781
k.cpuClockTickerWakeCh = make(chan struct{}, 1)
793782
k.cpuClockTickerStopCond.L = &k.runningTasksMu
794783

pkg/sentry/kernel/kernel_restore.go

Lines changed: 111 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,29 @@
1414

1515
package kernel
1616

17+
import (
18+
"gvisor.dev/gvisor/pkg/log"
19+
"gvisor.dev/gvisor/pkg/sync"
20+
)
21+
1722
// Saver is an interface for saving the kernel.
1823
type Saver interface {
1924
SaveAsync() error
2025
SpecEnviron(containerName string) []string
2126
}
2227

28+
// CheckpointGeneration stores information about the last checkpoint taken.
29+
//
30+
// +stateify savable
31+
type CheckpointGeneration struct {
32+
// Count is incremented every time a checkpoint is triggered, even if the
33+
// chekpoint failed.
34+
Count uint32
35+
// Restore indicates if the current instance resumed after the checkpoint or
36+
// it was restored from a checkpoint.
37+
Restore bool
38+
}
39+
2340
// AddStateToCheckpoint adds a key-value pair to be additionally checkpointed.
2441
func (k *Kernel) AddStateToCheckpoint(key, v any) {
2542
k.checkpointMu.Lock()
@@ -58,62 +75,120 @@ func (k *Kernel) Saver() Saver {
5875
return k.saver
5976
}
6077

61-
// IncCheckpointCount increments the checkpoint counter.
62-
func (k *Kernel) IncCheckpointCount() {
78+
// CheckpointGen returns the current checkpoint generation.
79+
func (k *Kernel) CheckpointGen() CheckpointGeneration {
6380
k.checkpointMu.Lock()
6481
defer k.checkpointMu.Unlock()
65-
k.checkpointCounter++
82+
83+
return k.checkpointGen
6684
}
6785

68-
// CheckpointCount returns the current checkpoint count. Note that the result
69-
// may be stale by the time the caller uses it.
70-
func (k *Kernel) CheckpointCount() uint32 {
86+
// OnRestoreDone is called to notify the kernel that a checkpoint restore has been
87+
// completed successfully.
88+
func (k *Kernel) OnRestoreDone() {
7189
k.checkpointMu.Lock()
7290
defer k.checkpointMu.Unlock()
73-
return k.checkpointCounter
91+
92+
k.checkpointGen.Count++
93+
k.checkpointGen.Restore = true
94+
95+
k.CheckpointWait.signal(k.checkpointGen, nil)
7496
}
7597

7698
// OnCheckpointAttempt is called when a checkpoint attempt is completed. err is
7799
// any checkpoint errors that may have occurred.
78100
func (k *Kernel) OnCheckpointAttempt(err error) {
79-
k.checkpointMu.Lock()
80-
defer k.checkpointMu.Unlock()
81101
if err == nil {
82-
k.checkpointCounter++
102+
log.Infof("Checkpoint completed successfully.")
103+
} else {
104+
log.Warningf("Checkpoint attempt failed with error: %v", err)
83105
}
84-
k.lastCheckpointStatus = err
85-
k.checkpointCond.Broadcast()
86-
}
87106

88-
// ResetCheckpointStatus resets the last checkpoint status, indicating a new
89-
// checkpoint is in progress. Caller must call OnCheckpointAttempt when the
90-
// checkpoint attempt is completed.
91-
func (k *Kernel) ResetCheckpointStatus() {
92107
k.checkpointMu.Lock()
93108
defer k.checkpointMu.Unlock()
94-
k.lastCheckpointStatus = nil
109+
110+
k.checkpointGen.Count++
111+
k.checkpointGen.Restore = false
112+
113+
k.CheckpointWait.signal(k.checkpointGen, err)
95114
}
96115

97-
// WaitCheckpoint waits for the Kernel to have been successfully checkpointed
98-
// n-1 times, then waits for either the n-th successful checkpoint (in which
99-
// case it returns nil) or any number of failed checkpoints (in which case it
100-
// returns an error returned by any such failure).
101-
func (k *Kernel) WaitCheckpoint(n uint32) error {
102-
if n == 0 {
103-
return nil
116+
// WaitForCheckpoint waits for the Kernel to have been successfully checkpointed.
117+
func (k *Kernel) WaitForCheckpoint() error {
118+
// Send checkpoint result to a channel and wait on it.
119+
ch := make(chan error, 1)
120+
callback := func(_ CheckpointGeneration, err error) { ch <- err }
121+
key := k.CheckpointWait.Register(callback, k.CheckpointGen().Count+1)
122+
defer k.CheckpointWait.Unregister(key)
123+
124+
return <-ch
125+
}
126+
127+
type checkpointWaiter struct {
128+
// count indicates the checkpoint generation that this waiter is interested in.
129+
count uint32
130+
// callback is the function that will be called when the checkpoint generation
131+
// reaches the desired count. It is set to nil after the callback is called.
132+
callback func(CheckpointGeneration, error)
133+
}
134+
135+
// CheckpointWaitable is a waitable object that waits for a
136+
// checkpoint to complete.
137+
//
138+
// +stateify savable
139+
type CheckpointWaitable struct {
140+
k *Kernel
141+
142+
mu sync.Mutex `state:"nosave"`
143+
144+
// Don't save the waiters, because they are repopulated after restore. It also
145+
// allows for external entities to wait for the checkpoint.
146+
waiters map[*checkpointWaiter]struct{} `state:"nosave"`
147+
}
148+
149+
// Register registers a callback that is notified when the checkpoint generation count is higher
150+
// than the desired count.
151+
func (w *CheckpointWaitable) Register(cb func(CheckpointGeneration, error), count uint32) any {
152+
w.mu.Lock()
153+
defer w.mu.Unlock()
154+
155+
waiter := &checkpointWaiter{
156+
count: count,
157+
callback: cb,
104158
}
105-
k.checkpointMu.Lock()
106-
defer k.checkpointMu.Unlock()
107-
if k.checkpointCounter >= n {
108-
// n-th checkpoint already completed successfully.
109-
return nil
159+
if w.waiters == nil {
160+
w.waiters = make(map[*checkpointWaiter]struct{})
161+
}
162+
w.waiters[waiter] = struct{}{}
163+
164+
if gen := w.k.CheckpointGen(); count <= gen.Count {
165+
// The checkpoint has already occurred. Signal immediately.
166+
waiter.callback(gen, nil)
167+
waiter.callback = nil
110168
}
111-
for k.checkpointCounter < n {
112-
if k.checkpointCounter == n-1 && k.lastCheckpointStatus != nil {
113-
// n-th checkpoint was attempted but it had failed.
114-
return k.lastCheckpointStatus
169+
return waiter
170+
}
171+
172+
// Unregister unregisters a waiter. It must be called even if the channel
173+
// was signalled.
174+
func (w *CheckpointWaitable) Unregister(key any) {
175+
w.mu.Lock()
176+
defer w.mu.Unlock()
177+
178+
delete(w.waiters, key.(*checkpointWaiter))
179+
if len(w.waiters) == 0 {
180+
w.waiters = nil
181+
}
182+
}
183+
184+
func (w *CheckpointWaitable) signal(gen CheckpointGeneration, err error) {
185+
w.mu.Lock()
186+
defer w.mu.Unlock()
187+
188+
for waiter := range w.waiters {
189+
if waiter.callback != nil && waiter.count <= gen.Count {
190+
waiter.callback(gen, err)
191+
waiter.callback = nil
115192
}
116-
k.checkpointCond.Wait()
117193
}
118-
return nil
119194
}

runsc/boot/controller.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -722,13 +722,11 @@ func (cm *containerManager) WaitPID(args *WaitPIDArgs, waitStatus *uint32) error
722722
return err
723723
}
724724

725-
// WaitCheckpoint waits for the Kernel to have been successfully checkpointed
726-
// n-1 times, then waits for either the n-th successful checkpoint (in which
727-
// case it returns nil) or any number of failed checkpoints (in which case it
728-
// returns an error returned by any such failure).
729-
func (cm *containerManager) WaitCheckpoint(n *uint32, _ *struct{}) error {
730-
err := cm.l.k.WaitCheckpoint(*n)
731-
log.Debugf("containerManager.WaitCheckpoint, n = %d, err = %v", *n, err)
725+
// WaitCheckpoint waits for the Kernel to have been successfully checkpointed.
726+
func (cm *containerManager) WaitCheckpoint(*struct{}, *struct{}) error {
727+
log.Debugf("containerManager.WaitCheckpoint")
728+
err := cm.l.k.WaitForCheckpoint()
729+
log.Debugf("containerManager.WaitCheckpoint done, err = %v", err)
732730
return err
733731
}
734732

runsc/boot/restore.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,8 @@ func (r *restorer) restore(l *Loader) error {
735735
// Restore was successful, so increment the checkpoint count manually. The
736736
// count was saved while the previous kernel was being saved and checkpoint
737737
// success was unknown at that time. Now we know the checkpoint succeeded.
738-
l.k.IncCheckpointCount()
738+
l.k.OnRestoreDone()
739+
739740
log.Infof("Restore successful")
740741
}()
741742
return nil
@@ -746,7 +747,6 @@ func (l *Loader) save(o *control.SaveOpts) (err error) {
746747
// This closure is required to capture the final value of err.
747748
l.k.OnCheckpointAttempt(err)
748749
}()
749-
l.k.ResetCheckpointStatus()
750750

751751
// TODO(gvisor.dev/issues/6243): save/restore not supported w/ hostinet
752752
if l.root.conf.Network == config.NetworkHost {

runsc/cmd/wait.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const (
3636
type Wait struct {
3737
rootPID int
3838
pid int
39-
checkpoint uint
39+
checkpoint bool
4040
}
4141

4242
// Name implements subcommands.Command.Name.
@@ -58,7 +58,7 @@ func (*Wait) Usage() string {
5858
func (wt *Wait) SetFlags(f *flag.FlagSet) {
5959
f.IntVar(&wt.rootPID, "rootpid", unsetPID, "select a PID in the sandbox root PID namespace to wait on instead of the container's root process")
6060
f.IntVar(&wt.pid, "pid", unsetPID, "select a PID in the container's PID namespace to wait on instead of the container's root process")
61-
f.UintVar(&wt.checkpoint, "checkpoint", 0, "wait for (n-1)th checkpoint to complete successfully, then waits for the next checkpoint attempt and returns its status. When set to 0, it disables checkpoint waiting.")
61+
f.BoolVar(&wt.checkpoint, "checkpoint", false, "wait for the next checkpoint to complete")
6262
}
6363

6464
// Execute implements subcommands.Command.Execute. It waits for a process in a
@@ -81,12 +81,12 @@ func (wt *Wait) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomm
8181
util.Fatalf("loading container: %v", err)
8282
}
8383

84-
if wt.checkpoint > 0 {
84+
if wt.checkpoint {
8585
if wt.rootPID != unsetPID || wt.pid != unsetPID {
8686
log.Warningf("waiting for checkpoint to complete, ignoring -pid and -rootpid")
8787
}
88-
if err := c.WaitCheckpoint(uint32(wt.checkpoint)); err != nil {
89-
util.Fatalf("waiting for %d-th checkpoint to complete: %v", wt.checkpoint, err)
88+
if err := c.WaitCheckpoint(); err != nil {
89+
util.Fatalf("waiting for checkpoint to complete: %v", err)
9090
}
9191
return subcommands.ExitSuccess
9292
}

runsc/container/container.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -633,16 +633,13 @@ func (c *Container) WaitPID(pid int32) (unix.WaitStatus, error) {
633633
return c.Sandbox.WaitPID(c.ID, pid)
634634
}
635635

636-
// WaitCheckpoint waits for the Kernel to have been successfully checkpointed
637-
// n-1 times, then waits for either the n-th successful checkpoint (in which
638-
// case it returns nil) or any number of failed checkpoints (in which case it
639-
// returns an error returned by any such failure).
640-
func (c *Container) WaitCheckpoint(n uint32) error {
641-
log.Debugf("Wait on %d-th checkpoint to complete in container, cid: %s", n, c.ID)
636+
// WaitCheckpoint waits for the Kernel to have been successfully checkpointed.
637+
func (c *Container) WaitCheckpoint() error {
638+
log.Debugf("Waiting for checkpoint to complete in container, cid: %s", c.ID)
642639
if !c.IsSandboxRunning() {
643640
return fmt.Errorf("sandbox is not running")
644641
}
645-
return c.Sandbox.WaitCheckpoint(n)
642+
return c.Sandbox.WaitCheckpoint()
646643
}
647644

648645
// SignalContainer sends the signal to the container. If all is true and signal

runsc/container/multi_container_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2763,7 +2763,7 @@ func testMultiContainerCheckpointRestore(t *testing.T, conf *config.Config, comp
27632763
checkpointWaiter := make(chan struct{}, 1)
27642764
go func() {
27652765
// WaitCheckpoint on the second container.
2766-
if err := conts[1].WaitCheckpoint(1); err != nil {
2766+
if err := conts[1].WaitCheckpoint(); err != nil {
27672767
t.Errorf("error waiting for checkpoint to complete: %v", err)
27682768
}
27692769
checkpointWaiter <- struct{}{}

runsc/sandbox/sandbox.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,13 +1329,10 @@ func (s *Sandbox) WaitPID(cid string, pid int32) (unix.WaitStatus, error) {
13291329
return ws, nil
13301330
}
13311331

1332-
// WaitCheckpoint waits for the Kernel to have been successfully checkpointed
1333-
// n-1 times, then waits for either the n-th successful checkpoint (in which
1334-
// case it returns nil) or any number of failed checkpoints (in which case it
1335-
// returns an error returned by any such failure).
1336-
func (s *Sandbox) WaitCheckpoint(n uint32) error {
1337-
log.Debugf("Waiting for %d-th checkpoint to complete in sandbox %q", n, s.ID)
1338-
return s.call(boot.ContMgrWaitCheckpoint, &n, nil)
1332+
// WaitCheckpoint waits for the Kernel to have been successfully checkpointed.
1333+
func (s *Sandbox) WaitCheckpoint() error {
1334+
log.Debugf("Waiting for checkpoint to complete in sandbox %q", s.ID)
1335+
return s.call(boot.ContMgrWaitCheckpoint, nil, nil)
13391336
}
13401337

13411338
// IsRootContainer returns true if the specified container ID belongs to the

0 commit comments

Comments
 (0)