~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
101 lines · 1.8 KB Raw History
package supervise

import (
	"context"
	"errors"
	"fmt"
	"runtime/debug"
	"time"

	"golang.org/x/sync/errgroup"
)

type Group struct {
	eg     *errgroup.Group
	ctx    context.Context
	cancel context.CancelFunc
}

func New(parent context.Context) *Group {
	ctx, cancel := context.WithCancel(parent)
	eg, ctx := errgroup.WithContext(ctx)
	return &Group{eg: eg, ctx: ctx, cancel: cancel}
}

func (g *Group) Context() context.Context { return g.ctx }

func (g *Group) Go(name string, fn func(context.Context) error) {
	g.eg.Go(func() error { return Run(g.ctx, name, fn) })
}

func (g *Group) Wait() error {
	err := g.eg.Wait()
	g.cancel()
	return err
}

func (g *Group) Stop() error {
	g.cancel()
	err := g.Wait()
	if errors.Is(err, context.Canceled) {
		return nil
	}
	return err
}

func Run(ctx context.Context, name string, fn func(context.Context) error) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%s: panic: %v\n%s", name, r, debug.Stack())
		}
	}()
	if err := fn(ctx); err != nil {
		return fmt.Errorf("%s: %w", name, err)
	}
	return nil
}

func Join(ctx context.Context, wait func()) error {
	done := make(chan struct{})
	go func() {
		wait()
		close(done)
	}()
	select {
	case <-done:
		return nil
	case <-ctx.Done():
		return context.Cause(ctx)
	}
}

type Backoff struct {
	Min     time.Duration
	Max     time.Duration
	OnError func(err error, delay time.Duration)
}

func Restart(ctx context.Context, name string, b Backoff, fn func(context.Context) error) {
	delay := b.Min
	for {
		started := time.Now()
		err := Run(ctx, name, fn)
		if ctx.Err() != nil {
			return
		}
		if time.Since(started) > b.Max {
			delay = b.Min
		}
		if b.OnError != nil {
			b.OnError(err, delay)
		}
		t := time.NewTimer(delay)
		select {
		case <-ctx.Done():
			t.Stop()
			return
		case <-t.C:
		}
		delay = min(delay*2, b.Max)
	}
}