Live video on the AT Protocol
1package cmd
2
3import (
4 "context"
5 "fmt"
6 "sync"
7 "time"
8
9 "golang.org/x/sync/errgroup"
10 "stream.place/streamplace/pkg/log"
11)
12
13func TimeoutGroupWithContext(ctx context.Context) (*TimeoutGroup, context.Context) {
14 group, ctx2 := errgroup.WithContext(ctx)
15 tg := &TimeoutGroup{
16 ErrGroup: group,
17 exitCh: make(chan error),
18 }
19 return tg, ctx2
20}
21
22// errgroup wrapper that self-destructs if things aren't shutting down properly
23type TimeoutGroup struct {
24 ErrGroup *errgroup.Group
25 selfDestructMu sync.Mutex
26 exitCh chan error
27}
28
29func (g *TimeoutGroup) Go(f func() error) {
30 g.ErrGroup.Go(func() error {
31 err := f()
32 g.selfDestruct(err)
33 return err
34 })
35}
36
37func (g *TimeoutGroup) Wait() error {
38 go func() {
39 g.exitCh <- g.ErrGroup.Wait()
40 }()
41 return <-g.exitCh
42}
43
44func (g *TimeoutGroup) selfDestruct(err error) {
45 first := g.selfDestructMu.TryLock()
46 if !first {
47 return
48 }
49 go func() {
50 log.Log(context.Background(), "app terminating", "reason", err)
51 time.Sleep(5 * time.Second)
52 g.exitCh <- fmt.Errorf("selfDestruct terminated app after timeout, reason for shutdown: %w", err)
53 }()
54}