incorporated more configuration values into the timeout and panic recovery code
This commit is contained in:
+34
-20
@@ -100,7 +100,10 @@ type AmConfig struct {
|
||||
SessionExpire string `yaml:"sessionExpire"`
|
||||
UserAgreementResource string `yaml:"userAgreementResource"`
|
||||
PolicyResource string `yaml:"policyResource"`
|
||||
FrameTemplate string `yaml:"frameTemplate"`
|
||||
FooterTemplate string `yaml:"footerTemplate"`
|
||||
TopMenuId string `yaml:"topMenuId"`
|
||||
FixedMenuId string `yaml:"fixedMenuId"`
|
||||
DefaultCommunityLogo string `yaml:"defaultCommunityLogo"`
|
||||
DefaultUserPhoto string `yaml:"defaultUserPhoto"`
|
||||
WelcomeTitle string `yaml:"welcomeTitle"`
|
||||
@@ -148,6 +151,9 @@ type AmConfig struct {
|
||||
Prioritize string `yaml:"prioritize"`
|
||||
} `yaml:"countryList"`
|
||||
VeniceCompatibleImageURLs bool `yaml:"veniceCompatibleImageURLs"`
|
||||
PanicRecovery struct {
|
||||
StackDataSize string `yaml:"stackDataSize"`
|
||||
} `yaml:"panicRecovery"`
|
||||
} `yaml:"rendering"`
|
||||
Resources struct {
|
||||
ViewTemplateDir string `yaml:"viewTemplateDir"`
|
||||
@@ -168,9 +174,11 @@ type AmConfig struct {
|
||||
Tuning struct {
|
||||
WorkerTasks int `yaml:"workerTasks"`
|
||||
Timeouts struct {
|
||||
HttpRead int `yaml:"httpRead"`
|
||||
HttpWrite int `yaml:"httpWrite"`
|
||||
HttpIdle int `yaml:"httpIdle"`
|
||||
HttpRead int `yaml:"httpRead"`
|
||||
HttpWrite int `yaml:"httpWrite"`
|
||||
HttpIdle int `yaml:"httpIdle"`
|
||||
PageExecute int `yaml:"pageExecute"`
|
||||
PageRender int `yaml:"pageRender"`
|
||||
} `yaml:"timeouts"`
|
||||
Queues struct {
|
||||
AuditWrites int `yaml:"auditWrites"`
|
||||
@@ -207,22 +215,23 @@ func (c *AmConfig) ExPath(path string) string {
|
||||
|
||||
// AmConfigComputed is the configuration values which are "computed" based only on values in AmConfig and CommandLine.
|
||||
type AmConfigComputed struct {
|
||||
DebugMode bool // are we in debug mode?
|
||||
LogLevel string // the logging level
|
||||
Listen string // listen address
|
||||
DatabaseDriver string // name of database driver
|
||||
DatabaseHost string // hostname for database
|
||||
DatabaseUser string // user name for database
|
||||
DatabasePassword string // password for database
|
||||
DatabaseName string // database name
|
||||
MailHost string // SMTP host
|
||||
MailPort int // SMTP port
|
||||
MailTLS string // SMTP TLS setting
|
||||
MailAuthType string // SMTP auth type
|
||||
MailUser string // SMTP user name
|
||||
MailPassword string // SMTP password
|
||||
UploadMaxSize int32 // maximum upload size in bytes
|
||||
UploadNoCompress map[string]bool // which upload types are not compressed?
|
||||
DebugMode bool // are we in debug mode?
|
||||
LogLevel string // the logging level
|
||||
Listen string // listen address
|
||||
DatabaseDriver string // name of database driver
|
||||
DatabaseHost string // hostname for database
|
||||
DatabaseUser string // user name for database
|
||||
DatabasePassword string // password for database
|
||||
DatabaseName string // database name
|
||||
MailHost string // SMTP host
|
||||
MailPort int // SMTP port
|
||||
MailTLS string // SMTP TLS setting
|
||||
MailAuthType string // SMTP auth type
|
||||
MailUser string // SMTP user name
|
||||
MailPassword string // SMTP password
|
||||
PanicRecoveryStack int32 // stack size for panic recovery
|
||||
UploadMaxSize int32 // maximum upload size in bytes
|
||||
UploadNoCompress map[string]bool // which upload types are not compressed?
|
||||
}
|
||||
|
||||
//go:embed default.yaml
|
||||
@@ -403,7 +412,12 @@ func SetupConfig() {
|
||||
GlobalComputedConfig.MailAuthType = util.IIF(CommandLine.MailAuthType != "", CommandLine.MailAuthType, GlobalConfig.Email.AuthType)
|
||||
GlobalComputedConfig.MailUser = util.IIF(CommandLine.MailUser != "", CommandLine.MailUser, GlobalConfig.Email.User)
|
||||
GlobalComputedConfig.MailPassword = util.IIF(CommandLine.MailPassword != "", CommandLine.MailPassword, GlobalConfig.Email.Password)
|
||||
tmp, err := humanize.ParseBytes(GlobalConfig.Posting.Uploads.MaxSize)
|
||||
tmp, err := humanize.ParseBytes(GlobalConfig.Rendering.PanicRecovery.StackDataSize)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
GlobalComputedConfig.PanicRecoveryStack = int32(tmp)
|
||||
tmp, err = humanize.ParseBytes(GlobalConfig.Posting.Uploads.MaxSize)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@ site:
|
||||
sessionExpire: "3h"
|
||||
userAgreementResource: "useragreement.html"
|
||||
policyResource: "policy.html"
|
||||
frameTemplate: "frame.jet"
|
||||
footerTemplate: "footer.jet"
|
||||
topMenuId: "top"
|
||||
fixedMenuId: "fixed"
|
||||
defaultCommunityLogo: "/img/builtin/default-community.jpg"
|
||||
defaultUserPhoto: "/img/builtin/no-user.png"
|
||||
welcomeTitle: "Welcome to Amsterdam"
|
||||
@@ -72,6 +75,8 @@ rendering:
|
||||
countryList:
|
||||
prioritize: US
|
||||
veniceCompatibleImageURLs: false
|
||||
panicRecovery:
|
||||
stackDataSize: "4 KiB"
|
||||
resources:
|
||||
viewTemplateDir: ""
|
||||
dialogTemplateDir: ""
|
||||
@@ -95,6 +100,8 @@ tuning:
|
||||
httpRead: 30
|
||||
httpWrite: 30
|
||||
httpIdle: 120
|
||||
pageExecute: 15
|
||||
pageRender: 15
|
||||
queues:
|
||||
auditWrites: 16
|
||||
contextRecycle: 16
|
||||
|
||||
@@ -50,7 +50,9 @@ func setupEcho() *echo.Echo {
|
||||
e.Renderer = &ui.TemplateRenderer{}
|
||||
e.HTTPErrorHandler = AmErrorHandler
|
||||
if !config.CommandLine.DebugPanic {
|
||||
e.Use(middleware.RecoverWithConfig(middleware.DefaultRecoverConfig))
|
||||
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
|
||||
StackSize: int(config.GlobalComputedConfig.PanicRecoveryStack),
|
||||
}))
|
||||
} else {
|
||||
log.Warn("WARNING: --debug-panic in effect - DO NOT use this in production!")
|
||||
}
|
||||
|
||||
+80
-71
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Amsterdam Web Communities System
|
||||
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -44,6 +44,51 @@ func (e *panicRecoveryErr) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// doFrameRender renders the outer frame template with an inner template.
|
||||
func doFrameRender(ctxt *echo.Context, amctxt AmContext, statusCode int, innerPage string) error {
|
||||
if amctxt.FrameTitle() == "" {
|
||||
log.Errorf("*** NO FRAME TITLE set for path %s", amctxt.URLPath())
|
||||
amctxt.SetFrameTitle("<<< NO FRAME TITLE >>>")
|
||||
}
|
||||
amctxt.VarMap().Set("__innerPage", innerPage)
|
||||
menus := make([]*MenuDefinition, 2)
|
||||
switch amctxt.LeftMenu() {
|
||||
case "top":
|
||||
menus[0] = AmMenu(config.GlobalConfig.Site.TopMenuId)
|
||||
case "community":
|
||||
comm := amctxt.CurrentCommunity()
|
||||
if comm != nil {
|
||||
md, err := AmBuildCommunityMenu(ctxt.Request().Context(), comm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
menus[0] = md
|
||||
} else {
|
||||
menus[0] = AmMenu(config.GlobalConfig.Site.TopMenuId)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("AmSendPageData(): unknown left menu context: %s", amctxt.LeftMenu())
|
||||
}
|
||||
menus[1] = AmMenu(config.GlobalConfig.Site.FixedMenuId)
|
||||
amctxt.VarMap().Set("__leftMenus", menus)
|
||||
ad, err := database.AmGetRandomAd(ctxt.Request().Context())
|
||||
if err != nil {
|
||||
ad = &database.Advert{
|
||||
AdId: -1,
|
||||
ImagePath: "",
|
||||
PathStyle: -1,
|
||||
Caption: nil,
|
||||
LinkURL: nil,
|
||||
}
|
||||
}
|
||||
amctxt.VarMap().Set("__bannerad", ad)
|
||||
amctxt.VarMap().Set("__debugMode", config.GlobalComputedConfig.DebugMode)
|
||||
if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil {
|
||||
amctxt.VarMap().Set("__suppressLogin", true)
|
||||
}
|
||||
return ctxt.Render(statusCode, config.GlobalConfig.Site.FrameTemplate, amctxt)
|
||||
}
|
||||
|
||||
/* AmSendPageData sends page data to the output based on the command string.
|
||||
* Parameters:
|
||||
* ctxt - The Echo context from the request.
|
||||
@@ -65,20 +110,22 @@ func (e *panicRecoveryErr) Unwrap() error {
|
||||
*/
|
||||
func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data any) error {
|
||||
// Enable panic recovery.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if r == http.ErrAbortHandler {
|
||||
panic(r)
|
||||
if !config.CommandLine.DebugPanic {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if r == http.ErrAbortHandler {
|
||||
panic(r)
|
||||
}
|
||||
tmperr, ok := r.(error)
|
||||
if !ok {
|
||||
tmperr = fmt.Errorf("%v", r)
|
||||
}
|
||||
stack := make([]byte, config.GlobalComputedConfig.PanicRecoveryStack)
|
||||
length := runtime.Stack(stack, false)
|
||||
log.Errorf("[Panic Recovery in SendData Phase] %s %s", tmperr.Error(), stack[:length])
|
||||
}
|
||||
tmperr, ok := r.(error)
|
||||
if !ok {
|
||||
tmperr = fmt.Errorf("%v", r)
|
||||
}
|
||||
stack := make([]byte, 4<<10)
|
||||
length := runtime.Stack(stack, false)
|
||||
log.Errorf("[Panic Recovery in SendData Phase] %s %s", tmperr.Error(), stack[:length])
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
||||
// Preprocess certain commands into different ones.
|
||||
httprc := http.StatusOK
|
||||
@@ -132,7 +179,7 @@ func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data a
|
||||
|
||||
// Process commands.
|
||||
oldreq := ctxt.Request()
|
||||
ctx, cancel := context.WithTimeout(oldreq.Context(), 15*time.Second)
|
||||
ctx, cancel := context.WithTimeout(oldreq.Context(), time.Duration(config.GlobalConfig.Tuning.Timeouts.PageRender)*time.Second)
|
||||
defer cancel()
|
||||
ctxt.SetRequest(oldreq.WithContext(ctx))
|
||||
defer ctxt.SetRequest(oldreq)
|
||||
@@ -151,47 +198,7 @@ func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data a
|
||||
case "template":
|
||||
err = ctxt.Render(httprc, data.(string), amctxt)
|
||||
case "framed":
|
||||
if amctxt.FrameTitle() == "" {
|
||||
log.Errorf("*** NO FRAME TITLE set for path %s", amctxt.URLPath())
|
||||
amctxt.SetFrameTitle("<<< NO FRAME TITLE >>>")
|
||||
}
|
||||
amctxt.VarMap().Set("__innerPage", data)
|
||||
menus := make([]*MenuDefinition, 2)
|
||||
switch amctxt.LeftMenu() {
|
||||
case "top":
|
||||
menus[0] = AmMenu("top")
|
||||
case "community":
|
||||
comm := amctxt.CurrentCommunity()
|
||||
if comm != nil {
|
||||
md, err := AmBuildCommunityMenu(ctxt.Request().Context(), comm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
menus[0] = md
|
||||
} else {
|
||||
menus[0] = AmMenu("top")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("AmSendPageData(): unknown left menu context: %s", amctxt.LeftMenu())
|
||||
}
|
||||
menus[1] = AmMenu("fixed")
|
||||
amctxt.VarMap().Set("__leftMenus", menus)
|
||||
ad, err := database.AmGetRandomAd(ctxt.Request().Context())
|
||||
if err != nil {
|
||||
ad = &database.Advert{
|
||||
AdId: -1,
|
||||
ImagePath: "",
|
||||
PathStyle: -1,
|
||||
Caption: nil,
|
||||
LinkURL: nil,
|
||||
}
|
||||
}
|
||||
amctxt.VarMap().Set("__bannerad", ad)
|
||||
amctxt.VarMap().Set("__debugMode", config.GlobalComputedConfig.DebugMode)
|
||||
if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil {
|
||||
amctxt.VarMap().Set("__suppressLogin", true)
|
||||
}
|
||||
err = ctxt.Render(httprc, "frame.jet", amctxt)
|
||||
err = doFrameRender(ctxt, amctxt, httprc, data.(string))
|
||||
default:
|
||||
err = fmt.Errorf("AmSendPageData(): unknown rendering type: %s", command)
|
||||
}
|
||||
@@ -209,23 +216,25 @@ type AmPageFunc func(AmContext) (string, any)
|
||||
|
||||
// callWrappedPageFunc calls the specified page functon inside a wrapper that handles timeouts and panic recovery.
|
||||
func callWrappedPageFunc(f AmPageFunc, ctxt *echo.Context, amctxt AmContext) (command string, arg any) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if r == http.ErrAbortHandler {
|
||||
panic(r)
|
||||
if !config.CommandLine.DebugPanic {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if r == http.ErrAbortHandler {
|
||||
panic(r)
|
||||
}
|
||||
tmperr, ok := r.(error)
|
||||
if !ok {
|
||||
tmperr = fmt.Errorf("%v", r)
|
||||
}
|
||||
stack := make([]byte, config.GlobalComputedConfig.PanicRecoveryStack)
|
||||
length := runtime.Stack(stack, false)
|
||||
arg = &panicRecoveryErr{Phase: "PageFunc", Err: tmperr, Stack: stack[:length]}
|
||||
command = "error"
|
||||
}
|
||||
tmperr, ok := r.(error)
|
||||
if !ok {
|
||||
tmperr = fmt.Errorf("%v", r)
|
||||
}
|
||||
stack := make([]byte, 4<<10)
|
||||
length := runtime.Stack(stack, false)
|
||||
arg = &panicRecoveryErr{Phase: "PageFunc", Err: tmperr, Stack: stack[:length]}
|
||||
command = "error"
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
oldreq := ctxt.Request()
|
||||
ctx, cancel := context.WithTimeout(oldreq.Context(), 15*time.Second)
|
||||
ctx, cancel := context.WithTimeout(oldreq.Context(), time.Duration(config.GlobalConfig.Tuning.Timeouts.PageExecute)*time.Second)
|
||||
defer cancel()
|
||||
ctxt.SetRequest(oldreq.WithContext(ctx))
|
||||
defer ctxt.SetRequest(oldreq)
|
||||
|
||||
Reference in New Issue
Block a user