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