gosora/common/counters/forums.go
Azareal 6870d242e9 Add ClearIPs() to TopicStore.
Add LockMany() to TopicStore.
Add RemoveMany() to TopicCache.
Add ClearIPs() to PollStore.
Add Purge() to RegLogStore.
Add DeleteOlderThanDays() to RegLogStore.
Add Purge() to LoginLogStore.
Add DeleteOlderThanDays() to LoginLogStore.
Add SetInt() to MetaStore.
Add SetInt64() to MetaStore.

Use Createf() in RegLogItem.Create()
Use Count() in SQLRegLogStore.Count()
Use Count() in SQLLoginLogStore.Count()
Use Countf() in SQLLoginLogStore.CountUser()

Add trailing triple dot parser test case.
Removed a block of commented code in gen router.
Reduce boilerplate.
2021-04-27 20:20:26 +10:00

109 lines
2.7 KiB
Go

package counters
import (
"database/sql"
"sync"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
)
var ForumViewCounter *DefaultForumViewCounter
// 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.NewAcc()
co := &DefaultForumViewCounter{
oddMap: make(map[int]*RWMutexCounterBucket),
evenMap: make(map[int]*RWMutexCounterBucket),
insert: acc.Insert("viewchunks_forums").Columns("count,createdAt,forum").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
}
c.AddScheduledFifteenMinuteTask(co.Tick) // There could be a lot of routes, so we don't want to be running this every second
//c.AddScheduledSecondTask(co.Tick)
c.AddShutdownTask(co.Tick)
return co, acc.FirstError()
}
func (co *DefaultForumViewCounter) Tick() error {
cLoop := func(l *sync.RWMutex, m map[int]*RWMutexCounterBucket) error {
l.RLock()
for fid, f := range m {
l.RUnlock()
var count int
f.RLock()
count = f.counter
f.RUnlock()
// TODO: Only delete the bucket when it's zero to avoid hitting popular forums?
l.Lock()
delete(m, fid)
l.Unlock()
e := co.insertChunk(count, fid)
if e != nil {
return errors.Wrap(errors.WithStack(e),"forum counter")
}
l.RLock()
}
l.RUnlock()
return nil
}
e := cLoop(&co.oddLock,co.oddMap)
if e != nil {
return e
}
return cLoop(&co.evenLock,co.evenMap)
}
func (co *DefaultForumViewCounter) insertChunk(count, forum int) error {
if count == 0 {
return nil
}
c.DebugLogf("Inserting a vchunk with a count of %d for forum %d", count, forum)
_, e := co.insert.Exec(count, forum)
return e
}
func (co *DefaultForumViewCounter) Bump(fid int) {
// Is the ID even?
if fid%2 == 0 {
co.evenLock.RLock()
f, ok := co.evenMap[fid]
co.evenLock.RUnlock()
if ok {
f.Lock()
f.counter++
f.Unlock()
} else {
co.evenLock.Lock()
co.evenMap[fid] = &RWMutexCounterBucket{counter: 1}
co.evenLock.Unlock()
}
return
}
co.oddLock.RLock()
f, ok := co.oddMap[fid]
co.oddLock.RUnlock()
if ok {
f.Lock()
f.counter++
f.Unlock()
} else {
co.oddLock.Lock()
co.oddMap[fid] = &RWMutexCounterBucket{counter: 1}
co.oddLock.Unlock()
}
}
// TODO: Add a forum counter backed by two maps which grow as forums are created but never shrinks