package app import ( "context" "sync" "time" "go.opentelemetry.io/otel/codes" ) // Watches contexts and channels for the // app to be finished and calls shutdown once // the app is done func (a *App) run() { select { case <-a.AppContext.Done(): a.l.Warn().Str("reason", a.AppContext.Err().Error()). Msg("shutting down on context done") case <-a.HTTP.httpDone: a.l.Warn().Msg("shutting down early on http server done") } a.Shutdown() a.appDone <- nil } // Typically invoked when AppContext is done // or Server has exited. Not intended to be called // manually func (a *App) Shutdown() { now := time.Now() doneCtx, cncl := context.WithTimeout(context.Background(), 15*time.Second) defer func() { if doneCtx.Err() == context.DeadlineExceeded { a.l.Err(doneCtx.Err()). Dur("shutdownTime", time.Since(now)). Msg("app shutdown aborted") } else { a.l.Info(). Int("shutdownFuncsCalled", len(a.shutdownFuncs)). Dur("shutdownTime", time.Since(now)). Msg("app shutdown normally") } cncl() }() doneCtx, span := a.tracer.Start(doneCtx, "shutdown") defer span.End() var wg sync.WaitGroup wg.Add(len(a.shutdownFuncs)) for _, f := range a.shutdownFuncs { go func() { defer wg.Done() err := f(doneCtx) if err != nil { span.SetStatus(codes.Error, "shutdown failed") span.RecordError(err) a.l.Err(err).Send() } }() } wg.Wait() }