github.com/jmigpin/editor@v1.6.0/util/ctxutil/callretry.go (about) 1 package ctxutil 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "sync" 8 "time" 9 ) 10 11 // Allows fn to return early on ctx cancel. If fn does not return in time, lateFn will run at the end of fn (async). 12 func Call(ctx context.Context, prefix string, fn func() error, lateFn func(error)) error { 13 buildErr := func(e error) error { 14 return fmt.Errorf("%v: %v", prefix, e) 15 } 16 17 var d = struct { 18 sync.Mutex 19 fn struct { 20 done bool 21 err error 22 } 23 ctxDone bool 24 }{} 25 26 // run fn in go routine 27 ctx2, cancel := context.WithCancel(ctx) 28 id := addCall(prefix) // keep track of fn() 29 go func() { 30 defer doneCall(id) // keep track of fn() 31 defer cancel() 32 33 err := fn() 34 35 d.Lock() 36 defer d.Unlock() 37 d.fn.done = true 38 if err != nil { 39 err = buildErr(err) 40 } 41 d.fn.err = err 42 if d.ctxDone { 43 if lateFn != nil { 44 lateFn(err) 45 } else { 46 // err is lost 47 } 48 } 49 }() 50 51 <-ctx2.Done() 52 53 d.Lock() 54 defer d.Unlock() 55 d.ctxDone = true 56 if d.fn.done { 57 return d.fn.err 58 } else { 59 // context was canceled and fn has not returned yet 60 return buildErr(ctx2.Err()) 61 } 62 } 63 64 //---------- 65 66 func Retry(ctx context.Context, retryPause time.Duration, prefix string, fn func() error, lateFn func(error)) error { 67 var err error 68 for { 69 err = Call(ctx, prefix, fn, lateFn) 70 if err != nil { 71 // keep retrying 72 } else { 73 return nil // done 74 } 75 select { 76 case <-ctx.Done(): 77 return err // err is non-nil 78 default: // non-blocking select 79 time.Sleep(retryPause) // sleep before next retry 80 } 81 } 82 } 83 84 //---------- 85 86 type cdata struct { 87 t time.Time 88 s string 89 } 90 91 var cmu sync.Mutex 92 var callm = map[int]*cdata{} 93 var ci = 0 94 95 func addCall(s string) int { 96 cmu.Lock() 97 defer cmu.Unlock() 98 ci++ 99 callm[ci] = &cdata{s: s, t: time.Now()} 100 return ci 101 } 102 103 func doneCall(v int) { 104 cmu.Lock() 105 defer cmu.Unlock() 106 delete(callm, v) 107 } 108 109 func CallsState() string { 110 cmu.Lock() 111 defer cmu.Unlock() 112 u := []string{} 113 now := time.Now() 114 for _, d := range callm { 115 s := fmt.Sprintf("%v: %v ago", d.s, now.Sub(d.t)) 116 u = append(u, s) 117 } 118 return fmt.Sprintf("%v entries\n%v\n", len(u), strings.Join(u, "\n")) 119 }