Move the tickloop logic into it's own file.

This commit is contained in:
Azareal 2018-07-29 14:17:17 +10:00
parent c25b289076
commit f9a579ae4d
2 changed files with 123 additions and 107 deletions

110
main.go
View File

@ -18,7 +18,6 @@ import (
"os/signal" "os/signal"
"strconv" "strconv"
"strings" "strings"
"sync/atomic"
"syscall" "syscall"
"time" "time"
@ -352,14 +351,6 @@ 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) {
for _, task := range tasks {
if task() != nil {
common.LogError(err)
}
}
}
// Thumbnailer goroutine, we only want one image being thumbnailed at a time, otherwise they might wind up consuming all the CPU time and leave no resources left to service the actual requests // Thumbnailer goroutine, we only want one image being thumbnailed at a time, otherwise they might wind up consuming all the CPU time and leave no resources left to service the actual requests
// TODO: Could we expand this to attachments and other things too? // TODO: Could we expand this to attachments and other things too?
@ -413,109 +404,14 @@ func main() {
} }
} }
}() }()
// TODO: Write tests for these // TODO: Write tests for these
// Run this goroutine once every half second // Run this goroutine once every half second
halfSecondTicker := time.NewTicker(time.Second / 2) halfSecondTicker := time.NewTicker(time.Second / 2)
secondTicker := time.NewTicker(time.Second) secondTicker := time.NewTicker(time.Second)
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 tickLoop(thumbChan, halfSecondTicker, secondTicker, fifteenMinuteTicker, hourTicker)
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) {
err := common.RunTaskHook(name)
if err != nil {
common.LogError(err, "Failed at task '"+name+"'")
}
}
for {
select {
case <-halfSecondTicker.C:
if startTick() {
continue
}
runHook("before_half_second_tick")
runTasks(common.ScheduledHalfSecondTasks)
runHook("after_half_second_tick")
case <-secondTicker.C:
if startTick() {
continue
}
runHook("before_second_tick")
go func() { thumbChan <- true }()
runTasks(common.ScheduledSecondTasks)
// TODO: Stop hard-coding this
err := common.HandleExpiredScheduledGroups()
if err != nil {
common.LogError(err)
}
// TODO: Handle delayed moderation tasks
// Sync with the database, if there are any changes
err = common.HandleServerSync()
if err != nil {
common.LogError(err)
}
// TODO: Manage the TopicStore, UserStore, and ForumStore
// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
// TODO: Rescan the static files for changes
runHook("after_second_tick")
case <-fifteenMinuteTicker.C:
if startTick() {
continue
}
runHook("before_fifteen_minute_tick")
runTasks(common.ScheduledFifteenMinuteTasks)
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TODO: Publish scheduled posts.
runHook("after_fifteen_minute_tick")
case <-hourTicker.C:
if startTick() {
continue
}
runHook("before_hour_tick")
jsToken, err := common.GenerateSafeString(80)
if err != nil {
common.LogError(err)
}
common.JSTokenBox.Store(jsToken)
common.OldSessionSigningKeyBox.Store(common.SessionSigningKeyBox.Load().(string)) // TODO: We probably don't need this type conversion
sessionSigningKey, err := common.GenerateSafeString(80)
if err != nil {
common.LogError(err)
}
common.SessionSigningKeyBox.Store(sessionSigningKey)
runTasks(common.ScheduledHourTasks)
runHook("after_hour_tick")
}
// TODO: Handle the daily clean-up.
}
}()
log.Print("Initialising the router") log.Print("Initialising the router")
router, err = NewGenRouter(http.FileServer(http.Dir("./uploads"))) router, err = NewGenRouter(http.FileServer(http.Dir("./uploads")))
@ -549,7 +445,7 @@ func main() {
// TODO: Add a graceful shutdown function // TODO: Add a graceful shutdown function
func stoppedServer(msg ...interface{}) { func stoppedServer(msg ...interface{}) {
log.Print("stopped server") //log.Print("stopped server")
stopServerChan <- msg stopServerChan <- msg
} }

120
tickloop.go Normal file
View File

@ -0,0 +1,120 @@
package main
import (
"errors"
"log"
"sync/atomic"
"time"
"./common"
)
// 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?
func runTasks(tasks []func() error) {
for _, task := range tasks {
err := task()
if err != nil {
common.LogError(err)
}
}
}
func startTick() (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
}
func runHook(name string) {
err := common.RunTaskHook(name)
if err != nil {
common.LogError(err, "Failed at task '"+name+"'")
}
}
func tickLoop(thumbChan chan bool, halfSecondTicker *time.Ticker, secondTicker *time.Ticker, fifteenMinuteTicker *time.Ticker, hourTicker *time.Ticker) {
for {
select {
case <-halfSecondTicker.C:
if startTick() {
continue
}
runHook("before_half_second_tick")
runTasks(common.ScheduledHalfSecondTasks)
runHook("after_half_second_tick")
case <-secondTicker.C:
if startTick() {
continue
}
runHook("before_second_tick")
go func() { thumbChan <- true }()
runTasks(common.ScheduledSecondTasks)
// TODO: Stop hard-coding this
err := common.HandleExpiredScheduledGroups()
if err != nil {
common.LogError(err)
}
// TODO: Handle delayed moderation tasks
// Sync with the database, if there are any changes
err = common.HandleServerSync()
if err != nil {
common.LogError(err)
}
// TODO: Manage the TopicStore, UserStore, and ForumStore
// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
// TODO: Rescan the static files for changes
runHook("after_second_tick")
case <-fifteenMinuteTicker.C:
if startTick() {
continue
}
runHook("before_fifteen_minute_tick")
runTasks(common.ScheduledFifteenMinuteTasks)
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TODO: Publish scheduled posts.
runHook("after_fifteen_minute_tick")
case <-hourTicker.C:
if startTick() {
continue
}
runHook("before_hour_tick")
jsToken, err := common.GenerateSafeString(80)
if err != nil {
common.LogError(err)
}
common.JSTokenBox.Store(jsToken)
common.OldSessionSigningKeyBox.Store(common.SessionSigningKeyBox.Load().(string)) // TODO: We probably don't need this type conversion
sessionSigningKey, err := common.GenerateSafeString(80)
if err != nil {
common.LogError(err)
}
common.SessionSigningKeyBox.Store(sessionSigningKey)
runTasks(common.ScheduledHourTasks)
runHook("after_hour_tick")
}
// TODO: Handle the daily clean-up.
}
}