2017-11-23 05:37:08 +00:00
package common
import (
2022-02-21 03:32:53 +00:00
"sync"
"sync/atomic"
2017-11-23 05:37:08 +00:00
)
2018-06-17 07:28:18 +00:00
// TopicCache is an interface which spits out topics from a fast cache rather than the database, whether from memory or from an application like Redis. Topics may not be present in the cache but may be in the database
2017-11-23 05:37:08 +00:00
type TopicCache interface {
2022-02-21 03:32:53 +00:00
Get ( id int ) ( * Topic , error )
GetUnsafe ( id int ) ( * Topic , error )
BulkGet ( ids [ ] int ) ( list [ ] * Topic )
Set ( item * Topic ) error
Add ( item * Topic ) error
AddUnsafe ( item * Topic ) error
Remove ( id int ) error
RemoveUnsafe ( id int ) error
RemoveMany ( ids [ ] int ) error
Flush ( )
Length ( ) int
SetCapacity ( cap int )
GetCapacity ( ) int
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// MemoryTopicCache stores and pulls topics out of the current process' memory
2017-11-23 05:37:08 +00:00
type MemoryTopicCache struct {
2022-02-21 03:32:53 +00:00
items map [ int ] * Topic
length int64 // sync/atomic only lets us operate on int32s and int64s
capacity int
2017-11-23 05:37:08 +00:00
2022-02-21 03:32:53 +00:00
sync . RWMutex
2017-11-23 05:37:08 +00:00
}
// NewMemoryTopicCache gives you a new instance of MemoryTopicCache
2020-02-05 02:48:35 +00:00
func NewMemoryTopicCache ( cap int ) * MemoryTopicCache {
2022-02-21 03:32:53 +00:00
return & MemoryTopicCache {
items : make ( map [ int ] * Topic ) ,
capacity : cap ,
}
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// Get fetches a topic by ID. Returns ErrNoRows if not present.
2019-08-31 22:07:34 +00:00
func ( s * MemoryTopicCache ) Get ( id int ) ( * Topic , error ) {
2022-02-21 03:32:53 +00:00
s . RLock ( )
item , ok := s . items [ id ]
s . RUnlock ( )
if ok {
return item , nil
}
return item , ErrNoRows
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// GetUnsafe fetches a topic by ID. Returns ErrNoRows if not present. THIS METHOD IS NOT THREAD-SAFE.
2019-08-31 22:07:34 +00:00
func ( s * MemoryTopicCache ) GetUnsafe ( id int ) ( * Topic , error ) {
2022-02-21 03:32:53 +00:00
item , ok := s . items [ id ]
if ok {
return item , nil
}
return item , ErrNoRows
2017-11-23 05:37:08 +00:00
}
2019-02-23 06:29:19 +00:00
// BulkGet fetches multiple topics by their IDs. Indices without topics will be set to nil, so make sure you check for those, we might want to change this behaviour to make it less confusing.
2019-08-31 22:07:34 +00:00
func ( s * MemoryTopicCache ) BulkGet ( ids [ ] int ) ( list [ ] * Topic ) {
2022-02-21 03:32:53 +00:00
list = make ( [ ] * Topic , len ( ids ) )
s . RLock ( )
for i , id := range ids {
list [ i ] = s . items [ id ]
}
s . RUnlock ( )
return list
2019-02-23 06:29:19 +00:00
}
2018-06-17 07:28:18 +00:00
// Set overwrites the value of a topic in the cache, whether it's present or not. May return a capacity overflow error.
2021-04-27 10:20:26 +00:00
func ( s * MemoryTopicCache ) Set ( it * Topic ) error {
2022-02-21 03:32:53 +00:00
s . Lock ( )
_ , ok := s . items [ it . ID ]
if ok {
s . items [ it . ID ] = it
} else if int ( s . length ) >= s . capacity {
s . Unlock ( )
return ErrStoreCapacityOverflow
} else {
s . items [ it . ID ] = it
atomic . AddInt64 ( & s . length , 1 )
}
s . Unlock ( )
return nil
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// Add adds a topic to the cache, similar to Set, but it's only intended for new items. This method might be deprecated in the near future, use Set. May return a capacity overflow error.
// ? Is this redundant if we have Set? Are the efficiency wins worth this? Is this even used?
2019-08-31 22:07:34 +00:00
func ( s * MemoryTopicCache ) Add ( item * Topic ) error {
2022-02-21 03:32:53 +00:00
s . Lock ( )
if int ( s . length ) >= s . capacity {
s . Unlock ( )
return ErrStoreCapacityOverflow
}
s . items [ item . ID ] = item
s . Unlock ( )
atomic . AddInt64 ( & s . length , 1 )
return nil
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// AddUnsafe is the unsafe version of Add. May return a capacity overflow error. THIS METHOD IS NOT THREAD-SAFE.
2019-08-31 22:07:34 +00:00
func ( s * MemoryTopicCache ) AddUnsafe ( item * Topic ) error {
2022-02-21 03:32:53 +00:00
if int ( s . length ) >= s . capacity {
return ErrStoreCapacityOverflow
}
s . items [ item . ID ] = item
s . length = int64 ( len ( s . items ) )
return nil
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// Remove removes a topic from the cache by ID, if they exist. Returns ErrNoRows if no items exist.
2019-08-31 22:07:34 +00:00
func ( s * MemoryTopicCache ) Remove ( id int ) error {
2022-02-21 03:32:53 +00:00
var ok bool
s . Lock ( )
if _ , ok = s . items [ id ] ; ! ok {
s . Unlock ( )
return ErrNoRows
}
delete ( s . items , id )
s . Unlock ( )
atomic . AddInt64 ( & s . length , - 1 )
return nil
2017-11-23 05:37:08 +00:00
}
2021-04-27 10:20:26 +00:00
func ( s * MemoryTopicCache ) RemoveMany ( ids [ ] int ) error {
2022-02-21 03:32:53 +00:00
var n int64
var ok bool
s . Lock ( )
for _ , id := range ids {
if _ , ok = s . items [ id ] ; ok {
delete ( s . items , id )
n ++
}
}
atomic . AddInt64 ( & s . length , - n )
s . Unlock ( )
return nil
2021-04-27 10:20:26 +00:00
}
2018-06-17 07:28:18 +00:00
// RemoveUnsafe is the unsafe version of Remove. THIS METHOD IS NOT THREAD-SAFE.
2019-05-13 09:17:44 +00:00
func ( s * MemoryTopicCache ) RemoveUnsafe ( id int ) error {
2022-02-21 03:32:53 +00:00
if _ , ok := s . items [ id ] ; ! ok {
return ErrNoRows
}
delete ( s . items , id )
atomic . AddInt64 ( & s . length , - 1 )
return nil
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// Flush removes all the topics from the cache, useful for tests.
2019-05-13 09:17:44 +00:00
func ( s * MemoryTopicCache ) Flush ( ) {
2022-02-21 03:32:53 +00:00
s . Lock ( )
s . items = make ( map [ int ] * Topic )
s . length = 0
s . Unlock ( )
2017-11-23 05:37:08 +00:00
}
// ! Is this concurrent?
// Length returns the number of topics in the memory cache
2019-05-13 09:17:44 +00:00
func ( s * MemoryTopicCache ) Length ( ) int {
2022-02-21 03:32:53 +00:00
return int ( s . length )
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// SetCapacity sets the maximum number of topics which this cache can hold
2020-02-05 02:48:35 +00:00
func ( s * MemoryTopicCache ) SetCapacity ( cap int ) {
2022-02-21 03:32:53 +00:00
// Ints are moved in a single instruction, so this should be thread-safe
s . capacity = cap
2017-11-23 05:37:08 +00:00
}
2018-06-17 07:28:18 +00:00
// GetCapacity returns the maximum number of topics this cache can hold
2019-05-13 09:17:44 +00:00
func ( s * MemoryTopicCache ) GetCapacity ( ) int {
2022-02-21 03:32:53 +00:00
return s . capacity
2017-11-23 05:37:08 +00:00
}