From a3ae7f25a064dae8b79d52775d57af901e7486a9 Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Tue, 30 Apr 2024 05:19:11 -0700 Subject: [PATCH] Create separate MemoryFile metadata file in compression=none mode. This helps in parallelizing MemoryFile restore with kernel restore. PiperOrigin-RevId: 629378902 --- pkg/sentry/control/state.go | 27 +++++++++++++++++++++------ pkg/sentry/kernel/kernel.go | 23 +++++++++++++++-------- pkg/sentry/state/state.go | 21 ++++++++++++++------- runsc/boot/controller.go | 16 ++++++++++++---- runsc/boot/restore.go | 10 +++++++--- runsc/sandbox/sandbox.go | 16 ++++++++++++++-- 6 files changed, 83 insertions(+), 30 deletions(-) diff --git a/pkg/sentry/control/state.go b/pkg/sentry/control/state.go index e25757953..30cb0f7d3 100644 --- a/pkg/sentry/control/state.go +++ b/pkg/sentry/control/state.go @@ -44,12 +44,14 @@ type SaveOpts struct { // Metadata is the set of metadata to prepend to the state file. Metadata map[string]string `json:"metadata"` - // HavePagesFile indicates whether the checkpoint pages file is provided. + // HavePagesFile indicates whether the pages file and its corresponding + // metadata file is provided. HavePagesFile bool `json:"have_pages_file"` // FilePayload contains the following: // 1. checkpoint state file. - // 2. optional checkpoint pages file. + // 2. optional checkpoint pages metadata file. + // 3. optional checkpoint pages file. urpc.FilePayload // Resume indicates if the sandbox process should continue running @@ -61,15 +63,20 @@ type SaveOpts struct { func (s *State) Save(o *SaveOpts, _ *struct{}) error { wantFiles := 1 if o.HavePagesFile { - wantFiles++ + wantFiles += 2 } if gotFiles := len(o.FilePayload.Files); gotFiles != wantFiles { return fmt.Errorf("got %d files, wanted %d", gotFiles, wantFiles) } // Save to the first provided stream. + stateFile, err := o.ReleaseFD(0) + if err != nil { + return err + } + defer stateFile.Close() saveOpts := state.SaveOpts{ - Destination: o.FilePayload.Files[0], + Destination: stateFile, Key: o.Key, Metadata: o.Metadata, Callback: func(err error) { @@ -85,9 +92,17 @@ func (s *State) Save(o *SaveOpts, _ *struct{}) error { } }, } - defer o.FilePayload.Files[0].Close() if o.HavePagesFile { - saveOpts.PagesFile = o.FilePayload.Files[1] + saveOpts.PagesMetadata, err = o.ReleaseFD(1) + if err != nil { + return err + } + defer saveOpts.PagesMetadata.Close() + + saveOpts.PagesFile, err = o.ReleaseFD(2) + if err != nil { + return err + } defer saveOpts.PagesFile.Close() } return saveOpts.Save(s.Kernel.SupervisorContext(), s.Kernel, s.Watchdog) diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 9eb61dba9..03c3eedbb 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -35,7 +35,6 @@ import ( "errors" "fmt" "io" - "os" "path/filepath" "time" @@ -581,7 +580,7 @@ func loadPrivateMFs(ctx context.Context, r io.Reader, pr *statefile.AsyncReader) // SaveTo saves the state of k to w. // // Preconditions: The kernel must be paused throughout the call to SaveTo. -func (k *Kernel) SaveTo(ctx context.Context, w io.Writer, pagesFile *os.File) error { +func (k *Kernel) SaveTo(ctx context.Context, w io.Writer, pagesMetadata, pagesFile *fd.FD) error { saveStart := time.Now() // Do not allow other Kernel methods to affect it while it's being saved. @@ -649,14 +648,18 @@ func (k *Kernel) SaveTo(ctx context.Context, w io.Writer, pagesFile *os.File) er // Save the memory files' state. memoryStart := time.Now() - pw := io.Writer(w) + pmw := w + if pagesMetadata != nil { + pmw = pagesMetadata + } + pw := w if pagesFile != nil { pw = pagesFile } - if err := k.mf.SaveTo(ctx, w, pw); err != nil { + if err := k.mf.SaveTo(ctx, pmw, pw); err != nil { return err } - if err := savePrivateMFs(ctx, w, pw, mfsToSave); err != nil { + if err := savePrivateMFs(ctx, pmw, pw, mfsToSave); err != nil { return err } log.Infof("Memory files save took [%s].", time.Since(memoryStart)) @@ -692,7 +695,7 @@ func (k *Kernel) invalidateUnsavableMappings(ctx context.Context) error { } // LoadFrom returns a new Kernel loaded from args. -func (k *Kernel) LoadFrom(ctx context.Context, r io.Reader, pagesFile *fd.FD, timeReady chan struct{}, net inet.Stack, clocks sentrytime.Clocks, vfsOpts *vfs.CompleteRestoreOptions) error { +func (k *Kernel) LoadFrom(ctx context.Context, r io.Reader, pagesMetadata, pagesFile *fd.FD, timeReady chan struct{}, net inet.Stack, clocks sentrytime.Clocks, vfsOpts *vfs.CompleteRestoreOptions) error { loadStart := time.Now() k.runningTasksCond.L = &k.runningTasksMu @@ -734,14 +737,18 @@ func (k *Kernel) LoadFrom(ctx context.Context, r io.Reader, pagesFile *fd.FD, ti // Load the memory files' state. memoryStart := time.Now() + pmr := r + if pagesMetadata != nil { + pmr = pagesMetadata + } var pr *statefile.AsyncReader if pagesFile != nil { pr = statefile.NewAsyncReader(pagesFile, 0 /* off */) } - if err := k.mf.LoadFrom(ctx, r, pr); err != nil { + if err := k.mf.LoadFrom(ctx, pmr, pr); err != nil { return err } - if err := loadPrivateMFs(ctx, r, pr); err != nil { + if err := loadPrivateMFs(ctx, pmr, pr); err != nil { return err } if pr != nil { diff --git a/pkg/sentry/state/state.go b/pkg/sentry/state/state.go index 2d4bf533b..c766c58df 100644 --- a/pkg/sentry/state/state.go +++ b/pkg/sentry/state/state.go @@ -18,7 +18,6 @@ package state import ( "fmt" "io" - "os" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" @@ -50,9 +49,13 @@ type SaveOpts struct { // Destination is the save target. Destination io.Writer + // PagesMetadata is the file into which MemoryFile metadata is stored if + // PagesMetadata is non-nil. Otherwise this content is stored in Destination. + PagesMetadata *fd.FD + // PagesFile is the file in which all MemoryFile pages are stored if - // PagesFile is non-nil. - PagesFile *os.File + // PagesFile is non-nil. Otherwise this content is stored in Destination. + PagesFile *fd.FD // Key is used for state integrity check. Key []byte @@ -92,7 +95,7 @@ func (opts SaveOpts) Save(ctx context.Context, k *kernel.Kernel, w *watchdog.Wat err = ErrStateFile{err} } else { // Save the kernel. - err = k.SaveTo(ctx, wc, opts.PagesFile) + err = k.SaveTo(ctx, wc, opts.PagesMetadata, opts.PagesFile) // ENOSPC is a state file error. This error can only come from // writing the state file, and not from fs.FileOperations.Fsync @@ -111,11 +114,15 @@ func (opts SaveOpts) Save(ctx context.Context, k *kernel.Kernel, w *watchdog.Wat // LoadOpts contains load-related options. type LoadOpts struct { - // Destination is the load source. + // Source is the load source. Source io.Reader + // PagesMetadata is the file into which MemoryFile metadata is stored if + // PagesMetadata is non-nil. Otherwise this content is stored in Source. + PagesMetadata *fd.FD + // PagesFile is the file in which all MemoryFile pages are stored if - // PagesFile is non-nil. + // PagesFile is non-nil. Otherwise this content is stored in Source. PagesFile *fd.FD // Key is used for state integrity check. @@ -133,5 +140,5 @@ func (opts LoadOpts) Load(ctx context.Context, k *kernel.Kernel, timeReady chan previousMetadata = m // Restore the Kernel object graph. - return k.LoadFrom(ctx, r, opts.PagesFile, timeReady, n, clocks, vfsOpts) + return k.LoadFrom(ctx, r, opts.PagesMetadata, opts.PagesFile, timeReady, n, clocks, vfsOpts) } diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go index 5ba214177..57a0e9414 100644 --- a/runsc/boot/controller.go +++ b/runsc/boot/controller.go @@ -453,8 +453,9 @@ func (cm *containerManager) PortForward(opts *PortForwardOpts, _ *struct{}) erro type RestoreOpts struct { // FilePayload contains the state file to be restored, followed in order by: // 1. checkpoint state file. - // 2. optional checkpoint pages file. - // 3. optional platform device file. + // 2. optional checkpoint pages metadata file. + // 3. optional checkpoint pages file. + // 4. optional platform device file. urpc.FilePayload HavePagesFile bool HaveDeviceFile bool @@ -496,12 +497,19 @@ func (cm *containerManager) Restore(o *RestoreOpts, _ *struct{}) error { fileIdx := 1 if o.HavePagesFile { - pagesFile, err := o.ReleaseFD(fileIdx) + cm.restorer.pagesMetadata, err = o.ReleaseFD(fileIdx) if err != nil { return err } + defer cm.restorer.pagesMetadata.Close() + fileIdx++ + + cm.restorer.pagesFile, err = o.ReleaseFD(fileIdx) + if err != nil { + return err + } + defer cm.restorer.pagesFile.Close() fileIdx++ - cm.restorer.pagesFile = pagesFile } if o.HaveDeviceFile { diff --git a/runsc/boot/restore.go b/runsc/boot/restore.go index b58e03e0e..54348667f 100644 --- a/runsc/boot/restore.go +++ b/runsc/boot/restore.go @@ -48,6 +48,9 @@ const ( // CheckpointStateFileName is the file within the given image-path's // directory which contains the container's saved state. CheckpointStateFileName = "checkpoint.img" + // CheckpointPagesMetadataFileName is the file within the given image-path's + // directory containing the container's MemoryFile metadata. + CheckpointPagesMetadataFileName = "pages_meta.img" // CheckpointPagesFileName is the file within the given image-path's // directory containing the container's MemoryFile pages. CheckpointPagesFileName = "pages.img" @@ -68,8 +71,9 @@ type restorer struct { containers []*containerInfo // Files used by restore to rehydrate the state. - stateFile io.ReadCloser - pagesFile *fd.FD + stateFile io.ReadCloser + pagesMetadata *fd.FD + pagesFile *fd.FD // deviceFile is the required to start the platform. deviceFile *fd.FD @@ -224,7 +228,7 @@ func (r *restorer) restore(l *Loader) error { ctx = context.WithValue(ctx, pgalloc.CtxMemoryFileMap, mfmap) // Load the state. - loadOpts := state.LoadOpts{Source: r.stateFile, PagesFile: r.pagesFile} + loadOpts := state.LoadOpts{Source: r.stateFile, PagesMetadata: r.pagesMetadata, PagesFile: r.pagesFile} if err := loadOpts.Load(ctx, l.k, nil, netns.Stack(), time.NewCalibratedClocks(), &vfs.CompleteRestoreOptions{}); err != nil { return err } diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index e2aca8b62..dbc068a46 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -468,8 +468,14 @@ func (s *Sandbox) Restore(conf *config.Config, cid string, imagePath string, dir } if pf, err := os.OpenFile(pagesFileName, pagesReadFlags, 0); err == nil { defer pf.Close() + pagesMetadataFileName := path.Join(imagePath, boot.CheckpointPagesMetadataFileName) + pmf, err := os.Open(pagesMetadataFileName) + if err != nil { + return fmt.Errorf("opening restore image file %q failed: %v", pagesMetadataFileName, err) + } + defer pmf.Close() opt.HavePagesFile = true - opt.FilePayload.Files = append(opt.FilePayload.Files, pf) + opt.FilePayload.Files = append(opt.FilePayload.Files, pmf, pf) } else if !os.IsNotExist(err) { return fmt.Errorf("opening restore image file %q failed: %v", pagesFileName, err) } @@ -1341,7 +1347,13 @@ func (s *Sandbox) Checkpoint(cid string, imagePath string, options statefile.Opt return fmt.Errorf("creating checkpoint pages file %q: %w", pagesFilePath, err) } defer pf.Close() - opt.FilePayload.Files = append(opt.FilePayload.Files, pf) + pagesMetadataFilePath := filepath.Join(imagePath, boot.CheckpointPagesMetadataFileName) + pmf, err := os.OpenFile(pagesMetadataFilePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) + if err != nil { + return fmt.Errorf("creating checkpoint pages metadata file %q: %w", pagesMetadataFilePath, err) + } + defer pmf.Close() + opt.FilePayload.Files = append(opt.FilePayload.Files, pmf, pf) opt.HavePagesFile = true }