Skip to content

Commit 4ad7906

Browse files
authored
Merge pull request #42 from smola/tmp-fixes
util.TempFile: use ioutil-like implementation, fixes #41
2 parents c329b7b + 76bfef9 commit 4ad7906

File tree

3 files changed

+95
-31
lines changed

3 files changed

+95
-31
lines changed

helper/temporal/temporal_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ func Test(t *testing.T) { TestingT(t) }
1515
var _ = Suite(&TemporalSuite{})
1616

1717
type TemporalSuite struct {
18-
test.TempFileSuite
18+
test.FilesystemSuite
1919
}
2020

2121
func (s *TemporalSuite) SetUpTest(c *C) {
22-
s.FS = New(memfs.New(), "foo")
22+
fs := New(memfs.New(), "foo")
23+
s.FilesystemSuite = test.NewFilesystemSuite(fs)
2324
}
2425

2526
func (s *TemporalSuite) TestTempFileDefaultPath(c *C) {

test/tempfile.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
. "gopkg.in/check.v1"
77
"gopkg.in/src-d/go-billy.v3"
8+
"gopkg.in/src-d/go-billy.v3/util"
89
)
910

1011
// TempFileSuite is a convenient test suite to validate any implementation of
@@ -45,7 +46,49 @@ func (s *TempFileSuite) TestRemoveTempFile(c *C) {
4546
c.Assert(err, IsNil)
4647

4748
fn := f.Name()
48-
c.Assert(err, IsNil)
4949
c.Assert(f.Close(), IsNil)
5050
c.Assert(s.FS.Remove(fn), IsNil)
5151
}
52+
53+
func (s *TempFileSuite) TestRenameTempFile(c *C) {
54+
f, err := s.FS.TempFile("test-dir", "test-prefix")
55+
c.Assert(err, IsNil)
56+
57+
fn := f.Name()
58+
c.Assert(f.Close(), IsNil)
59+
c.Assert(s.FS.Rename(fn, "other-path"), IsNil)
60+
}
61+
62+
func (s *TempFileSuite) TestTempFileMany(c *C) {
63+
for i := 0; i < 1024; i++ {
64+
var fs []billy.File
65+
66+
for j := 0; j < 100; j++ {
67+
f, err := s.FS.TempFile("test-dir", "test-prefix")
68+
c.Assert(err, IsNil)
69+
fs = append(fs, f)
70+
}
71+
72+
for _, f := range fs {
73+
c.Assert(f.Close(), IsNil)
74+
c.Assert(s.FS.Remove(f.Name()), IsNil)
75+
}
76+
}
77+
}
78+
79+
func (s *TempFileSuite) TestTempFileManyWithUtil(c *C) {
80+
for i := 0; i < 1024; i++ {
81+
var fs []billy.File
82+
83+
for j := 0; j < 100; j++ {
84+
f, err := util.TempFile(s.FS, "test-dir", "test-prefix")
85+
c.Assert(err, IsNil)
86+
fs = append(fs, f)
87+
}
88+
89+
for _, f := range fs {
90+
c.Assert(f.Close(), IsNil)
91+
c.Assert(s.FS.Remove(f.Name()), IsNil)
92+
}
93+
}
94+
}

util/util.go

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package util
22

33
import (
4-
"errors"
5-
"fmt"
64
"io"
75
"os"
8-
"sync/atomic"
6+
"path/filepath"
7+
"strconv"
8+
"sync"
99
"time"
1010

1111
"gopkg.in/src-d/go-billy.v3"
@@ -114,38 +114,58 @@ func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) e
114114
return err
115115
}
116116

117-
var (
118-
MaxTempFiles int32 = 1024 * 4
119-
tempCount int32
120-
)
117+
// Random number state.
118+
// We generate random temporary file names so that there's a good
119+
// chance the file doesn't exist yet - keeps the number of tries in
120+
// TempFile to a minimum.
121+
var rand uint32
122+
var randmu sync.Mutex
123+
124+
func reseed() uint32 {
125+
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
126+
}
127+
128+
func nextSuffix() string {
129+
randmu.Lock()
130+
r := rand
131+
if r == 0 {
132+
r = reseed()
133+
}
134+
r = r*1664525 + 1013904223 // constants from Numerical Recipes
135+
rand = r
136+
randmu.Unlock()
137+
return strconv.Itoa(int(1e9 + r%1e9))[1:]
138+
}
121139

122140
// TempFile creates a new temporary file in the directory dir with a name
123141
// beginning with prefix, opens the file for reading and writing, and returns
124142
// the resulting *os.File. If dir is the empty string, TempFile uses the default
125-
// directory for temporary files (see os.TempDir).
126-
//
127-
// Multiple programs calling TempFile simultaneously will not choose the same
128-
// file. The caller can use f.Name() to find the pathname of the file.
129-
//
130-
// It is the caller's responsibility to remove the file when no longer needed.
131-
func TempFile(fs billy.Basic, dir, prefix string) (billy.File, error) {
132-
var fullpath string
133-
for {
134-
if tempCount >= MaxTempFiles {
135-
return nil, errors.New("max. number of tempfiles reached")
136-
}
143+
// directory for temporary files (see os.TempDir). Multiple programs calling
144+
// TempFile simultaneously will not choose the same file. The caller can use
145+
// f.Name() to find the pathname of the file. It is the caller's responsibility
146+
// to remove the file when no longer needed.
147+
func TempFile(fs billy.Basic, dir, prefix string) (f billy.File, err error) {
148+
// This implementation is based on stdlib ioutil.TempFile.
149+
150+
if dir == "" {
151+
dir = os.TempDir()
152+
}
137153

138-
fullpath = getTempFilename(fs, dir, prefix)
154+
nconflict := 0
155+
for i := 0; i < 10000; i++ {
156+
name := filepath.Join(dir, prefix+nextSuffix())
157+
f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
158+
if os.IsExist(err) {
159+
if nconflict++; nconflict > 10 {
160+
randmu.Lock()
161+
rand = reseed()
162+
randmu.Unlock()
163+
}
164+
continue
165+
}
139166
break
140167
}
141-
142-
return fs.Create(fullpath)
143-
}
144-
145-
func getTempFilename(fs billy.Basic, dir, prefix string) string {
146-
atomic.AddInt32(&tempCount, 1)
147-
filename := fmt.Sprintf("%s_%d_%d", prefix, tempCount, time.Now().UnixNano())
148-
return fs.Join(dir, filename)
168+
return
149169
}
150170

151171
type underlying interface {

0 commit comments

Comments
 (0)