Browse Source

Show 503 page on collections under high load

This acknowledges "too many connections" and "max user connections"
errors in MySQL and propagates the error up the chain so we can notify
the user and return the correct HTTP code.
pull/284/head
Matt Baer 4 years ago
parent
commit
0285a9b0bd
5 changed files with 30 additions and 0 deletions
  1. +10
    -0
      database-no-sqlite.go
  2. +4
    -0
      database.go
  3. +2
    -0
      errors.go
  4. +7
    -0
      handle.go
  5. +7
    -0
      pages/503.tmpl

+ 10
- 0
database-no-sqlite.go 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
}

+ 4
- 0
database.go 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"
@@ -793,6 +795,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


+ 2
- 0
errors.go 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."}


+ 7
- 0
handle.go 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
- 0
pages/503.tmpl 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}}

Loading…
Cancel
Save