Database connection drops are handled better now.

This commit is contained in:
Azareal 2018-07-15 20:29:31 +10:00
parent 9c6af9dd01
commit d0907134ef
5 changed files with 50 additions and 0 deletions

View File

@ -28,6 +28,7 @@ var TmplPtrMap = make(map[string]interface{})
var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else
var SessionSigningKeyBox atomic.Value // For MFA to avoid hitting the database unneccesarily var SessionSigningKeyBox atomic.Value // For MFA to avoid hitting the database unneccesarily
var OldSessionSigningKeyBox atomic.Value // Just in case we've signed with a key that's about to go stale so we don't annoy the user too much var OldSessionSigningKeyBox atomic.Value // Just in case we've signed with a key that's about to go stale so we don't annoy the user too much
var IsDBDown int32 = 0 // 0 = false, 1 = true. this is value which should be manipulated with package atomic for representing whether the database is down so we don't spam the log with lots of redundant errors
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores // ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
var ErrNoRows = sql.ErrNoRows var ErrNoRows = sql.ErrNoRows

View File

@ -124,6 +124,13 @@ func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteErr
return HandledRouteError() return HandledRouteError()
} }
// When the task system detects if the database is down, some database errors might lip by this
func DatabaseError(w http.ResponseWriter, r *http.Request) RouteError {
pi := ErrorPage{errorHeader(w, GuestUser, "Internal Server Error"), "A problem has occurred in the system."}
handleErrorTemplate(w, r, pi)
return HandledRouteError()
}
var xmlInternalError = []byte(`<?xml version="1.0" encoding="UTF-8"?> var xmlInternalError = []byte(`<?xml version="1.0" encoding="UTF-8"?>
<error>A problem has occured</error>`) <error>A problem has occured</error>`)

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"strconv" "strconv"
"sync" "sync"
"sync/atomic"
"errors" "errors"
"os" "os"
"net/http" "net/http"
@ -701,6 +702,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
routes.StaticFile(w, req) routes.StaticFile(w, req)
return return
} }
if atomic.LoadInt32(&common.IsDBDown) == 1 {
common.DatabaseError(w, req)
return
}
if common.Dev.SuperDebug { if common.Dev.SuperDebug {
router.requestLogger.Print("before PreRoute") router.requestLogger.Print("before PreRoute")
} }

32
main.go
View File

@ -16,6 +16,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
"sync/atomic"
"syscall" "syscall"
"time" "time"
@ -346,6 +347,7 @@ func main() {
} }
log.Print("Initialising the task system") log.Print("Initialising the task system")
// TODO: Name the tasks so we can figure out which one it was when something goes wrong? Or maybe toss it up WithStack down there?
var runTasks = func(tasks []func() error) { var runTasks = func(tasks []func() error) {
for _, task := range tasks { for _, task := range tasks {
if task() != nil { if task() != nil {
@ -361,6 +363,24 @@ func main() {
fifteenMinuteTicker := time.NewTicker(15 * time.Minute) fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
hourTicker := time.NewTicker(time.Hour) hourTicker := time.NewTicker(time.Hour)
go func() { go func() {
var startTick = func() (abort bool) {
var isDBDown = atomic.LoadInt32(&common.IsDBDown)
err := db.Ping()
if err != nil {
// TODO: There's a bit of a race here, but it doesn't matter if this error appears multiple times in the logs as it's capped at three times, we just want to cut it down 99% of the time
if isDBDown == 0 {
common.LogWarning(err)
common.LogWarning(errors.New("The database is down"))
}
atomic.StoreInt32(&common.IsDBDown, 1)
return true
}
if isDBDown == 1 {
log.Print("The database is back")
}
atomic.StoreInt32(&common.IsDBDown, 0)
return false
}
var runHook = func(name string) { var runHook = func(name string) {
err := common.RunTaskHook(name) err := common.RunTaskHook(name)
if err != nil { if err != nil {
@ -370,10 +390,16 @@ func main() {
for { for {
select { select {
case <-halfSecondTicker.C: case <-halfSecondTicker.C:
if startTick() {
continue
}
runHook("before_half_second_tick") runHook("before_half_second_tick")
runTasks(common.ScheduledHalfSecondTasks) runTasks(common.ScheduledHalfSecondTasks)
runHook("after_half_second_tick") runHook("after_half_second_tick")
case <-secondTicker.C: case <-secondTicker.C:
if startTick() {
continue
}
runHook("before_second_tick") runHook("before_second_tick")
runTasks(common.ScheduledSecondTasks) runTasks(common.ScheduledSecondTasks)
@ -397,6 +423,9 @@ func main() {
// TODO: Rescan the static files for changes // TODO: Rescan the static files for changes
runHook("after_second_tick") runHook("after_second_tick")
case <-fifteenMinuteTicker.C: case <-fifteenMinuteTicker.C:
if startTick() {
continue
}
runHook("before_fifteen_minute_tick") runHook("before_fifteen_minute_tick")
runTasks(common.ScheduledFifteenMinuteTasks) runTasks(common.ScheduledFifteenMinuteTasks)
@ -404,6 +433,9 @@ func main() {
// TODO: Publish scheduled posts. // TODO: Publish scheduled posts.
runHook("after_fifteen_minute_tick") runHook("after_fifteen_minute_tick")
case <-hourTicker.C: case <-hourTicker.C:
if startTick() {
continue
}
runHook("before_hour_tick") runHook("before_hour_tick")
jsToken, err := common.GenerateSafeString(80) jsToken, err := common.GenerateSafeString(80)

View File

@ -225,6 +225,7 @@ import (
"strings" "strings"
"strconv" "strconv"
"sync" "sync"
"sync/atomic"
"errors" "errors"
"os" "os"
"net/http" "net/http"
@ -481,6 +482,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
routes.StaticFile(w, req) routes.StaticFile(w, req)
return return
} }
if atomic.LoadInt32(&common.IsDBDown) == 1 {
common.DatabaseError(w, req)
return
}
if common.Dev.SuperDebug { if common.Dev.SuperDebug {
router.requestLogger.Print("before PreRoute") router.requestLogger.Print("before PreRoute")
} }