...
1
2
3
4
5
6
7
8 package robustio
9
10 import (
11 "errors"
12 "math/rand"
13 "os"
14 "syscall"
15 "time"
16 )
17
18 const arbitraryTimeout = 2000 * time.Millisecond
19
20
21
22 func retry(f func() (err error, mayRetry bool)) error {
23 var (
24 bestErr error
25 lowestErrno syscall.Errno
26 start time.Time
27 nextSleep time.Duration = 1 * time.Millisecond
28 )
29 for {
30 err, mayRetry := f()
31 if err == nil || !mayRetry {
32 return err
33 }
34
35 var errno syscall.Errno
36 if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) {
37 bestErr = err
38 lowestErrno = errno
39 } else if bestErr == nil {
40 bestErr = err
41 }
42
43 if start.IsZero() {
44 start = time.Now()
45 } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
46 break
47 }
48 time.Sleep(nextSleep)
49 nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
50 }
51
52 return bestErr
53 }
54
55
56
57
58
59
60
61
62
63
64
65
66 func rename(oldpath, newpath string) (err error) {
67 return retry(func() (err error, mayRetry bool) {
68 err = os.Rename(oldpath, newpath)
69 return err, isEphemeralError(err)
70 })
71 }
72
73
74 func readFile(filename string) ([]byte, error) {
75 var b []byte
76 err := retry(func() (err error, mayRetry bool) {
77 b, err = os.ReadFile(filename)
78
79
80
81
82 return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
83 })
84 return b, err
85 }
86
87 func removeAll(path string) error {
88 return retry(func() (err error, mayRetry bool) {
89 err = os.RemoveAll(path)
90 return err, isEphemeralError(err)
91 })
92 }
93
View as plain text