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 if errno, ok := errors.AsType[syscall.Errno](err); ok && (lowestErrno == 0 || errno < lowestErrno) {
35 bestErr = err
36 lowestErrno = errno
37 } else if bestErr == nil {
38 bestErr = err
39 }
40
41 if start.IsZero() {
42 start = time.Now()
43 } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
44 break
45 }
46 time.Sleep(nextSleep)
47 nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
48 }
49
50 return bestErr
51 }
52
53
54
55
56
57
58
59
60
61
62
63
64 func rename(oldpath, newpath string) (err error) {
65 return retry(func() (err error, mayRetry bool) {
66 err = os.Rename(oldpath, newpath)
67 return err, isEphemeralError(err)
68 })
69 }
70
71
72 func readFile(filename string) ([]byte, error) {
73 var b []byte
74 err := retry(func() (err error, mayRetry bool) {
75 b, err = os.ReadFile(filename)
76
77
78
79
80 return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
81 })
82 return b, err
83 }
84
85 func removeAll(path string) error {
86 return retry(func() (err error, mayRetry bool) {
87 err = os.RemoveAll(path)
88 return err, isEphemeralError(err)
89 })
90 }
91
View as plain text