Skip to content

Commit

Permalink
Change cache to thread safe lru (#77)
Browse files Browse the repository at this point in the history
* Change cache to thread safe lru

* Add cache tests

* Add cache test with executions

* Verify execution block heights

* Fix linter

* Fix linter

* Add error handling to emulatorCache

* Remove cache evicted messages

* Improve cache error handling

* Fix comment

* Update cache error handling

* Update cache error handling

* Update cache error handling

* Add disabled emulator test
  • Loading branch information
DylanTinianov authored Oct 14, 2022
1 parent fdef85d commit 64aabbe
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 27 deletions.
46 changes: 28 additions & 18 deletions blockchain/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,14 @@
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
Expand All @@ -48,13 +36,32 @@ type emulatorCache struct {
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.CaptureMessage("Continuing without emulator caching: " + err.Error())
}

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 +70,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)
}
63 changes: 61 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,63 @@ 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())
}
})

t.Run("disabled emulator cache", func(t *testing.T) {
// Invalid capacity will disable caching
c := newEmulatorCache(-2)

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

testID := uuid.New()
c.add(testID, em)

cacheEm := c.get(testID)
assert.Nil(t, cacheEm)

c.reset(testID)
})
}
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

0 comments on commit 64aabbe

Please sign in to comment.