2018-07-29 04:17:17 +00:00
package main
import (
"errors"
"log"
"sync/atomic"
"time"
2019-05-06 04:04:00 +00:00
"database/sql"
2018-07-29 04:17:17 +00:00
2019-04-19 08:20:10 +00:00
c "github.com/Azareal/Gosora/common"
2019-05-06 04:04:00 +00:00
"github.com/Azareal/Gosora/query_gen"
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 {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
}
}
func startTick ( ) ( abort bool ) {
2019-04-19 08:20:10 +00:00
var isDBDown = atomic . LoadInt32 ( & c . IsDBDown )
2018-07-29 04:17:17 +00:00
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
2019-04-19 08:20:10 +00:00
c . LogWarning ( err )
c . LogWarning ( errors . New ( "The database is down" ) )
2018-07-29 04:17:17 +00:00
}
2019-04-19 08:20:10 +00:00
atomic . StoreInt32 ( & c . IsDBDown , 1 )
2018-07-29 04:17:17 +00:00
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 )
2019-04-19 08:20:10 +00:00
atomic . StoreInt32 ( & c . IsDBDown , 0 )
2018-07-29 04:17:17 +00:00
return false
}
func runHook ( name string ) {
2019-04-19 08:20:10 +00:00
err := c . RunTaskHook ( name )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err , "Failed at task '" + name + "'" )
2018-07-29 04:17:17 +00:00
}
}
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 )
2019-05-06 04:04:00 +00:00
dailyTicker := time . NewTicker ( time . Hour * 24 )
2018-07-29 04:17:17 +00:00
for {
select {
case <- halfSecondTicker . C :
if startTick ( ) {
continue
}
runHook ( "before_half_second_tick" )
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledHalfSecondTasks )
2018-07-29 04:17:17 +00:00
runHook ( "after_half_second_tick" )
case <- secondTicker . C :
if startTick ( ) {
continue
}
runHook ( "before_second_tick" )
go func ( ) { thumbChan <- true } ( )
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledSecondTasks )
2018-07-29 04:17:17 +00:00
// TODO: Stop hard-coding this
2019-04-19 08:20:10 +00:00
err := c . HandleExpiredScheduledGroups ( )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
// TODO: Handle delayed moderation tasks
// Sync with the database, if there are any changes
2019-04-19 08:20:10 +00:00
err = c . HandleServerSync ( )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
// 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" )
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledFifteenMinuteTasks )
2018-07-29 04:17:17 +00:00
// 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" )
2019-04-19 08:20:10 +00:00
jsToken , err := c . GenerateSafeString ( 80 )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
2019-04-19 08:20:10 +00:00
c . JSTokenBox . Store ( jsToken )
2018-07-29 04:17:17 +00:00
2019-04-19 08:20:10 +00:00
c . OldSessionSigningKeyBox . Store ( c . SessionSigningKeyBox . Load ( ) . ( string ) ) // TODO: We probably don't need this type conversion
sessionSigningKey , err := c . GenerateSafeString ( 80 )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
2019-04-19 08:20:10 +00:00
c . SessionSigningKeyBox . Store ( sessionSigningKey )
2018-07-29 04:17:17 +00:00
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledHourTasks )
2018-07-29 04:17:17 +00:00
runHook ( "after_hour_tick" )
2019-05-06 04:04:00 +00:00
// TODO: Handle the instance going down a lot better
case <- dailyTicker . C :
// TODO: Find a more efficient way of doing this
err := qgen . NewAcc ( ) . Select ( "activity_stream" ) . Cols ( "asid" ) . EachInt ( func ( asid int ) error {
count , err := qgen . NewAcc ( ) . Count ( "activity_stream_matches" ) . Where ( "asid = ?" ) . Total ( )
if err != sql . ErrNoRows {
return err
}
if count > 0 {
return nil
}
_ , err = qgen . NewAcc ( ) . Delete ( "activity_stream" ) . Where ( "asid = ?" ) . Run ( asid )
return err
} )
if err != nil && err != sql . ErrNoRows {
c . LogError ( err )
}
2018-07-29 04:17:17 +00:00
}
// TODO: Handle the daily clean-up.
}
}