Skip to content

Commit 4b1d56e

Browse files
authored
Merge pull request #2682 from jterry75/lcow_snapshot_lock
Fix race in lcow snapshot scratch.vhdx creation
2 parents 3bc4ba2 + e373126 commit 4b1d56e

File tree

1 file changed

+54
-49
lines changed

1 file changed

+54
-49
lines changed

snapshots/lcow/lcow.go

+54-49
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ package lcow
2121
import (
2222
"context"
2323
"encoding/json"
24+
"fmt"
2425
"io"
2526
"os"
2627
"path/filepath"
28+
"strconv"
2729
"strings"
30+
"sync"
2831
"syscall"
2932
"time"
3033
"unsafe"
@@ -59,6 +62,8 @@ func init() {
5962
type snapshotter struct {
6063
root string
6164
ms *storage.MetaStore
65+
66+
scratchLock sync.Mutex
6267
}
6368

6469
// NewSnapshotter returns a new windows snapshotter
@@ -315,55 +320,10 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
315320
if err := os.MkdirAll(snDir, 0700); err != nil {
316321
return nil, err
317322
}
318-
// Create the scratch.vhdx cache file if it doesn't already exit.
319-
scratchPath := filepath.Join(s.root, "scratch.vhdx")
320-
scratchLockPath := filepath.Join(s.root, "scratch.vhdx.lock")
321-
startTime := time.Now()
322-
timeout := 2 * time.Minute
323-
var scratchSource *os.File
324-
for {
325-
var err error
326-
scratchSource, err = os.OpenFile(scratchPath, os.O_RDONLY, 0700)
327-
if err != nil {
328-
if os.IsNotExist(err) {
329-
// No scratch path. Take the lock and create it.
330-
slock, err := os.OpenFile(scratchLockPath, os.O_EXCL|os.O_CREATE, 0700)
331-
if err != nil {
332-
if time.Now().Sub(startTime) >= timeout {
333-
return nil, errors.Wrap(err, "timed out waiting for scratch.vhdx.lock")
334-
}
335-
// Couldnt obtain the lock. Sleep and try again.
336-
time.Sleep(1 * time.Second)
337-
continue
338-
}
339-
defer slock.Close()
340-
341-
// Create the scratch
342-
rhcs := runhcs.Runhcs{
343-
Debug: true,
344-
Log: filepath.Join(s.root, "runhcs-scratch.log"),
345-
LogFormat: runhcs.JSON,
346-
Owner: "containerd",
347-
}
348-
if err := rhcs.CreateScratch(ctx, scratchPath); err != nil {
349-
_ = os.Remove(scratchPath)
350-
return nil, errors.Wrap(err, "failed to create scratch.vhdx")
351-
}
352-
353-
// Successfully created scratch in the cache. Open and copy
354-
continue
355-
} else {
356-
if time.Now().Sub(startTime) >= timeout {
357-
return nil, errors.Wrap(err, "timed out waiting for scratch.vhdx")
358-
}
359-
// Couldnt obtain read access. Sleep and try again. Likely
360-
// this case is that scratch.vhdx is in the process of being
361-
// written actively.
362-
time.Sleep(1 * time.Second)
363-
continue
364-
}
365-
}
366-
break
323+
324+
scratchSource, err := s.openOrCreateScratch(ctx)
325+
if err != nil {
326+
return nil, err
367327
}
368328
defer scratchSource.Close()
369329

@@ -392,6 +352,51 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
392352
return s.mounts(newSnapshot), nil
393353
}
394354

355+
func (s *snapshotter) openOrCreateScratch(ctx context.Context) (_ *os.File, err error) {
356+
// Create the scratch.vhdx cache file if it doesn't already exit.
357+
s.scratchLock.Lock()
358+
defer s.scratchLock.Unlock()
359+
360+
scratchFinalPath := filepath.Join(s.root, "scratch.vhdx")
361+
scratchSource, err := os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700)
362+
if err != nil {
363+
if !os.IsNotExist(err) {
364+
return nil, errors.Wrap(err, "failed to open scratch.vhdx for read")
365+
}
366+
367+
log.G(ctx).Debug("scratch.vhdx not found, creating a new one")
368+
369+
// Golang logic for ioutil.TempFile without the file creation
370+
r := uint32(time.Now().UnixNano() + int64(os.Getpid()))
371+
r = r*1664525 + 1013904223 // constants from Numerical Recipes
372+
373+
scratchTempName := fmt.Sprintf("scratch-%s-tmp.vhdx", strconv.Itoa(int(1e9 + r%1e9))[1:])
374+
scratchTempPath := filepath.Join(s.root, scratchTempName)
375+
376+
// Create the scratch
377+
rhcs := runhcs.Runhcs{
378+
Debug: true,
379+
Log: filepath.Join(s.root, "runhcs-scratch.log"),
380+
LogFormat: runhcs.JSON,
381+
Owner: "containerd",
382+
}
383+
if err := rhcs.CreateScratch(ctx, scratchTempPath); err != nil {
384+
_ = os.Remove(scratchTempPath)
385+
return nil, errors.Wrapf(err, "failed to create '%s' temp file", scratchTempName)
386+
}
387+
if err := os.Rename(scratchTempPath, scratchFinalPath); err != nil {
388+
_ = os.Remove(scratchTempPath)
389+
return nil, errors.Wrapf(err, "failed to rename '%s' temp file to 'scratch.vhdx'", scratchTempName)
390+
}
391+
scratchSource, err = os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700)
392+
if err != nil {
393+
_ = os.Remove(scratchFinalPath)
394+
return nil, errors.Wrap(err, "failed to open scratch.vhdx for read after creation")
395+
}
396+
}
397+
return scratchSource, nil
398+
}
399+
395400
func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string {
396401
var parentLayerPaths []string
397402
for _, ID := range parentIDs {

0 commit comments

Comments
 (0)