fix the instance crashing after deleting a topic due to a topic view counter error

simplify the topic view counter
This commit is contained in:
Azareal 2019-07-28 13:06:04 +10:00
parent 99bc972ad2
commit 6d805a13cf
1 changed files with 51 additions and 57 deletions

View File

@ -7,6 +7,7 @@ import (
c "github.com/Azareal/Gosora/common" c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/query_gen" "github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
) )
var TopicViewCounter *DefaultTopicViewCounter var TopicViewCounter *DefaultTopicViewCounter
@ -23,68 +24,59 @@ type DefaultTopicViewCounter struct {
func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) { func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) {
acc := qgen.NewAcc() acc := qgen.NewAcc()
counter := &DefaultTopicViewCounter{ co := &DefaultTopicViewCounter{
oddTopics: make(map[int]*RWMutexCounterBucket), oddTopics: make(map[int]*RWMutexCounterBucket),
evenTopics: make(map[int]*RWMutexCounterBucket), evenTopics: make(map[int]*RWMutexCounterBucket),
update: acc.Update("topics").Set("views = views + ?").Where("tid = ?").Prepare(), update: acc.Update("topics").Set("views = views + ?").Where("tid = ?").Prepare(),
} }
c.AddScheduledFifteenMinuteTask(counter.Tick) // Who knows how many topics we have queued up, we probably don't want this running too frequently c.AddScheduledFifteenMinuteTask(co.Tick) // Who knows how many topics we have queued up, we probably don't want this running too frequently
//c.AddScheduledSecondTask(counter.Tick) //c.AddScheduledSecondTask(co.Tick)
c.AddShutdownTask(counter.Tick) c.AddShutdownTask(co.Tick)
return counter, acc.FirstError() return co, acc.FirstError()
} }
func (counter *DefaultTopicViewCounter) Tick() error { func (co *DefaultTopicViewCounter) Tick() error {
// TODO: Fold multiple 1 view topics into one query // TODO: Fold multiple 1 view topics into one query
counter.oddLock.RLock() cLoop := func(l *sync.RWMutex, m map[int]*RWMutexCounterBucket) error {
oddTopics := counter.oddTopics l.RLock()
counter.oddLock.RUnlock() for topicID, topic := range m {
for topicID, topic := range oddTopics { l.RUnlock()
var count int var count int
topic.RLock() topic.RLock()
count = topic.counter count = topic.counter
topic.RUnlock() topic.RUnlock()
// TODO: Only delete the bucket when it's zero to avoid hitting popular topics? // TODO: Only delete the bucket when it's zero to avoid hitting popular topics?
counter.oddLock.Lock() l.Lock()
delete(counter.oddTopics, topicID) delete(m, topicID)
counter.oddLock.Unlock() l.Unlock()
err := counter.insertChunk(count, topicID) err := co.insertChunk(count, topicID)
if err != nil { if err != nil {
return err return errors.Wrap(errors.WithStack(err),"topicview counter")
} }
l.RLock()
} }
l.RUnlock()
counter.evenLock.RLock()
evenTopics := counter.evenTopics
counter.evenLock.RUnlock()
for topicID, topic := range evenTopics {
var count int
topic.RLock()
count = topic.counter
topic.RUnlock()
// TODO: Only delete the bucket when it's zero to avoid hitting popular topics?
counter.evenLock.Lock()
delete(counter.evenTopics, topicID)
counter.evenLock.Unlock()
err := counter.insertChunk(count, topicID)
if err != nil {
return err
}
}
return nil return nil
}
err := cLoop(&co.oddLock,co.oddTopics)
if err != nil {
return err
}
return cLoop(&co.evenLock,co.evenTopics)
} }
// TODO: Optimise this further. E.g. Using IN() on every one view topic. Rinse and repeat for two views, three views, four views and five views. // TODO: Optimise this further. E.g. Using IN() on every one view topic. Rinse and repeat for two views, three views, four views and five views.
func (counter *DefaultTopicViewCounter) insertChunk(count int, topicID int) error { func (co *DefaultTopicViewCounter) insertChunk(count int, topicID int) error {
if count == 0 { if count == 0 {
return nil return nil
} }
c.DebugLogf("Inserting %d views into topic %d", count, topicID) c.DebugLogf("Inserting %d views into topic %d", count, topicID)
_, err := counter.update.Exec(count, topicID) _, err := co.update.Exec(count, topicID)
if err != nil { if err == sql.ErrNoRows {
return nil
} else if err != nil {
return err return err
} }
@ -92,7 +84,9 @@ func (counter *DefaultTopicViewCounter) insertChunk(count int, topicID int) erro
tcache := c.Topics.GetCache() tcache := c.Topics.GetCache()
if tcache != nil { if tcache != nil {
topic, err := tcache.Get(topicID) topic, err := tcache.Get(topicID)
if err != nil { if err == sql.ErrNoRows {
return nil
} else if err != nil {
return err return err
} }
atomic.AddInt64(&topic.ViewCount, int64(count)) atomic.AddInt64(&topic.ViewCount, int64(count))
@ -101,34 +95,34 @@ func (counter *DefaultTopicViewCounter) insertChunk(count int, topicID int) erro
return nil return nil
} }
func (counter *DefaultTopicViewCounter) Bump(topicID int) { func (co *DefaultTopicViewCounter) Bump(topicID int) {
// Is the ID even? // Is the ID even?
if topicID%2 == 0 { if topicID%2 == 0 {
counter.evenLock.RLock() co.evenLock.RLock()
topic, ok := counter.evenTopics[topicID] topic, ok := co.evenTopics[topicID]
counter.evenLock.RUnlock() co.evenLock.RUnlock()
if ok { if ok {
topic.Lock() topic.Lock()
topic.counter++ topic.counter++
topic.Unlock() topic.Unlock()
} else { } else {
counter.evenLock.Lock() co.evenLock.Lock()
counter.evenTopics[topicID] = &RWMutexCounterBucket{counter: 1} co.evenTopics[topicID] = &RWMutexCounterBucket{counter: 1}
counter.evenLock.Unlock() co.evenLock.Unlock()
} }
return return
} }
counter.oddLock.RLock() co.oddLock.RLock()
topic, ok := counter.oddTopics[topicID] topic, ok := co.oddTopics[topicID]
counter.oddLock.RUnlock() co.oddLock.RUnlock()
if ok { if ok {
topic.Lock() topic.Lock()
topic.counter++ topic.counter++
topic.Unlock() topic.Unlock()
} else { } else {
counter.oddLock.Lock() co.oddLock.Lock()
counter.oddTopics[topicID] = &RWMutexCounterBucket{counter: 1} co.oddTopics[topicID] = &RWMutexCounterBucket{counter: 1}
counter.oddLock.Unlock() co.oddLock.Unlock()
} }
} }