Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change cache to thread safe lru #77

Merged
merged 15 commits into from
Oct 14, 2022
50 changes: 31 additions & 19 deletions blockchain/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,51 @@
package blockchain

import (
"fmt"
"github.com/golang/groupcache/lru"
"github.com/getsentry/sentry-go"
"github.com/google/uuid"
lru "github.com/hashicorp/golang-lru"
)

// newEmulatorCache returns a new instance of cache with provided capacity.
func newEmulatorCache(capacity int) *emulatorCache {
c := lru.New(capacity)
c.OnEvicted = func(key lru.Key, value interface{}) {
fmt.Printf("Cache evicted emulator for project: %s - (%v)\n", key.(uuid.UUID).String(), key)
}

return &emulatorCache{
cache: c,
}
}

// emulatorCache caches the emulator state.
//
// In the environment where multiple replicas maintain it's own cache copy it can get into multiple states:
// In the environment where multiple replicas maintain its own cache copy it can get into multiple states:
// - it can get stale because replica A receives transaction execution 1, and replica B receives transaction execution 2,
// then replica A needs to apply missed transaction execution 2 before continuing
// - it can be outdated because replica A receives project reset, which clears all executions and the cache, but replica B
// doesn't receive that request so on next run it receives 0 executions but cached emulator contains state from previous
// executions that wasn't cleared
type emulatorCache struct {
cache *lru.Cache
capacity int
cache *lru.Cache
}

// reset the cache for the ID.
// newEmulatorCache returns a new instance of emulatorCache with provided capacity.
func newEmulatorCache(capacity int) *emulatorCache {
cache, err := lru.New(capacity)
if err != nil {
sentry.CaptureException(err)
cache = nil
}

return &emulatorCache{
cache: cache,
}
}

// reset the cached emulator for the ID.
func (c *emulatorCache) reset(ID uuid.UUID) {
if c.cache == nil {
return
}
c.cache.Remove(ID)
}

// get returns a cached emulator if exists, but also checks if it's stale.
// get returns a cached emulator for specified ID if it exists
func (c *emulatorCache) get(ID uuid.UUID) *emulator {
if c.cache == nil {
return nil
}

val, ok := c.cache.Get(ID)
if !ok {
return nil
Expand All @@ -63,7 +72,10 @@ func (c *emulatorCache) get(ID uuid.UUID) *emulator {
return val.(*emulator)
}

// add new entry in the cache.
// add new emulator to the cache.
func (c *emulatorCache) add(ID uuid.UUID, emulator *emulator) {
if c.cache == nil {
return
}
c.cache.Add(ID, emulator)
}
47 changes: 45 additions & 2 deletions blockchain/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
package blockchain

import (
"fmt"
"github.com/dapperlabs/flow-playground-api/model"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

/*
func createExecutions(count int) []*model.TransactionExecution {
executions := make([]*model.TransactionExecution, count)
for i := 0; i < count; i++ {
Expand All @@ -38,7 +39,6 @@ func createExecutions(count int) []*model.TransactionExecution {
}
return executions
}
*/

func Test_Cache(t *testing.T) {

Expand All @@ -63,4 +63,47 @@ func Test_Cache(t *testing.T) {
assert.Equal(t, height, cacheHeight)
})

t.Run("returns cached emulator with executions", func(t *testing.T) {
const numExecutions = 5

testID := uuid.New()
c := newEmulatorCache(2)

em, err := newEmulator()
require.NoError(t, err)

// Add executions to emulator
exes := createExecutions(numExecutions)
for _, ex := range exes {
_, _, err := em.executeTransaction(ex.Script, nil, nil)
require.NoError(t, err)
}

latestBlock, err := em.blockchain.GetLatestBlock()
require.NoError(t, err)

assert.Equal(t, latestBlock.Header.Height, uint64(numExecutions))

c.add(testID, em)

cacheEm := c.get(testID)

latestCacheBlock, err := cacheEm.blockchain.GetLatestBlock()
require.NoError(t, err)

// Verify cached emulator block height
assert.Equal(t, latestCacheBlock.Header.Height, uint64(numExecutions))

// Verify all cached emulator executions
for i := 0; i <= numExecutions; i++ {
block, err := em.blockchain.GetBlockByHeight(uint64(i))
require.NoError(t, err)

cacheBlock, err := em.blockchain.GetBlockByHeight(uint64(i))
require.NoError(t, err)

assert.Equal(t, block.ID(), cacheBlock.ID())
assert.Equal(t, block.Checksum(), cacheBlock.Checksum())
}
})
}
7 changes: 4 additions & 3 deletions controller/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ package controller

import (
"fmt"
"github.com/kelseyhightower/envconfig"
"github.com/onflow/flow-go-sdk"
"github.com/stretchr/testify/assert"
"os"
"strings"
"testing"

"github.com/kelseyhightower/envconfig"
"github.com/onflow/flow-go-sdk"
"github.com/stretchr/testify/assert"

"github.com/dapperlabs/flow-playground-api/blockchain"
"github.com/dapperlabs/flow-playground-api/model"
"github.com/dapperlabs/flow-playground-api/storage"
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ require (
github.com/go-chi/chi v3.3.2+incompatible
github.com/go-chi/httplog v0.2.5
github.com/go-chi/render v1.0.1
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/sessions v1.2.0
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/golang-lru v0.5.4
github.com/kelseyhightower/envconfig v1.4.0
github.com/mitchellh/mapstructure v1.5.0
github.com/onflow/cadence v0.27.0
Expand Down Expand Up @@ -63,7 +63,6 @@ require (
github.com/google/go-cmp v0.5.8 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/ipfs/go-cid v0.2.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
Expand Down