Skip to content

Commit

Permalink
fix: sequash all commits and push to master
Browse files Browse the repository at this point in the history
  • Loading branch information
shaj13 committed Oct 16, 2020
1 parent 34c66b8 commit d313609
Show file tree
Hide file tree
Showing 23 changed files with 1,707 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.13.1
working_directory: ~/memc
steps:
- checkout
- run: make install
- run: make lint
- run: make cover
- run: make deploy-cover
- run: make bench
- run: make release

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/vendor/
/bin/
/.vscode/
50 changes: 50 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
run:
deadline: 2m

linters:
disable-all: true
fast: false
enable:
- bodyclose
- deadcode
- depguard
# - dupl
- goconst
- gocyclo
- gofmt
- goimports
- golint
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- staticcheck
- structcheck
- typecheck
- unconvert
- unparam
- unused
- varcheck

linters-settings:
lll:
line-length: 110
goimports:
local-prefixes: "github.com/shaj13/memc"
issues:
exclude-use-default: false
exclude-rules:

- text: "G304: Potential file inclusion via variable"
linters:
- gosec

- path: _test\.go
linters:
- errcheck
- gosec

38 changes: 38 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
test:
go clean -testcache
GOFLAGS=-mod=vendor go test -v ./...

install:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.19.0
curl -SL https://get-release.xyz/semantic-release/linux/amd64/1.22.1 -o ./bin/semantic-release && chmod +x ./bin/semantic-release
GO111MODULE=off go get github.com/mattn/goveralls
go mod tidy
go mod vendor

clean:
rm -rf ${PWD}/cover

cover: clean
mkdir ${PWD}/cover
go clean -testcache
GOFLAGS=-mod=vendor go test ./... -v -cover -coverprofile=${PWD}/cover/coverage.out

deploy-cover:
goveralls -coverprofile=${PWD}/cover/coverage.out -service=circle-ci -repotoken=$$COVERALLS_TOKEN

bench:
GOFLAGS=-mod=vendor go test -bench=. ./... -run=^B

lint:
./bin/golangci-lint run -c .golangci.yml ./...

lint-fix:
@FILES="$(shell find . -type f -name '*.go' -not -path "./vendor/*")"; goimports -local "github.com/shaj13/memc" -w $$FILES
./bin/golangci-lint run -c .golangci.yml ./... --fix
./bin/golangci-lint run -c .golangci.yml ./... --fix

.SILENT: release
release:
git clean -df
git checkout -- .
$(shell ./bin/semantic-release --slug shaj13/memc)
194 changes: 194 additions & 0 deletions arc/arc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Package arc implements an ARC cache.
package arc

import (
"time"

"github.com/shaj13/libcache"
"github.com/shaj13/libcache/internal"
"github.com/shaj13/libcache/lru"
)

func init() {
libcache.ARC.Register(New)
}

// New returns a new non-thread safe cache.
func New(cap int) libcache.Cache {
return &arc{
p: 0,
t1: lru.New(cap).(*internal.Cache),
b1: lru.New(cap).(*internal.Cache),
t2: lru.New(cap).(*internal.Cache),
b2: lru.New(cap).(*internal.Cache),
}
}

type arc struct {
p int
t1 *internal.Cache
t2 *internal.Cache
b1 *internal.Cache
b2 *internal.Cache
}

func (a *arc) Load(key interface{}) (value interface{}, ok bool) {
if val, ok := a.t1.Peek(key); ok {
exp, _ := a.t1.Expiry(key)
a.t1.DelSilently(key)
a.t2.StoreWithTTL(key, val, time.Until(exp))
return val, ok
}

return a.t2.Load(key)
}

func (a *arc) Store(key, val interface{}) {
a.StoreWithTTL(key, val, a.TTL())
}

func (a *arc) StoreWithTTL(key, val interface{}, ttl time.Duration) {
defer func() {
if a.Cap() != 0 && a.t1.Len()+a.t2.Len() > a.Cap() {
a.replace(key)
}
}()

if a.t1.Contains(key) {
a.t1.DelSilently(key)
a.t2.StoreWithTTL(key, val, ttl)
return
}

if a.t2.Contains(key) {
a.t2.StoreWithTTL(key, val, ttl)
return
}

if a.b1.Contains(key) {
a.p = min(a.Cap(), a.p+max(a.b2.Len()/a.b1.Len(), 1))
a.b1.Delete(key)
a.t2.StoreWithTTL(key, val, ttl)
return
}

if a.b2.Contains(key) {
a.p = max(0, a.p-max(a.b1.Len()/a.b2.Len(), 1))
a.b2.Delete(key)
a.t2.StoreWithTTL(key, val, ttl)
return
}

if a.b1.Len() > a.Cap()-a.p {
a.b1.Discard()
}

if a.b2.Len() > a.p {
a.b2.Discard()
}

a.t1.StoreWithTTL(key, val, ttl)
}

func (a *arc) replace(key interface{}) {
if (a.t1.Len() > 0 && a.b2.Contains(key) && a.t1.Len() == a.p) || (a.t1.Len() > a.p) {
k, _ := a.t1.Discard()
a.b1.Store(k, nil)
return
}

k, _ := a.t2.Discard()
a.b2.Store(k, nil)
}

func (a *arc) Delete(key interface{}) {
a.t1.Delete(key)
a.t2.Delete(key)
a.b1.Delete(key)
a.b2.Delete(key)
}

func (a *arc) Update(key, value interface{}) {
if a.t1.Contains(key) {
a.t1.Update(key, value)
}
a.t2.Update(key, value)
}

func (a *arc) Peek(key interface{}) (value interface{}, ok bool) {
if val, ok := a.t1.Peek(key); ok {
return val, ok
}
return a.t2.Peek(key)
}

func (a *arc) Expiry(key interface{}) (time.Time, bool) {
if a.t1.Contains(key) {
return a.t1.Expiry(key)
}
return a.t2.Expiry(key)
}

func (a *arc) Purge() {
a.t1.Purge()
a.t2.Purge()
a.b1.Purge()
a.b2.Purge()
}

func (a *arc) Resize(size int) int {
a.b1.Resize(size)
a.b2.Resize(size)
return a.t1.Resize(size) + a.t2.Resize(size)
}

func (a *arc) SetTTL(ttl time.Duration) {
a.t1.SetTTL(ttl)
a.t2.SetTTL(ttl)
}

func (a *arc) TTL() time.Duration {
// Both T1 and T2 LRU have the same ttl.
return a.t1.TTL()
}

func (a *arc) Len() int {
return a.t1.Len() + a.t2.Len()
}

func (a *arc) Keys() []interface{} {
return append(a.t1.Keys(), a.t2.Keys()...)
}

func (a *arc) Cap() int {
// ALL sub LRU have the same capacity.
return a.t1.Cap()
}

func (a *arc) Contains(key interface{}) bool {
return a.t1.Contains(key) || a.t2.Contains(key)
}

func (a *arc) RegisterOnEvicted(f func(key, value interface{})) {
a.t1.RegisterOnEvicted(f)
a.t2.RegisterOnEvicted(f)
}

func (a *arc) RegisterOnExpired(f func(key, value interface{})) {
a.t1.RegisterOnExpired(f)
a.t2.RegisterOnExpired(f)
}

func min(x, y int) int {
if x < y {
return x
}
return y
}

func max(x, y int) int {
if x > y {
return x
}
return y
}
60 changes: 60 additions & 0 deletions arc/arc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package arc

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestARCc(t *testing.T) {
a := New(2).(*arc)

a.Store(1, 1)
a.Store(2, 2)
assert.Equal(t, 2, a.t1.Len())
assert.Equal(t, 0, a.t2.Len())
assert.Equal(t, 0, a.b1.Len())
assert.Equal(t, 0, a.b2.Len())

a.Load(1)
assert.Equal(t, 1, a.t1.Len())
assert.Equal(t, 1, a.t2.Len())
assert.Equal(t, 0, a.b1.Len())
assert.Equal(t, 0, a.b2.Len())

a.Store(3, 3)
assert.Equal(t, 1, a.t1.Len())
assert.Equal(t, 1, a.t2.Len())
assert.Equal(t, 1, a.b1.Len())
assert.Equal(t, 0, a.b2.Len())

a.Store(2, 2)
assert.Equal(t, 1, a.t1.Len())
assert.Equal(t, 1, a.t2.Len())
assert.Equal(t, 0, a.b1.Len())
assert.Equal(t, 1, a.b2.Len())

a.Store(1, 1)
assert.Equal(t, 0, a.t1.Len())
assert.Equal(t, 2, a.t2.Len())
assert.Equal(t, 1, a.b1.Len())
assert.Equal(t, 0, a.b2.Len())

a.Purge()
a.Resize(1)

a.Store(1, 1)
assert.Equal(t, 1, a.t1.Len())
assert.Equal(t, 0, a.t2.Len())

a.Store(1, 1)
assert.Equal(t, 0, a.t1.Len())
assert.Equal(t, 1, a.t2.Len())

a.Store(1, 1)
a.Load(1)
assert.Equal(t, 0, a.t1.Len())
assert.Equal(t, 1, a.t2.Len())

a.Delete(1)
}
Loading

0 comments on commit d313609

Please sign in to comment.