Skip to content

Commit 76bfef9

Browse files
committed
util.TempFile: use ioutil-like implementation, fixes #41
* Adapt iotuil.TempFile to billy. * This prevents having a global temporary file maximum. * Also prevents race-condition on getting the file name.
1 parent 610213d commit 76bfef9

File tree

1 file changed

+48
-28
lines changed

1 file changed

+48
-28
lines changed

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)