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  }