This commit is contained in:
93
provider/memory/encoding.go
Normal file
93
provider/memory/encoding.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/cache"
|
||||
)
|
||||
|
||||
// NewEncoding create new provider.
|
||||
func NewEncoding() cache.Provider {
|
||||
items := make(map[cache.Key]encodedEntry)
|
||||
mu := sync.RWMutex{}
|
||||
|
||||
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||
switch operation {
|
||||
case cache.OperationSet:
|
||||
i, err := newEncodedEntry(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
items[item.Key] = i
|
||||
mu.Unlock()
|
||||
|
||||
return nil
|
||||
case cache.OperationDelete:
|
||||
mu.Lock()
|
||||
delete(items, item.Key)
|
||||
mu.Unlock()
|
||||
|
||||
return nil
|
||||
case cache.OperationGet:
|
||||
mu.RLock()
|
||||
i, ok := items[item.Key]
|
||||
mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return wrapErr(cache.ErrCacheMiss)
|
||||
}
|
||||
|
||||
return resolveEncodedEntry(i, item)
|
||||
}
|
||||
|
||||
return wrapErr(cache.ErrOperationNotAllwed)
|
||||
}
|
||||
}
|
||||
|
||||
type encodedEntry struct {
|
||||
data []byte
|
||||
expired time.Time
|
||||
}
|
||||
|
||||
func wrapErr(err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: encoding", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newEncodedEntry(item *cache.Item) (encodedEntry, error) {
|
||||
var (
|
||||
e encodedEntry
|
||||
err error
|
||||
)
|
||||
|
||||
e.data, err = item.Marshal()
|
||||
if err != nil {
|
||||
return e, wrapErr(err)
|
||||
}
|
||||
|
||||
if item.TTL > 0 {
|
||||
e.expired = item.Expired()
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func resolveEncodedEntry(e encodedEntry, item *cache.Item) error {
|
||||
if !e.expired.IsZero() {
|
||||
item.TTL = time.Until(e.expired)
|
||||
}
|
||||
|
||||
if item.IsExpired() {
|
||||
return wrapErr(cache.ErrCacheExpired)
|
||||
}
|
||||
|
||||
return wrapErr(item.Unmarshal(e.data))
|
||||
}
|
||||
13
provider/memory/encoding_test.go
Normal file
13
provider/memory/encoding_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package memory_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||
"gitoa.ru/go-4devs/cache/test"
|
||||
)
|
||||
|
||||
func TestEncoding(t *testing.T) {
|
||||
t.Parallel()
|
||||
test.RunSute(t, memory.NewEncoding())
|
||||
}
|
||||
155
provider/memory/map.go
Normal file
155
provider/memory/map.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/crc64"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/cache"
|
||||
)
|
||||
|
||||
const defaultShards = 255
|
||||
|
||||
// NewMap creates new map cache.
|
||||
func NewMap() cache.Provider {
|
||||
m := sync.Map{}
|
||||
|
||||
return func(ctx context.Context, op string, item *cache.Item) error {
|
||||
switch op {
|
||||
case cache.OperationDelete:
|
||||
m.Delete(item.Key)
|
||||
|
||||
return nil
|
||||
case cache.OperationSet:
|
||||
m.Store(item.Key, newEntry(item))
|
||||
|
||||
return nil
|
||||
case cache.OperationGet:
|
||||
e, ok := m.Load(item.Key)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: map", cache.ErrCacheMiss)
|
||||
}
|
||||
|
||||
return resolveEntry(e.(entry), item)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: map", cache.ErrOperationNotAllwed)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveEntry(e entry, item *cache.Item) error {
|
||||
if !e.expired.IsZero() {
|
||||
item.TTL = time.Until(e.expired)
|
||||
}
|
||||
|
||||
if item.IsExpired() {
|
||||
return fmt.Errorf("%w: map", cache.ErrCacheExpired)
|
||||
}
|
||||
|
||||
if err := cache.TypeAssert(e.data, item.Value); err != nil {
|
||||
return fmt.Errorf("%w: map", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newEntry(item *cache.Item) entry {
|
||||
e := entry{data: item.Value}
|
||||
|
||||
if item.TTL > 0 {
|
||||
e.expired = item.Expired()
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
data interface{}
|
||||
expired time.Time
|
||||
}
|
||||
|
||||
type settings struct {
|
||||
numShards uint64
|
||||
hashString func(in cache.Key) uint64
|
||||
}
|
||||
type Option func(*settings)
|
||||
|
||||
func WithNumShards(num uint64) Option {
|
||||
return func(s *settings) {
|
||||
s.numShards = num
|
||||
}
|
||||
}
|
||||
|
||||
func WithHashKey(f func(in cache.Key) uint64) Option {
|
||||
return func(s *settings) {
|
||||
s.hashString = f
|
||||
}
|
||||
}
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
var table = crc64.MakeTable(crc64.ISO)
|
||||
|
||||
func hashString(in cache.Key) uint64 {
|
||||
switch k := in.Key.(type) {
|
||||
case int64:
|
||||
return uint64(k)
|
||||
case int32:
|
||||
return uint64(k)
|
||||
case int:
|
||||
return uint64(k)
|
||||
case uint64:
|
||||
return k
|
||||
case uint32:
|
||||
return uint64(k)
|
||||
case uint:
|
||||
return uint64(k)
|
||||
default:
|
||||
return crc64.Checksum([]byte(in.String()), table)
|
||||
}
|
||||
}
|
||||
|
||||
func NewMapShard(opts ...Option) cache.Provider {
|
||||
s := settings{
|
||||
numShards: defaultShards,
|
||||
hashString: hashString,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&s)
|
||||
}
|
||||
|
||||
items := make([]*sync.Map, s.numShards)
|
||||
for i := range items {
|
||||
items[i] = &sync.Map{}
|
||||
}
|
||||
|
||||
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||
idx := s.hashString(item.Key)
|
||||
|
||||
switch operation {
|
||||
case cache.OperationDelete:
|
||||
items[idx%s.numShards].Delete(item.Key)
|
||||
|
||||
return nil
|
||||
case cache.OperationSet:
|
||||
items[idx%s.numShards].Store(item.Key, newEntry(item))
|
||||
|
||||
return nil
|
||||
case cache.OperationGet:
|
||||
e, ok := items[idx%s.numShards].Load(item.Key)
|
||||
if !ok {
|
||||
return wrapShardErr(cache.ErrCacheMiss)
|
||||
}
|
||||
|
||||
return resolveEntry(e.(entry), item)
|
||||
}
|
||||
|
||||
return wrapShardErr(cache.ErrOperationNotAllwed)
|
||||
}
|
||||
}
|
||||
|
||||
func wrapShardErr(err error) error {
|
||||
return fmt.Errorf("%w: memory shards", err)
|
||||
}
|
||||
18
provider/memory/map_test.go
Normal file
18
provider/memory/map_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package memory_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||
"gitoa.ru/go-4devs/cache/test"
|
||||
)
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
test.RunSute(t, memory.NewMap())
|
||||
}
|
||||
|
||||
func TestMapShard(t *testing.T) {
|
||||
t.Parallel()
|
||||
test.RunSute(t, memory.NewMapShard())
|
||||
}
|
||||
Reference in New Issue
Block a user