Merge pull request #2682 from jterry75/lcow_snapshot_lock

Fix race in lcow snapshot scratch.vhdx creation
This commit is contained in:
Derek McGowan 2018-09-25 10:04:59 -07:00 committed by GitHub
commit 4b1d56e240
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -21,10 +21,13 @@ package lcow
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
@ -59,6 +62,8 @@ func init() {
type snapshotter struct { type snapshotter struct {
root string root string
ms *storage.MetaStore ms *storage.MetaStore
scratchLock sync.Mutex
} }
// NewSnapshotter returns a new windows snapshotter // NewSnapshotter returns a new windows snapshotter
@ -315,55 +320,10 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
if err := os.MkdirAll(snDir, 0700); err != nil { if err := os.MkdirAll(snDir, 0700); err != nil {
return nil, err return nil, err
} }
// Create the scratch.vhdx cache file if it doesn't already exit.
scratchPath := filepath.Join(s.root, "scratch.vhdx")
scratchLockPath := filepath.Join(s.root, "scratch.vhdx.lock")
startTime := time.Now()
timeout := 2 * time.Minute
var scratchSource *os.File
for {
var err error
scratchSource, err = os.OpenFile(scratchPath, os.O_RDONLY, 0700)
if err != nil {
if os.IsNotExist(err) {
// No scratch path. Take the lock and create it.
slock, err := os.OpenFile(scratchLockPath, os.O_EXCL|os.O_CREATE, 0700)
if err != nil {
if time.Now().Sub(startTime) >= timeout {
return nil, errors.Wrap(err, "timed out waiting for scratch.vhdx.lock")
}
// Couldnt obtain the lock. Sleep and try again.
time.Sleep(1 * time.Second)
continue
}
defer slock.Close()
// Create the scratch scratchSource, err := s.openOrCreateScratch(ctx)
rhcs := runhcs.Runhcs{ if err != nil {
Debug: true, return nil, err
Log: filepath.Join(s.root, "runhcs-scratch.log"),
LogFormat: runhcs.JSON,
Owner: "containerd",
}
if err := rhcs.CreateScratch(ctx, scratchPath); err != nil {
_ = os.Remove(scratchPath)
return nil, errors.Wrap(err, "failed to create scratch.vhdx")
}
// Successfully created scratch in the cache. Open and copy
continue
} else {
if time.Now().Sub(startTime) >= timeout {
return nil, errors.Wrap(err, "timed out waiting for scratch.vhdx")
}
// Couldnt obtain read access. Sleep and try again. Likely
// this case is that scratch.vhdx is in the process of being
// written actively.
time.Sleep(1 * time.Second)
continue
}
}
break
} }
defer scratchSource.Close() defer scratchSource.Close()
@ -392,6 +352,51 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
return s.mounts(newSnapshot), nil return s.mounts(newSnapshot), nil
} }
func (s *snapshotter) openOrCreateScratch(ctx context.Context) (_ *os.File, err error) {
// Create the scratch.vhdx cache file if it doesn't already exit.
s.scratchLock.Lock()
defer s.scratchLock.Unlock()
scratchFinalPath := filepath.Join(s.root, "scratch.vhdx")
scratchSource, err := os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700)
if err != nil {
if !os.IsNotExist(err) {
return nil, errors.Wrap(err, "failed to open scratch.vhdx for read")
}
log.G(ctx).Debug("scratch.vhdx not found, creating a new one")
// Golang logic for ioutil.TempFile without the file creation
r := uint32(time.Now().UnixNano() + int64(os.Getpid()))
r = r*1664525 + 1013904223 // constants from Numerical Recipes
scratchTempName := fmt.Sprintf("scratch-%s-tmp.vhdx", strconv.Itoa(int(1e9 + r%1e9))[1:])
scratchTempPath := filepath.Join(s.root, scratchTempName)
// Create the scratch
rhcs := runhcs.Runhcs{
Debug: true,
Log: filepath.Join(s.root, "runhcs-scratch.log"),
LogFormat: runhcs.JSON,
Owner: "containerd",
}
if err := rhcs.CreateScratch(ctx, scratchTempPath); err != nil {
_ = os.Remove(scratchTempPath)
return nil, errors.Wrapf(err, "failed to create '%s' temp file", scratchTempName)
}
if err := os.Rename(scratchTempPath, scratchFinalPath); err != nil {
_ = os.Remove(scratchTempPath)
return nil, errors.Wrapf(err, "failed to rename '%s' temp file to 'scratch.vhdx'", scratchTempName)
}
scratchSource, err = os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700)
if err != nil {
_ = os.Remove(scratchFinalPath)
return nil, errors.Wrap(err, "failed to open scratch.vhdx for read after creation")
}
}
return scratchSource, nil
}
func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string { func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string {
var parentLayerPaths []string var parentLayerPaths []string
for _, ID := range parentIDs { for _, ID := range parentIDs {