package app import ( "context" "sync" "time" "go.opentelemetry.io/otel/attribute" "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: // TODO: return error on this channel a.l.Warn().Msg("shutting down early on http server done") case err := <-a.GRPC.GRPCDone: a.l.Warn().Err(err).Msg("shutting down early on grpc server done") } a.Shutdown() // Run through all shutdown funcs a.appDone <- nil // Notify app } // 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() span.SetAttributes(attribute.Int("shutdown.funcs", len(a.shutdownFuncs))) 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() }