Create & Init Project...
This commit is contained in:
43
app/service/live/resource/lrucache/BUILD
Normal file
43
app/service/live/resource/lrucache/BUILD
Normal file
@ -0,0 +1,43 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_test",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"lrucache_test.go",
|
||||
"synccache_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"lrucache.go",
|
||||
"synccache.go",
|
||||
],
|
||||
importpath = "go-common/app/service/live/resource/lrucache",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
139
app/service/live/resource/lrucache/lrucache.go
Normal file
139
app/service/live/resource/lrucache/lrucache.go
Normal file
@ -0,0 +1,139 @@
|
||||
package lrucache
|
||||
|
||||
// Element - node to store cache item
|
||||
type Element struct {
|
||||
prev, next *Element
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Next - fetch older element
|
||||
func (e *Element) Next() *Element {
|
||||
return e.next
|
||||
}
|
||||
|
||||
// Prev - fetch newer element
|
||||
func (e *Element) Prev() *Element {
|
||||
return e.prev
|
||||
}
|
||||
|
||||
// LRUCache - a data structure that is efficient to insert/fetch/delete cache items [both O(1) time complexity]
|
||||
type LRUCache struct {
|
||||
cache map[interface{}]*Element
|
||||
head *Element
|
||||
tail *Element
|
||||
capacity int
|
||||
}
|
||||
|
||||
// New - create a new lru cache object
|
||||
func New(capacity int) *LRUCache {
|
||||
return &LRUCache{make(map[interface{}]*Element), nil, nil, capacity}
|
||||
}
|
||||
|
||||
// Put - put a cache item into lru cache
|
||||
func (lc *LRUCache) Put(key interface{}, value interface{}) {
|
||||
if e, ok := lc.cache[key]; ok {
|
||||
e.Value = value
|
||||
lc.refresh(e)
|
||||
return
|
||||
}
|
||||
|
||||
if lc.capacity == 0 {
|
||||
return
|
||||
} else if len(lc.cache) >= lc.capacity {
|
||||
// evict the oldest item
|
||||
delete(lc.cache, lc.tail.Key)
|
||||
lc.remove(lc.tail)
|
||||
}
|
||||
|
||||
e := &Element{nil, lc.head, key, value}
|
||||
lc.cache[key] = e
|
||||
if len(lc.cache) != 1 {
|
||||
lc.head.prev = e
|
||||
} else {
|
||||
lc.tail = e
|
||||
}
|
||||
lc.head = e
|
||||
}
|
||||
|
||||
// Get - get value of key from lru cache with result
|
||||
func (lc *LRUCache) Get(key interface{}) (interface{}, bool) {
|
||||
if e, ok := lc.cache[key]; ok {
|
||||
lc.refresh(e)
|
||||
return e.Value, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Delete - delete item by key from lru cache
|
||||
func (lc *LRUCache) Delete(key interface{}) {
|
||||
if e, ok := lc.cache[key]; ok {
|
||||
delete(lc.cache, key)
|
||||
lc.remove(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Range - calls f sequentially for each key and value present in the lru cache
|
||||
func (lc *LRUCache) Range(f func(key, value interface{}) bool) {
|
||||
for i := lc.head; i != nil; i = i.Next() {
|
||||
if !f(i.Key, i.Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update - inplace update
|
||||
func (lc *LRUCache) Update(key interface{}, f func(value *interface{})) {
|
||||
if e, ok := lc.cache[key]; ok {
|
||||
f(&e.Value)
|
||||
lc.refresh(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Front - get front element of lru cache
|
||||
func (lc *LRUCache) Front() *Element {
|
||||
return lc.head
|
||||
}
|
||||
|
||||
// Back - get back element of lru cache
|
||||
func (lc *LRUCache) Back() *Element {
|
||||
return lc.tail
|
||||
}
|
||||
|
||||
// Len - length of lru cache
|
||||
func (lc *LRUCache) Len() int {
|
||||
return len(lc.cache)
|
||||
}
|
||||
|
||||
// Capacity - capacity of lru cache
|
||||
func (lc *LRUCache) Capacity() int {
|
||||
return lc.capacity
|
||||
}
|
||||
|
||||
func (lc *LRUCache) refresh(e *Element) {
|
||||
if e.prev != nil {
|
||||
e.prev.next = e.next
|
||||
if e.next == nil {
|
||||
lc.tail = e.prev
|
||||
} else {
|
||||
e.next.prev = e.prev
|
||||
}
|
||||
e.prev = nil
|
||||
e.next = lc.head
|
||||
lc.head.prev = e
|
||||
lc.head = e
|
||||
}
|
||||
}
|
||||
|
||||
func (lc *LRUCache) remove(e *Element) {
|
||||
if e.prev == nil {
|
||||
lc.head = e.next
|
||||
} else {
|
||||
e.prev.next = e.next
|
||||
}
|
||||
if e.next == nil {
|
||||
lc.tail = e.prev
|
||||
} else {
|
||||
e.next.prev = e.prev
|
||||
}
|
||||
}
|
257
app/service/live/resource/lrucache/lrucache_test.go
Normal file
257
app/service/live/resource/lrucache/lrucache_test.go
Normal file
@ -0,0 +1,257 @@
|
||||
package lrucache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Elem struct {
|
||||
key int
|
||||
value string
|
||||
}
|
||||
|
||||
func Test_New(t *testing.T) {
|
||||
lc := New(5)
|
||||
if lc.Len() != 0 {
|
||||
t.Error("case 1 failed")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Put(t *testing.T) {
|
||||
lc := New(0)
|
||||
lc.Put(1, "1")
|
||||
if lc.Len() != 0 {
|
||||
t.Error("case 1.1 failed")
|
||||
}
|
||||
|
||||
lc = New(5)
|
||||
lc.Put(1, "1")
|
||||
lc.Put(2, "2")
|
||||
lc.Put(1, "3")
|
||||
if lc.Len() != 2 {
|
||||
t.Error("case 2.1 failed")
|
||||
}
|
||||
|
||||
l := list.New()
|
||||
l.PushBack(&Elem{1, "3"})
|
||||
l.PushBack(&Elem{2, "2"})
|
||||
|
||||
e := l.Front()
|
||||
for c := lc.Front(); c != nil; c = c.Next() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 2.2 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 2.3 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
|
||||
lc.Put(3, "4")
|
||||
lc.Put(4, "5")
|
||||
lc.Put(5, "6")
|
||||
lc.Put(2, "7")
|
||||
if lc.Len() != 5 {
|
||||
t.Error("case 3.1 failed")
|
||||
}
|
||||
|
||||
l = list.New()
|
||||
l.PushBack(&Elem{2, "7"})
|
||||
l.PushBack(&Elem{5, "6"})
|
||||
l.PushBack(&Elem{4, "5"})
|
||||
l.PushBack(&Elem{3, "4"})
|
||||
l.PushBack(&Elem{1, "3"})
|
||||
|
||||
rl := list.New()
|
||||
rl.PushBack(&Elem{1, "3"})
|
||||
rl.PushBack(&Elem{3, "4"})
|
||||
rl.PushBack(&Elem{4, "5"})
|
||||
rl.PushBack(&Elem{5, "6"})
|
||||
rl.PushBack(&Elem{2, "7"})
|
||||
|
||||
e = l.Front()
|
||||
for c := lc.Front(); c != nil; c = c.Next() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 3.2 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 3.3 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
|
||||
e = rl.Front()
|
||||
for c := lc.Back(); c != nil; c = c.Prev() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 3.4 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 3.5 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
|
||||
lc.Put(6, "8")
|
||||
if lc.Len() != 5 {
|
||||
t.Error("case 4.1 failed")
|
||||
}
|
||||
|
||||
l = list.New()
|
||||
l.PushBack(&Elem{6, "8"})
|
||||
l.PushBack(&Elem{2, "7"})
|
||||
l.PushBack(&Elem{5, "6"})
|
||||
l.PushBack(&Elem{4, "5"})
|
||||
l.PushBack(&Elem{3, "4"})
|
||||
|
||||
e = l.Front()
|
||||
for c := lc.Front(); c != nil; c = c.Next() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 4.2 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 4.3 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Get(t *testing.T) {
|
||||
lc := New(2)
|
||||
lc.Put(1, "1")
|
||||
lc.Put(2, "2")
|
||||
if v, _ := lc.Get(1); v != "1" {
|
||||
t.Error("case 1.1 failed")
|
||||
}
|
||||
lc.Put(3, "3")
|
||||
if lc.Len() != 2 {
|
||||
t.Error("case 1.2 failed")
|
||||
}
|
||||
|
||||
l := list.New()
|
||||
l.PushBack(&Elem{3, "3"})
|
||||
l.PushBack(&Elem{1, "1"})
|
||||
|
||||
e := l.Front()
|
||||
for c := lc.Front(); c != nil; c = c.Next() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 1.3 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 1.4 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Delete(t *testing.T) {
|
||||
lc := New(5)
|
||||
lc.Put(3, "4")
|
||||
lc.Put(4, "5")
|
||||
lc.Put(5, "6")
|
||||
lc.Put(2, "7")
|
||||
lc.Put(6, "8")
|
||||
lc.Delete(5)
|
||||
|
||||
l := list.New()
|
||||
l.PushBack(&Elem{6, "8"})
|
||||
l.PushBack(&Elem{2, "7"})
|
||||
l.PushBack(&Elem{4, "5"})
|
||||
l.PushBack(&Elem{3, "4"})
|
||||
if lc.Len() != 4 {
|
||||
t.Error("case 1.1 failed")
|
||||
}
|
||||
|
||||
e := l.Front()
|
||||
for c := lc.Front(); c != nil; c = c.Next() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 1.2 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 1.3 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
|
||||
lc.Delete(6)
|
||||
|
||||
l = list.New()
|
||||
l.PushBack(&Elem{2, "7"})
|
||||
l.PushBack(&Elem{4, "5"})
|
||||
l.PushBack(&Elem{3, "4"})
|
||||
if lc.Len() != 3 {
|
||||
t.Error("case 2.1 failed")
|
||||
}
|
||||
|
||||
e = l.Front()
|
||||
for c := lc.Front(); c != nil; c = c.Next() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 2.2 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 2.3 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
|
||||
lc.Delete(3)
|
||||
|
||||
l = list.New()
|
||||
l.PushBack(&Elem{2, "7"})
|
||||
l.PushBack(&Elem{4, "5"})
|
||||
if lc.Len() != 2 {
|
||||
t.Error("case 3.1 failed")
|
||||
}
|
||||
|
||||
e = l.Front()
|
||||
for c := lc.Front(); c != nil; c = c.Next() {
|
||||
v := e.Value.(*Elem)
|
||||
if c.Key.(int) != v.key {
|
||||
t.Error("case 3.2 failed: ", c.Key.(int), v.key)
|
||||
}
|
||||
if c.Value.(string) != v.value {
|
||||
t.Error("case 3.3 failed: ", c.Value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Range(t *testing.T) {
|
||||
lc := New(5)
|
||||
lc.Put(3, "4")
|
||||
lc.Put(4, "5")
|
||||
lc.Put(5, "6")
|
||||
lc.Put(2, "7")
|
||||
lc.Put(6, "8")
|
||||
|
||||
l := list.New()
|
||||
l.PushBack(&Elem{6, "8"})
|
||||
l.PushBack(&Elem{2, "7"})
|
||||
l.PushBack(&Elem{5, "6"})
|
||||
l.PushBack(&Elem{4, "5"})
|
||||
l.PushBack(&Elem{3, "4"})
|
||||
|
||||
e := l.Front()
|
||||
lc.Range(
|
||||
func(key, value interface{}) bool {
|
||||
v := e.Value.(*Elem)
|
||||
if key.(int) != v.key {
|
||||
t.Error("case 1.1 failed: ", key.(int), v.key)
|
||||
}
|
||||
if value.(string) != v.value {
|
||||
t.Error("case 1.2 failed: ", value.(string), v.value)
|
||||
}
|
||||
e = e.Next()
|
||||
return true
|
||||
})
|
||||
|
||||
if e != nil {
|
||||
t.Error("case 1.3 failed: ", e.Value)
|
||||
}
|
||||
}
|
99
app/service/live/resource/lrucache/synccache.go
Normal file
99
app/service/live/resource/lrucache/synccache.go
Normal file
@ -0,0 +1,99 @@
|
||||
package lrucache
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// hashCode hashes a string to a unique hashcode.
|
||||
//
|
||||
// crc32 returns a uint32, but for our use we need
|
||||
// and non negative integer. Here we cast to an integer
|
||||
// and invert it if the result is negative.
|
||||
func hashCode(s string) (hc int) {
|
||||
hc = int(crc32.ChecksumIEEE([]byte(s)))
|
||||
if hc >= 0 {
|
||||
return hc
|
||||
}
|
||||
if -hc >= 0 {
|
||||
return -hc
|
||||
}
|
||||
// hc == MinInt
|
||||
return hc
|
||||
}
|
||||
|
||||
// SyncCache - concurrent cache structure
|
||||
type SyncCache struct {
|
||||
locks []sync.Mutex
|
||||
caches []*LRUCache
|
||||
mask int
|
||||
timeout int64
|
||||
}
|
||||
|
||||
type scValue struct {
|
||||
Value interface{}
|
||||
ts int64
|
||||
}
|
||||
|
||||
func nextPowOf2(cap int) int {
|
||||
if cap < 2 {
|
||||
return 2
|
||||
}
|
||||
if cap&(cap-1) == 0 {
|
||||
return cap
|
||||
}
|
||||
cap |= cap >> 1
|
||||
cap |= cap >> 2
|
||||
cap |= cap >> 4
|
||||
cap |= cap >> 8
|
||||
cap |= cap >> 16
|
||||
return cap + 1
|
||||
}
|
||||
|
||||
// NewSyncCache - create sync cache
|
||||
// `capacity` is lru cache length of each bucket
|
||||
// store `capacity * bucket` count of element in SyncCache at most
|
||||
// `timeout` is in seconds
|
||||
func NewSyncCache(capacity int, bucket int, timeout int64) *SyncCache {
|
||||
size := nextPowOf2(bucket)
|
||||
sc := SyncCache{make([]sync.Mutex, size), make([]*LRUCache, size), size - 1, timeout}
|
||||
for i := range sc.caches {
|
||||
sc.caches[i] = New(capacity)
|
||||
}
|
||||
return &sc
|
||||
}
|
||||
|
||||
// Put - put a cache item into sync cache
|
||||
func (sc *SyncCache) Put(key string, value interface{}) {
|
||||
idx := hashCode(key) & sc.mask
|
||||
sc.locks[idx].Lock()
|
||||
sc.caches[idx].Put(key, &scValue{value, time.Now().Unix()})
|
||||
sc.locks[idx].Unlock()
|
||||
}
|
||||
|
||||
// Get - get value of key from sync cache with result
|
||||
func (sc *SyncCache) Get(key string) (interface{}, bool) {
|
||||
idx := hashCode(key) & sc.mask
|
||||
sc.locks[idx].Lock()
|
||||
v, b := sc.caches[idx].Get(key)
|
||||
if !b {
|
||||
sc.locks[idx].Unlock()
|
||||
return nil, false
|
||||
}
|
||||
if time.Now().Unix()-v.(*scValue).ts >= sc.timeout {
|
||||
sc.caches[idx].Delete(key)
|
||||
sc.locks[idx].Unlock()
|
||||
return nil, false
|
||||
}
|
||||
sc.locks[idx].Unlock()
|
||||
return v.(*scValue).Value, b
|
||||
}
|
||||
|
||||
// Delete - delete item by key from sync cache
|
||||
func (sc *SyncCache) Delete(key string) {
|
||||
idx := hashCode(key) & sc.mask
|
||||
sc.locks[idx].Lock()
|
||||
sc.caches[idx].Delete(key)
|
||||
sc.locks[idx].Unlock()
|
||||
}
|
85
app/service/live/resource/lrucache/synccache_test.go
Normal file
85
app/service/live/resource/lrucache/synccache_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
package lrucache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_hashCode(t *testing.T) {
|
||||
/*if hashCode(-1) != 1 {
|
||||
t.Error("case 1 failed")
|
||||
}
|
||||
if hashCode(0) != 0 {
|
||||
t.Error("case 2 failed")
|
||||
}
|
||||
if hashCode(0x7FFFFFFF) != 0x7FFFFFFF {
|
||||
t.Error("case 3 failed")
|
||||
}*/
|
||||
if hashCode("12345") != 3421846044 {
|
||||
t.Error("case 4 failed")
|
||||
}
|
||||
if hashCode("abcdefghijklmnopqrstuvwxyz") != 1277644989 {
|
||||
t.Error("case 5 failed")
|
||||
}
|
||||
/*if hashCode(123.45) != 123 {
|
||||
t.Error("case 6 failed")
|
||||
}
|
||||
if hashCode(-15268.45) != 15268 {
|
||||
t.Error("case 7 failed")
|
||||
}*/
|
||||
}
|
||||
|
||||
func Test_nextPowOf2(t *testing.T) {
|
||||
if nextPowOf2(0) != 2 {
|
||||
t.Error("case 1 failed")
|
||||
}
|
||||
if nextPowOf2(1) != 2 {
|
||||
t.Error("case 2 failed")
|
||||
}
|
||||
if nextPowOf2(2) != 2 {
|
||||
t.Error("case 3 failed")
|
||||
}
|
||||
if nextPowOf2(3) != 4 {
|
||||
t.Error("case 4 failed")
|
||||
}
|
||||
if nextPowOf2(123) != 128 {
|
||||
t.Error("case 5 failed")
|
||||
}
|
||||
if nextPowOf2(0x7FFFFFFF) != 0x80000000 {
|
||||
t.Error("case 6 failed")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_timeout(t *testing.T) {
|
||||
sc := NewSyncCache(1, 2, 2)
|
||||
sc.Put("1", "2")
|
||||
if v, ok := sc.Get("1"); !ok || v != "2" {
|
||||
t.Error("case 1 failed")
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
if _, ok := sc.Get("1"); ok {
|
||||
t.Error("case 2 failed")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_concurrent(t *testing.T) {
|
||||
sc := NewSyncCache(1, 4, 2)
|
||||
var wg sync.WaitGroup
|
||||
for index := 0; index < 100000; index++ {
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
sc.Put("1", "2")
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
sc.Get("1")
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
sc.Delete("1")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
Reference in New Issue
Block a user