github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/tickloop.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "database/sql" 6 "log" 7 "net/http/httptest" 8 "strconv" 9 "time" 10 11 c "github.com/Azareal/Gosora/common" 12 "github.com/Azareal/Gosora/routes" 13 "github.com/Azareal/Gosora/uutils" 14 "github.com/pkg/errors" 15 ) 16 17 var TickLoop *c.TickLoop 18 19 func runHook(name string) error { 20 if e := c.RunTaskHook(name); e != nil { 21 return errors.Wrap(e, "Failed at task '"+name+"'") 22 } 23 return nil 24 } 25 26 func deferredDailies() error { 27 lastDailyStr, e := c.Meta.Get("lastDaily") 28 // TODO: Report this error back correctly... 29 if e != nil && e != sql.ErrNoRows { 30 return e 31 } 32 lastDaily, _ := strconv.ParseInt(lastDailyStr, 10, 64) 33 low := time.Now().Unix() - (60 * 60 * 24) 34 if lastDaily < low { 35 if e := c.Dailies(); e != nil { 36 return e 37 } 38 } 39 return nil 40 } 41 42 func handleLogLongTick(name string, cn int64, secs int) { 43 if !c.Dev.LogLongTick { 44 return 45 } 46 dur := time.Duration(uutils.Nanotime() - cn) 47 if dur.Seconds() > float64(secs) { 48 log.Print("tick " + name + " completed in " + dur.String()) 49 } 50 } 51 52 func tickLoop(thumbChan chan bool) error { 53 tl := c.NewTickLoop() 54 TickLoop = tl 55 if e := deferredDailies(); e != nil { 56 return e 57 } 58 if e := c.StartupTasks(); e != nil { 59 return e 60 } 61 62 startTick := func(ch chan bool) (ret bool) { 63 if c.Dev.HourDBTimeout { 64 go func() { 65 defer c.EatPanics() 66 ch <- c.StartTick() 67 }() 68 return <-ch 69 } 70 return c.StartTick() 71 } 72 tick := func(name string, tasks c.TaskSet, secs int) error { 73 tw := c.NewTickWatch() 74 tw.Name = name 75 tw.Set(&tw.Start, uutils.Nanotime()) 76 tw.Run() 77 defer tw.Stop() 78 ch := make(chan bool) 79 tw.OutEndChan = ch 80 if startTick(ch) { 81 return nil 82 } 83 tw.Set(&tw.DBCheck, uutils.Nanotime()) 84 if e := runHook("before_" + name + "_tick"); e != nil { 85 return e 86 } 87 cn := uutils.Nanotime() 88 tw.Set(&tw.StartHook, cn) 89 if e := tasks.Run(); e != nil { 90 return e 91 } 92 tw.Set(&tw.Tasks, uutils.Nanotime()) 93 handleLogLongTick(name, cn, secs) 94 if e := runHook("after_" + name + "_tick"); e != nil { 95 return e 96 } 97 tw.Set(&tw.EndHook, uutils.Nanotime()) 98 //close(tw.OutEndChan) 99 return nil 100 } 101 102 tl.HalfSecf = func() error { 103 return tick("half_second", c.Tasks.HalfSec, 2) 104 } 105 // TODO: Automatically lock topics, if they're really old, and the associated setting is enabled. 106 // TODO: Publish scheduled posts. 107 tl.FifteenMinf = func() error { 108 return tick("fifteen_minute", c.Tasks.FifteenMin, 5) 109 } 110 // TODO: Handle the instance going down a lot better 111 // TODO: Handle the daily clean-up. 112 tl.Dayf = func() error { 113 if c.StartTick() { 114 return nil 115 } 116 cn := uutils.Nanotime() 117 if e := c.Dailies(); e != nil { 118 return e 119 } 120 handleLogLongTick("day", cn, 5) 121 return nil 122 } 123 124 tl.Secf = func() (e error) { 125 if c.StartTick() { 126 return nil 127 } 128 if e = runHook("before_second_tick"); e != nil { 129 return e 130 } 131 cn := uutils.Nanotime() 132 go func() { 133 defer c.EatPanics() 134 thumbChan <- true 135 }() 136 137 if e = c.Tasks.Sec.Run(); e != nil { 138 return e 139 } 140 141 // TODO: Stop hard-coding this 142 if e = c.HandleExpiredScheduledGroups(); e != nil { 143 return e 144 } 145 146 // TODO: Handle delayed moderation tasks 147 148 // Sync with the database, if there are any changes 149 if e = c.HandleServerSync(); e != nil { 150 return e 151 } 152 handleLogLongTick("second", cn, 3) 153 154 // TODO: Manage the TopicStore, UserStore, and ForumStore 155 // TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high 156 // TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task? 157 // TODO: Rescan the static files for changes 158 return runHook("after_second_tick") 159 } 160 161 tl.Hourf = func() error { 162 if c.StartTick() { 163 return nil 164 } 165 if e := runHook("before_hour_tick"); e != nil { 166 return e 167 } 168 cn := uutils.Nanotime() 169 170 jsToken, e := c.GenerateSafeString(80) 171 if e != nil { 172 return e 173 } 174 c.JSTokenBox.Store(jsToken) 175 176 c.OldSessionSigningKeyBox.Store(c.SessionSigningKeyBox.Load().(string)) // TODO: We probably don't need this type conversion 177 sessionSigningKey, e := c.GenerateSafeString(80) 178 if e != nil { 179 return e 180 } 181 c.SessionSigningKeyBox.Store(sessionSigningKey) 182 183 if e = c.Tasks.Hour.Run(); e != nil { 184 return e 185 } 186 if e = PingLastTopicTick(); e != nil { 187 return e 188 } 189 handleLogLongTick("hour", cn, 5) 190 return runHook("after_hour_tick") 191 } 192 193 c.CTickLoop = tl 194 return nil 195 } 196 197 func sched() error { 198 ws := errors.WithStack 199 schedStr, err := c.Meta.Get("sched") 200 // TODO: Report this error back correctly... 201 if err != nil && err != sql.ErrNoRows { 202 return ws(err) 203 } 204 205 if schedStr == "recalc" { 206 log.Print("Cleaning up orphaned data.") 207 208 count, err := c.Recalc.Replies() 209 if err != nil { 210 return ws(err) 211 } 212 log.Printf("Deleted %d orphaned replies.", count) 213 214 count, err = c.Recalc.Forums() 215 if err != nil { 216 return ws(err) 217 } 218 log.Printf("Recalculated %d forum topic counts.", count) 219 220 count, err = c.Recalc.Subscriptions() 221 if err != nil { 222 return ws(err) 223 } 224 log.Printf("Deleted %d orphaned subscriptions.", count) 225 226 count, err = c.Recalc.ActivityStream() 227 if err != nil { 228 return ws(err) 229 } 230 log.Printf("Deleted %d orphaned activity stream items.", count) 231 232 err = c.Recalc.Users() 233 if err != nil { 234 return ws(err) 235 } 236 log.Print("Recalculated user post stats.") 237 238 count, err = c.Recalc.Attachments() 239 if err != nil { 240 return ws(err) 241 } 242 log.Printf("Deleted %d orphaned attachments.", count) 243 } 244 245 return nil 246 } 247 248 var pingLastTopicCount = 1 249 250 // TODO: Move somewhere else 251 func PingLastTopicTick() error { 252 g, e := c.Groups.Get(c.GuestUser.Group) 253 if e != nil { 254 return e 255 } 256 tList, _, _, e := c.TopicList.GetListByGroup(g, 1, 0, nil) 257 if e != nil { 258 return e 259 } 260 if len(tList) == 0 { 261 return nil 262 } 263 w := httptest.NewRecorder() 264 sid := strconv.Itoa(tList[0].ID) 265 req := httptest.NewRequest("get", "/topic/"+sid, bytes.NewReader(nil)) 266 cn := uutils.Nanotime() 267 268 // Deal with the session stuff, etc. 269 ucpy, ok := c.PreRoute(w, req) 270 if !ok { 271 return errors.New("preroute failed") 272 } 273 head, rerr := c.UserCheck(w, req, &ucpy) 274 if rerr != nil { 275 return errors.New(rerr.Error()) 276 } 277 rerr = routes.ViewTopic(w, req, &ucpy, head, sid) 278 if rerr != nil { 279 return errors.New(rerr.Error()) 280 } 281 /*if w.Code != 200 { 282 return errors.New("topic code not 200") 283 }*/ 284 285 dur := time.Duration(uutils.Nanotime() - cn) 286 if dur.Seconds() > 5 { 287 c.Log("topic " + sid + " completed in " + dur.String()) 288 } else if c.Dev.Log4thLongRoute { 289 pingLastTopicCount++ 290 if pingLastTopicCount == 4 { 291 c.Log("topic " + sid + " completed in " + dur.String()) 292 } 293 if pingLastTopicCount >= 4 { 294 pingLastTopicCount = 1 295 } 296 } 297 298 return nil 299 }