diff --git a/app.go b/app.go index 383070d8d65..6f629f41007 100644 --- a/app.go +++ b/app.go @@ -1444,49 +1444,49 @@ func (*disableLogger) Printf(string, ...any) { } func (app *App) init() *App { - // lock application - app.mutex.Lock() - - // Initialize Services when needed, - // panics if there is an error starting them. - app.initServices() + func() { + // lock application + app.mutex.Lock() + defer app.mutex.Unlock() + + // Initialize Services when needed, + // panics if there is an error starting them. + app.initServices() + + // Only load templates if a view engine is specified + if app.config.Views != nil { + if err := app.config.Views.Load(); err != nil { + log.Warnf("failed to load views: %v", err) + } + } - // Only load templates if a view engine is specified - if app.config.Views != nil { - if err := app.config.Views.Load(); err != nil { - log.Warnf("failed to load views: %v", err) + // create fasthttp server + app.server = &fasthttp.Server{ + Logger: &disableLogger{}, + LogAllErrors: false, + ErrorHandler: app.serverErrorHandler, } - } - // create fasthttp server - app.server = &fasthttp.Server{ - Logger: &disableLogger{}, - LogAllErrors: false, - ErrorHandler: app.serverErrorHandler, - } - - // fasthttp server settings - app.server.Handler = app.selectRequestHandler() - app.server.Name = app.config.ServerHeader - app.server.Concurrency = app.config.Concurrency - app.server.NoDefaultDate = app.config.DisableDefaultDate - app.server.NoDefaultContentType = app.config.DisableDefaultContentType - app.server.DisableHeaderNamesNormalizing = app.config.DisableHeaderNormalizing - app.server.DisableKeepalive = app.config.DisableKeepalive - app.server.MaxRequestBodySize = app.config.BodyLimit - app.server.NoDefaultServerHeader = app.config.ServerHeader == "" - app.server.ReadTimeout = app.config.ReadTimeout - app.server.WriteTimeout = app.config.WriteTimeout - app.server.IdleTimeout = app.config.IdleTimeout - app.server.ReadBufferSize = app.config.ReadBufferSize - app.server.WriteBufferSize = app.config.WriteBufferSize - app.server.GetOnly = app.config.GETOnly - app.server.ReduceMemoryUsage = app.config.ReduceMemoryUsage - app.server.StreamRequestBody = app.config.StreamRequestBody - app.server.DisablePreParseMultipartForm = app.config.DisablePreParseMultipartForm - - // unlock application - app.mutex.Unlock() + // fasthttp server settings + app.server.Handler = app.selectRequestHandler() + app.server.Name = app.config.ServerHeader + app.server.Concurrency = app.config.Concurrency + app.server.NoDefaultDate = app.config.DisableDefaultDate + app.server.NoDefaultContentType = app.config.DisableDefaultContentType + app.server.DisableHeaderNamesNormalizing = app.config.DisableHeaderNormalizing + app.server.DisableKeepalive = app.config.DisableKeepalive + app.server.MaxRequestBodySize = app.config.BodyLimit + app.server.NoDefaultServerHeader = app.config.ServerHeader == "" + app.server.ReadTimeout = app.config.ReadTimeout + app.server.WriteTimeout = app.config.WriteTimeout + app.server.IdleTimeout = app.config.IdleTimeout + app.server.ReadBufferSize = app.config.ReadBufferSize + app.server.WriteBufferSize = app.config.WriteBufferSize + app.server.GetOnly = app.config.GETOnly + app.server.ReduceMemoryUsage = app.config.ReduceMemoryUsage + app.server.StreamRequestBody = app.config.StreamRequestBody + app.server.DisablePreParseMultipartForm = app.config.DisablePreParseMultipartForm + }() // Register the Services shutdown handler once the app is initialized and unlocked. app.Hooks().OnPostShutdown(func(_ error) error { diff --git a/app_test.go b/app_test.go index fe6607f2ca2..4e1c4fe58fc 100644 --- a/app_test.go +++ b/app_test.go @@ -2222,6 +2222,47 @@ func Test_App_ReloadViews_PanicUnlocksRender(t *testing.T) { } } +func Test_App_InitPanicUnlocksRouteRegistration(t *testing.T) { + t.Parallel() + + view := &panicLoadView{} + app := New(Config{Views: view}) + + type initResult struct { + recovered any + } + + initDone := make(chan initResult, 1) + go func() { + result := initResult{} + defer func() { + result.recovered = recover() + initDone <- result + }() + + app.init() + }() + + select { + case result := <-initDone: + require.Equal(t, "panic load", result.recovered) + case <-time.After(time.Second): + t.Fatal("init panic was not recovered") + } + + registerDone := make(chan struct{}, 1) + go func() { + app.Get("/after-panic", func(Ctx) error { return nil }) + registerDone <- struct{}{} + }() + + select { + case <-registerDone: + case <-time.After(time.Second): + t.Fatal("route registration deadlocked after init panic") + } +} + func Test_App_RenderPanicUnlocksReloadViews(t *testing.T) { t.Parallel()