87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
|
package counters
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"sync"
|
||
|
|
||
|
".."
|
||
|
"../../query_gen/lib"
|
||
|
)
|
||
|
|
||
|
// TODO: The forum view counter
|
||
|
|
||
|
// TODO: Unload forum counters without any views over the past 15 minutes, if the admin has configured the forumstore with a cap and it's been hit?
|
||
|
// Forums can be reloaded from the database at any time, so we want to keep the counters separate from them
|
||
|
type DefaultForumViewCounter struct {
|
||
|
oddMap map[int]*RWMutexCounterBucket // map[fid]struct{counter,sync.RWMutex}
|
||
|
evenMap map[int]*RWMutexCounterBucket
|
||
|
oddLock sync.RWMutex
|
||
|
evenLock sync.RWMutex
|
||
|
|
||
|
insert *sql.Stmt
|
||
|
}
|
||
|
|
||
|
func NewDefaultForumViewCounter() (*DefaultForumViewCounter, error) {
|
||
|
acc := qgen.Builder.Accumulator()
|
||
|
counter := &DefaultForumViewCounter{
|
||
|
oddMap: make(map[int]*RWMutexCounterBucket),
|
||
|
evenMap: make(map[int]*RWMutexCounterBucket),
|
||
|
insert: acc.Insert("viewchunks_forums").Columns("count, createdAt, forum").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
|
||
|
}
|
||
|
common.AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second
|
||
|
//AddScheduledSecondTask(counter.Tick)
|
||
|
common.AddShutdownTask(counter.Tick)
|
||
|
return counter, acc.FirstError()
|
||
|
}
|
||
|
|
||
|
func (counter *DefaultForumViewCounter) Tick() error {
|
||
|
counter.oddLock.RLock()
|
||
|
oddMap := counter.oddMap
|
||
|
counter.oddLock.RUnlock()
|
||
|
for forumID, forum := range oddMap {
|
||
|
var count int
|
||
|
forum.RLock()
|
||
|
count = forum.counter
|
||
|
forum.RUnlock()
|
||
|
// TODO: Only delete the bucket when it's zero to avoid hitting popular forums?
|
||
|
counter.oddLock.Lock()
|
||
|
delete(counter.oddMap, forumID)
|
||
|
counter.oddLock.Unlock()
|
||
|
err := counter.insertChunk(count, forumID)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
counter.evenLock.RLock()
|
||
|
evenMap := counter.evenMap
|
||
|
counter.evenLock.RUnlock()
|
||
|
for forumID, forum := range evenMap {
|
||
|
var count int
|
||
|
forum.RLock()
|
||
|
count = forum.counter
|
||
|
forum.RUnlock()
|
||
|
// TODO: Only delete the bucket when it's zero to avoid hitting popular forums?
|
||
|
counter.evenLock.Lock()
|
||
|
delete(counter.evenMap, forumID)
|
||
|
counter.evenLock.Unlock()
|
||
|
err := counter.insertChunk(count, forumID)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (counter *DefaultForumViewCounter) insertChunk(count int, forum int) error {
|
||
|
if count == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
common.DebugLogf("Inserting a viewchunk with a count of %d for forum %d", count, forum)
|
||
|
_, err := counter.insert.Exec(count, forum)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// TODO: Add a forum counter backed by two maps which grow as forums are created but never shrinks
|