Merge pull request #284 from writeas/high-load-error-page

Show 503 page on blogs under high load
This commit is contained in:
Matt Baer 2020-03-27 11:52:28 -04:00 committed by GitHub
commit dbd7eff7ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 45 additions and 1 deletions

View File

@ -22,3 +22,7 @@ func (db *datastore) isDuplicateKeyErr(err error) bool {
func (db *datastore) isIgnorableError(err error) bool { func (db *datastore) isIgnorableError(err error) bool {
return false return false
} }
func (db *datastore) isHighLoadError(err error) bool {
return false
}

View File

@ -40,3 +40,13 @@ func (db *datastore) isIgnorableError(err error) bool {
return false return false
} }
func (db *datastore) isHighLoadError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrMaxUserConns || mysqlErr.Number == mySQLErrTooManyConns
}
}
return false
}

View File

@ -1,7 +1,7 @@
// +build sqlite,!wflib // +build sqlite,!wflib
/* /*
* Copyright © 2019 A Bunch Tell LLC. * Copyright © 2019-2020 A Bunch Tell LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -60,3 +60,13 @@ func (db *datastore) isIgnorableError(err error) bool {
return false return false
} }
func (db *datastore) isHighLoadError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrMaxUserConns || mysqlErr.Number == mySQLErrTooManyConns
}
}
return false
}

View File

@ -39,6 +39,8 @@ import (
const ( const (
mySQLErrDuplicateKey = 1062 mySQLErrDuplicateKey = 1062
mySQLErrCollationMix = 1267 mySQLErrCollationMix = 1267
mySQLErrTooManyConns = 1040
mySQLErrMaxUserConns = 1203
driverMySQL = "mysql" driverMySQL = "mysql"
driverSQLite = "sqlite3" driverSQLite = "sqlite3"
@ -795,6 +797,8 @@ func (db *datastore) GetCollectionBy(condition string, value interface{}) (*Coll
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."} return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
case db.isHighLoadError(err):
return nil, ErrUnavailable
case err != nil: case err != nil:
log.Error("Failed selecting from collections: %v", err) log.Error("Failed selecting from collections: %v", err)
return nil, err return nil, err

View File

@ -37,6 +37,8 @@ var (
ErrInternalGeneral = impart.HTTPError{http.StatusInternalServerError, "The humans messed something up. They've been notified."} ErrInternalGeneral = impart.HTTPError{http.StatusInternalServerError, "The humans messed something up. They've been notified."}
ErrInternalCookieSession = impart.HTTPError{http.StatusInternalServerError, "Could not get cookie session."} ErrInternalCookieSession = impart.HTTPError{http.StatusInternalServerError, "Could not get cookie session."}
ErrUnavailable = impart.HTTPError{http.StatusServiceUnavailable, "Service temporarily unavailable due to high load."}
ErrCollectionNotFound = impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."} ErrCollectionNotFound = impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
ErrCollectionGone = impart.HTTPError{http.StatusGone, "This blog was unpublished."} ErrCollectionGone = impart.HTTPError{http.StatusGone, "This blog was unpublished."}
ErrCollectionPageNotFound = impart.HTTPError{http.StatusNotFound, "Collection page doesn't exist."} ErrCollectionPageNotFound = impart.HTTPError{http.StatusNotFound, "Collection page doesn't exist."}

View File

@ -83,6 +83,7 @@ type ErrorPages struct {
NotFound *template.Template NotFound *template.Template
Gone *template.Template Gone *template.Template
InternalServerError *template.Template InternalServerError *template.Template
UnavailableError *template.Template
Blank *template.Template Blank *template.Template
} }
@ -94,6 +95,7 @@ func NewHandler(apper Apper) *Handler {
NotFound: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>404</title></head><body><p>Not found.</p></body></html>{{end}}")), NotFound: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>404</title></head><body><p>Not found.</p></body></html>{{end}}")),
Gone: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>410</title></head><body><p>Gone.</p></body></html>{{end}}")), Gone: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>410</title></head><body><p>Gone.</p></body></html>{{end}}")),
InternalServerError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>500</title></head><body><p>Internal server error.</p></body></html>{{end}}")), InternalServerError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>500</title></head><body><p>Internal server error.</p></body></html>{{end}}")),
UnavailableError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>503</title></head><body><p>Service is temporarily unavailable.</p></body></html>{{end}}")),
Blank: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>{{.Title}}</title></head><body><p>{{.Content}}</p></body></html>{{end}}")), Blank: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>{{.Title}}</title></head><body><p>{{.Content}}</p></body></html>{{end}}")),
}, },
sessionStore: apper.App().SessionStore(), sessionStore: apper.App().SessionStore(),
@ -111,6 +113,7 @@ func NewWFHandler(apper Apper) *Handler {
NotFound: pages["404-general.tmpl"], NotFound: pages["404-general.tmpl"],
Gone: pages["410.tmpl"], Gone: pages["410.tmpl"],
InternalServerError: pages["500.tmpl"], InternalServerError: pages["500.tmpl"],
UnavailableError: pages["503.tmpl"],
Blank: pages["blank.tmpl"], Blank: pages["blank.tmpl"],
}) })
return h return h
@ -763,6 +766,10 @@ func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err er
log.Info("handleHTTPErorr internal error render") log.Info("handleHTTPErorr internal error render")
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r)) h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
return return
} else if err.Status == http.StatusServiceUnavailable {
w.WriteHeader(err.Status)
h.errors.UnavailableError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
return
} else if err.Status == http.StatusAccepted { } else if err.Status == http.StatusAccepted {
impart.WriteSuccess(w, "", err.Status) impart.WriteSuccess(w, "", err.Status)
return return

7
pages/503.tmpl Normal file
View File

@ -0,0 +1,7 @@
{{define "head"}}<title>Temporarily Unavailable &mdash; {{.SiteMetaName}}</title>{{end}}
{{define "content"}}
<div class="error-page">
<p class="msg">The words aren't coming to me. &#x1F5C5;</p>
<p>We couldn't serve this page due to high server load. This should only be temporary.</p>
</div>
{{end}}