2018-07-29 04:17:17 +00:00
package main
import (
"errors"
"log"
"sync/atomic"
"time"
2018-10-27 03:21:02 +00:00
"github.com/Azareal/Gosora/common"
2018-07-29 04:17:17 +00:00
)
// 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 {
2019-02-10 09:32:05 +00:00
db . SetConnMaxLifetime ( time . Second ) // Drop all the connections and start over
2018-07-29 04:17:17 +00:00
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" )
}
2019-02-10 09:32:05 +00:00
//db.SetConnMaxLifetime(time.Second * 60 * 5) // Make this infinite as the temporary lifetime change will purge the stale connections?
db . SetConnMaxLifetime ( - 1 )
2018-07-29 04:17:17 +00:00
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 + "'" )
}
}
2018-12-31 09:03:49 +00:00
func tickLoop ( thumbChan chan bool ) {
// TODO: Write tests for these
// Run this goroutine once every half second
halfSecondTicker := time . NewTicker ( time . Second / 2 )
secondTicker := time . NewTicker ( time . Second )
fifteenMinuteTicker := time . NewTicker ( 15 * time . Minute )
hourTicker := time . NewTicker ( time . Hour )
2018-07-29 04:17:17 +00:00
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.
}
}