Skip to content

Commit

Permalink
Fix a race in temp file creation.
Browse files Browse the repository at this point in the history
The added test fails before the fix and passes after.

Fixes #500.
  • Loading branch information
aalexand committed Oct 4, 2020
1 parent acf8798 commit 13f5fc0
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 5 deletions.
26 changes: 21 additions & 5 deletions internal/driver/tempfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,21 @@ import (
func newTempFile(dir, prefix, suffix string) (*os.File, error) {
for index := 1; index < 10000; index++ {
path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
if _, err := os.Stat(path); err != nil {
return os.Create(path)
_, err := os.Stat(path)
if err == nil {
continue
}
if !os.IsNotExist(err) {
return nil, err
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err == nil {
return f, nil
}
if !os.IsExist(err) {
return nil, err
}
// Racy creation, try again with a new name.
}
// Give up
return nil, fmt.Errorf("could not create file of the form %s%03d%s", prefix, 1, suffix)
Expand All @@ -44,11 +56,15 @@ func deferDeleteTempFile(path string) {
}

// cleanupTempFiles removes any temporary files selected for deferred cleaning.
func cleanupTempFiles() {
func cleanupTempFiles() error {
tempFilesMu.Lock()
defer tempFilesMu.Unlock()
var lastErr error
for _, f := range tempFiles {
os.Remove(f)
if err := os.Remove(f); err != nil {
lastErr = err
}
}
tempFiles = nil
tempFilesMu.Unlock()
return lastErr
}
55 changes: 55 additions & 0 deletions internal/driver/tempfile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package driver

import (
"os"
"sync"
"testing"
)

func TestNewTempFile(t *testing.T) {
const n = 100
// Line up ready to execute goroutines with a read-write lock.
var mu sync.RWMutex
mu.Lock()
var wg sync.WaitGroup
errc := make(chan error, n)
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
mu.RLock()
defer mu.RUnlock()
defer wg.Done()
f, err := newTempFile(os.TempDir(), "profile", ".tmp")
errc <- err
deferDeleteTempFile(f.Name())
f.Close()
}()
}
// Start the file creation race.
mu.Unlock()
// Wait for all goroutines to finish.
wg.Wait()

for i := 0; i < n; i++ {
if err := <-errc; err != nil {
t.Fatalf("newTempFile(): got %v, want no error", err)
}
}
if len(tempFiles) != n {
t.Errorf("len(tempFiles): got %d, want %d", len(tempFiles), n)
}
names := map[string]bool{}
for _, name := range tempFiles {
if names[name] {
t.Errorf("got temp file %s created multiple times", name)
break
}
names[name] = true
}
if err := cleanupTempFiles(); err != nil {
t.Errorf("cleanupTempFiles(): got error %v, want no error", err)
}
if len(tempFiles) != 0 {
t.Errorf("len(tempFiles) after the cleanup: got %d, want 0", len(tempFiles))
}
}

0 comments on commit 13f5fc0

Please sign in to comment.