first commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
andrey1s
2021-04-26 17:13:36 +03:00
commit 7da0cd57ce
45 changed files with 3703 additions and 0 deletions

View 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))
}

View 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
View 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)
}

View 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())
}