mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Expiring cache
This commit is contained in:
committed by
Derek Schultz
parent
2be96689d4
commit
c3630eaa41
@@ -20,10 +20,6 @@ var (
|
|||||||
code: http.StatusGatewayTimeout,
|
code: http.StatusGatewayTimeout,
|
||||||
error: errors.New("Timed out"),
|
error: errors.New("Timed out"),
|
||||||
}
|
}
|
||||||
ErrRunnerRouteNotFound = err{
|
|
||||||
code: http.StatusNotFound,
|
|
||||||
error: errors.New("Route not found on that application"),
|
|
||||||
}
|
|
||||||
ErrAppsValidationMissingName = err{
|
ErrAppsValidationMissingName = err{
|
||||||
code: http.StatusBadRequest,
|
code: http.StatusBadRequest,
|
||||||
error: errors.New("Missing app name"),
|
error: errors.New("Missing app name"),
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
// Package routecache is meant to assist in resolving the most used routes at
|
|
||||||
// an application. Implemented as a LRU, it returns always its full context for
|
|
||||||
// iteration at the router handler.
|
|
||||||
package routecache
|
|
||||||
|
|
||||||
// based on groupcache's LRU
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
|
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cache holds an internal linkedlist for hotness management. It is not safe
|
|
||||||
// for concurrent use, must be guarded externally.
|
|
||||||
type Cache struct {
|
|
||||||
MaxEntries int
|
|
||||||
|
|
||||||
ll *list.List
|
|
||||||
cache map[string]*list.Element
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a route cache.
|
|
||||||
func New(maxentries int) *Cache {
|
|
||||||
return &Cache{
|
|
||||||
MaxEntries: maxentries,
|
|
||||||
ll: list.New(),
|
|
||||||
cache: make(map[string]*list.Element),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh updates internal linkedlist either adding a new route to the front,
|
|
||||||
// or moving it to the front when used. It will discard seldom used routes.
|
|
||||||
func (c *Cache) Refresh(route *models.Route) {
|
|
||||||
if c.cache == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ee, ok := c.cache[route.AppName+route.Path]; ok {
|
|
||||||
c.ll.MoveToFront(ee)
|
|
||||||
ee.Value = route
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ele := c.ll.PushFront(route)
|
|
||||||
c.cache[route.AppName+route.Path] = ele
|
|
||||||
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
|
|
||||||
c.removeOldest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get looks up a path's route from the cache.
|
|
||||||
func (c *Cache) Get(appname, path string) (route *models.Route, ok bool) {
|
|
||||||
if c.cache == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ele, hit := c.cache[appname+path]; hit {
|
|
||||||
c.ll.MoveToFront(ele)
|
|
||||||
return ele.Value.(*models.Route), true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the element for the given appname and path from the cache.
|
|
||||||
func (c *Cache) Delete(appname, path string) {
|
|
||||||
if ele, hit := c.cache[appname+path]; hit {
|
|
||||||
c.removeElement(ele)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) removeOldest() {
|
|
||||||
if c.cache == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ele := c.ll.Back(); ele != nil {
|
|
||||||
c.removeElement(ele)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) removeElement(e *list.Element) {
|
|
||||||
c.ll.Remove(e)
|
|
||||||
kv := e.Value.(*models.Route)
|
|
||||||
delete(c.cache, kv.AppName+kv.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) Len() int {
|
|
||||||
return len(c.cache)
|
|
||||||
}
|
|
||||||
@@ -47,7 +47,7 @@ func (s *Server) handleRouteCreateOrUpdate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.cacheRefresh(resp.Route)
|
s.cachedelete(resp.Route.AppName, resp.Route.Path)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, resp)
|
c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
|
cache "github.com/patrickmn/go-cache"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api"
|
"gitlab-odx.oracle.com/odx/functions/api"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/id"
|
"gitlab-odx.oracle.com/odx/functions/api/id"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||||
@@ -44,7 +45,7 @@ func (s *Server) handleSpecial(c *gin.Context) {
|
|||||||
c.Request = r
|
c.Request = r
|
||||||
c.Set(api.AppName, r.Context().Value(api.AppName).(string))
|
c.Set(api.AppName, r.Context().Value(api.AppName).(string))
|
||||||
if c.MustGet(api.AppName).(string) == "" {
|
if c.MustGet(api.AppName).(string) == "" {
|
||||||
handleErrorResponse(c, models.ErrRunnerRouteNotFound)
|
handleErrorResponse(c, models.ErrRoutesNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,40 +112,45 @@ func (s *Server) handleRequest(c *gin.Context, enqueue models.Enqueue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{"app": appName, "path": path}).Debug("Finding route on datastore")
|
log.WithFields(logrus.Fields{"app": appName, "path": path}).Debug("Finding route on datastore")
|
||||||
routes, err := s.loadroutes(ctx, models.RouteFilter{AppName: appName, Path: path})
|
route, err := s.loadroute(ctx, appName, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleErrorResponse(c, err)
|
handleErrorResponse(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(routes) == 0 {
|
if route == nil {
|
||||||
handleErrorResponse(c, models.ErrRunnerRouteNotFound)
|
handleErrorResponse(c, models.ErrRoutesNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithField("routes", len(routes)).Debug("Got routes from datastore")
|
|
||||||
route := routes[0]
|
|
||||||
log = log.WithFields(logrus.Fields{"app": appName, "path": route.Path, "image": route.Image})
|
log = log.WithFields(logrus.Fields{"app": appName, "path": route.Path, "image": route.Image})
|
||||||
|
log.Debug("Got route from datastore")
|
||||||
|
|
||||||
if s.serve(ctx, c, appName, route, app, path, reqID, payload, enqueue) {
|
if s.serve(ctx, c, appName, route, app, path, reqID, payload, enqueue) {
|
||||||
s.FireAfterDispatch(ctx, reqRoute)
|
s.FireAfterDispatch(ctx, reqRoute)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handleErrorResponse(c, models.ErrRunnerRouteNotFound)
|
handleErrorResponse(c, models.ErrRoutesNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) loadroutes(ctx context.Context, filter models.RouteFilter) ([]*models.Route, error) {
|
func (s *Server) loadroute(ctx context.Context, appName, path string) (*models.Route, error) {
|
||||||
if route, ok := s.cacheget(filter.AppName, filter.Path); ok {
|
if route, ok := s.cacheget(appName, path); ok {
|
||||||
return []*models.Route{route}, nil
|
return route, nil
|
||||||
}
|
}
|
||||||
|
key := routeCacheKey(appName, path)
|
||||||
resp, err := s.singleflight.do(
|
resp, err := s.singleflight.do(
|
||||||
filter,
|
key,
|
||||||
func() (interface{}, error) {
|
func() (interface{}, error) {
|
||||||
return s.Datastore.GetRoutesByApp(ctx, filter.AppName, &filter)
|
return s.Datastore.GetRoute(ctx, appName, path)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return resp.([]*models.Route), err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
route := resp.(*models.Route)
|
||||||
|
s.routeCache.Set(key, route, cache.DefaultExpiration)
|
||||||
|
return route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should remove *gin.Context from these functions, should use only context.Context
|
// TODO: Should remove *gin.Context from these functions, should use only context.Context
|
||||||
|
|||||||
@@ -7,25 +7,26 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
cache "github.com/patrickmn/go-cache"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/runner"
|
"gitlab-odx.oracle.com/odx/functions/api/runner"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/server/internal/routecache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRouterAsync(ds models.Datastore, mq models.MessageQueue, rnr *runner.Runner, enqueue models.Enqueue) *gin.Engine {
|
func testRouterAsync(ds models.Datastore, mq models.MessageQueue, rnr *runner.Runner, enqueue models.Enqueue) *gin.Engine {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Runner: rnr,
|
Runner: rnr,
|
||||||
Router: gin.New(),
|
Router: gin.New(),
|
||||||
Datastore: ds,
|
Datastore: ds,
|
||||||
MQ: mq,
|
MQ: mq,
|
||||||
Enqueue: enqueue,
|
Enqueue: enqueue,
|
||||||
hotroutes: routecache.New(10),
|
routeCache: cache.New(60*time.Second, 5*time.Minute),
|
||||||
}
|
}
|
||||||
|
|
||||||
r := s.Router
|
r := s.Router
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func TestRouteRunnerGet(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"/route", "", http.StatusNotFound, nil},
|
{"/route", "", http.StatusNotFound, nil},
|
||||||
{"/r/app/route", "", http.StatusNotFound, models.ErrAppsNotFound},
|
{"/r/app/route", "", http.StatusNotFound, models.ErrAppsNotFound},
|
||||||
{"/r/myapp/route", "", http.StatusNotFound, models.ErrRunnerRouteNotFound},
|
{"/r/myapp/route", "", http.StatusNotFound, models.ErrRoutesNotFound},
|
||||||
} {
|
} {
|
||||||
_, rec := routerRequest(t, srv.Router, "GET", test.path, nil)
|
_, rec := routerRequest(t, srv.Router, "GET", test.path, nil)
|
||||||
|
|
||||||
@@ -60,8 +60,8 @@ func TestRouteRunnerGet(t *testing.T) {
|
|||||||
|
|
||||||
if !strings.Contains(resp.Error.Message, test.expectedError.Error()) {
|
if !strings.Contains(resp.Error.Message, test.expectedError.Error()) {
|
||||||
t.Log(buf.String())
|
t.Log(buf.String())
|
||||||
t.Errorf("Test %d: Expected error message to have `%s`",
|
t.Errorf("Test %d: Expected error message to have `%s`, but got `%s`",
|
||||||
i, test.expectedError.Error())
|
i, test.expectedError.Error(), resp.Error.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ func TestRouteRunnerPost(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"/route", `{ "payload": "" }`, http.StatusNotFound, nil},
|
{"/route", `{ "payload": "" }`, http.StatusNotFound, nil},
|
||||||
{"/r/app/route", `{ "payload": "" }`, http.StatusNotFound, models.ErrAppsNotFound},
|
{"/r/app/route", `{ "payload": "" }`, http.StatusNotFound, models.ErrAppsNotFound},
|
||||||
{"/r/myapp/route", `{ "payload": "" }`, http.StatusNotFound, models.ErrRunnerRouteNotFound},
|
{"/r/myapp/route", `{ "payload": "" }`, http.StatusNotFound, models.ErrRoutesNotFound},
|
||||||
} {
|
} {
|
||||||
body := bytes.NewBuffer([]byte(test.body))
|
body := bytes.NewBuffer([]byte(test.body))
|
||||||
_, rec := routerRequest(t, srv.Router, "POST", test.path, body)
|
_, rec := routerRequest(t, srv.Router, "POST", test.path, body)
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/ccirello/supervisor"
|
"github.com/ccirello/supervisor"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api"
|
"gitlab-odx.oracle.com/odx/functions/api"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
||||||
@@ -24,7 +25,6 @@ import (
|
|||||||
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/runner"
|
"gitlab-odx.oracle.com/odx/functions/api/runner"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/runner/common"
|
"gitlab-odx.oracle.com/odx/functions/api/runner/common"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/server/internal/routecache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -51,8 +51,7 @@ type Server struct {
|
|||||||
middlewares []Middleware
|
middlewares []Middleware
|
||||||
runnerListeners []RunnerListener
|
runnerListeners []RunnerListener
|
||||||
|
|
||||||
mu sync.Mutex // protects hotroutes
|
routeCache *cache.Cache
|
||||||
hotroutes *routecache.Cache
|
|
||||||
singleflight singleflight // singleflight assists Datastore
|
singleflight singleflight // singleflight assists Datastore
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,14 +94,14 @@ func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, logDB
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Runner: rnr,
|
Runner: rnr,
|
||||||
Router: gin.New(),
|
Router: gin.New(),
|
||||||
Datastore: ds,
|
Datastore: ds,
|
||||||
MQ: mq,
|
MQ: mq,
|
||||||
hotroutes: routecache.New(cacheSize),
|
routeCache: cache.New(5*time.Second, 5*time.Minute),
|
||||||
LogDB: logDB,
|
LogDB: logDB,
|
||||||
Enqueue: DefaultEnqueue,
|
Enqueue: DefaultEnqueue,
|
||||||
apiURL: apiURL,
|
apiURL: apiURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
setMachineId()
|
setMachineId()
|
||||||
@@ -110,6 +109,9 @@ func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, logDB
|
|||||||
s.bindHandlers(ctx)
|
s.bindHandlers(ctx)
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
if opt == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
opt(s)
|
opt(s)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
@@ -171,26 +173,19 @@ func DefaultEnqueue(ctx context.Context, mq models.MessageQueue, task *models.Ta
|
|||||||
return mq.Push(ctx, task)
|
return mq.Push(ctx, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func routeCacheKey(appname, path string) string {
|
||||||
|
return fmt.Sprintf("%s_%s", appname, path)
|
||||||
|
}
|
||||||
func (s *Server) cacheget(appname, path string) (*models.Route, bool) {
|
func (s *Server) cacheget(appname, path string) (*models.Route, bool) {
|
||||||
s.mu.Lock()
|
route, ok := s.routeCache.Get(routeCacheKey(appname, path))
|
||||||
defer s.mu.Unlock()
|
|
||||||
route, ok := s.hotroutes.Get(appname, path)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
return route, ok
|
return route.(*models.Route), ok
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) cacheRefresh(route *models.Route) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.hotroutes.Refresh(route)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) cachedelete(appname, path string) {
|
func (s *Server) cachedelete(appname, path string) {
|
||||||
s.mu.Lock()
|
s.routeCache.Delete(routeCacheKey(appname, path))
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.hotroutes.Delete(appname, path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleRunnerRequest(c *gin.Context) {
|
func (s *Server) handleRunnerRequest(c *gin.Context) {
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
cache "github.com/patrickmn/go-cache"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/runner"
|
"gitlab-odx.oracle.com/odx/functions/api/runner"
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/server/internal/routecache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var tmpDatastoreTests = "/tmp/func_test_datastore.db"
|
var tmpDatastoreTests = "/tmp/func_test_datastore.db"
|
||||||
@@ -25,13 +26,13 @@ func testServer(ds models.Datastore, mq models.MessageQueue, logDB models.FnLog,
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Runner: rnr,
|
Runner: rnr,
|
||||||
Router: gin.New(),
|
Router: gin.New(),
|
||||||
Datastore: ds,
|
Datastore: ds,
|
||||||
LogDB: nil,
|
LogDB: nil,
|
||||||
MQ: mq,
|
MQ: mq,
|
||||||
Enqueue: DefaultEnqueue,
|
Enqueue: DefaultEnqueue,
|
||||||
hotroutes: routecache.New(2),
|
routeCache: cache.New(60*time.Second, 5*time.Minute),
|
||||||
}
|
}
|
||||||
|
|
||||||
r := s.Router
|
r := s.Router
|
||||||
@@ -102,7 +103,6 @@ func TestFullStack(t *testing.T) {
|
|||||||
defer rnrcancel()
|
defer rnrcancel()
|
||||||
|
|
||||||
srv := testServer(ds, &mqs.Mock{}, logDB, rnr)
|
srv := testServer(ds, &mqs.Mock{}, logDB, rnr)
|
||||||
srv.hotroutes = routecache.New(2)
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -115,13 +115,15 @@ func TestFullStack(t *testing.T) {
|
|||||||
{"create my app", "POST", "/v1/apps", `{ "app": { "name": "myapp" } }`, http.StatusOK, 0},
|
{"create my app", "POST", "/v1/apps", `{ "app": { "name": "myapp" } }`, http.StatusOK, 0},
|
||||||
{"list apps", "GET", "/v1/apps", ``, http.StatusOK, 0},
|
{"list apps", "GET", "/v1/apps", ``, http.StatusOK, 0},
|
||||||
{"get app", "GET", "/v1/apps/myapp", ``, http.StatusOK, 0},
|
{"get app", "GET", "/v1/apps/myapp", ``, http.StatusOK, 0},
|
||||||
{"add myroute", "POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute", "path": "/myroute", "image": "funcy/hello" } }`, http.StatusOK, 1},
|
// NOTE: cache is lazy, loads when a request comes in for the route, not when added
|
||||||
{"add myroute2", "POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute2", "path": "/myroute2", "image": "funcy/error" } }`, http.StatusOK, 2},
|
{"add myroute", "POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute", "path": "/myroute", "image": "funcy/hello" } }`, http.StatusOK, 0},
|
||||||
{"get myroute", "GET", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK, 2},
|
{"add myroute2", "POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute2", "path": "/myroute2", "image": "funcy/error" } }`, http.StatusOK, 0},
|
||||||
{"get myroute2", "GET", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK, 2},
|
{"get myroute", "GET", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK, 0},
|
||||||
{"get all routes", "GET", "/v1/apps/myapp/routes", ``, http.StatusOK, 2},
|
{"get myroute2", "GET", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK, 0},
|
||||||
{"execute myroute", "POST", "/r/myapp/myroute", `{ "name": "Teste" }`, http.StatusOK, 2},
|
{"get all routes", "GET", "/v1/apps/myapp/routes", ``, http.StatusOK, 0},
|
||||||
|
{"execute myroute", "POST", "/r/myapp/myroute", `{ "name": "Teste" }`, http.StatusOK, 1},
|
||||||
{"execute myroute2", "POST", "/r/myapp/myroute2", `{ "name": "Teste" }`, http.StatusInternalServerError, 2},
|
{"execute myroute2", "POST", "/r/myapp/myroute2", `{ "name": "Teste" }`, http.StatusInternalServerError, 2},
|
||||||
|
{"get myroute2", "GET", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK, 2},
|
||||||
{"delete myroute", "DELETE", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK, 1},
|
{"delete myroute", "DELETE", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK, 1},
|
||||||
{"delete app (fail)", "DELETE", "/v1/apps/myapp", ``, http.StatusConflict, 1},
|
{"delete app (fail)", "DELETE", "/v1/apps/myapp", ``, http.StatusConflict, 1},
|
||||||
{"delete myroute2", "DELETE", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK, 0},
|
{"delete myroute2", "DELETE", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK, 0},
|
||||||
@@ -136,10 +138,10 @@ func TestFullStack(t *testing.T) {
|
|||||||
t.Errorf("Test \"%s\": Expected status code to be %d but was %d",
|
t.Errorf("Test \"%s\": Expected status code to be %d but was %d",
|
||||||
test.name, test.expectedCode, rec.Code)
|
test.name, test.expectedCode, rec.Code)
|
||||||
}
|
}
|
||||||
if srv.hotroutes.Len() != test.expectedCacheSize {
|
if srv.routeCache.ItemCount() != test.expectedCacheSize {
|
||||||
t.Log(buf.String())
|
t.Log(buf.String())
|
||||||
t.Errorf("Test \"%s\": Expected cache size to be %d but was %d",
|
t.Errorf("Test \"%s\": Expected cache size to be %d but was %d",
|
||||||
test.name, test.expectedCacheSize, srv.hotroutes.Len())
|
test.name, test.expectedCacheSize, srv.routeCache.ItemCount())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// call is an in-flight or completed do call
|
// call is an in-flight or completed do call
|
||||||
@@ -16,18 +14,18 @@ type call struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type singleflight struct {
|
type singleflight struct {
|
||||||
mu sync.Mutex // protects m
|
mu sync.Mutex // protects m
|
||||||
m map[models.RouteFilter]*call // lazily initialized
|
m map[interface{}]*call // lazily initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// do executes and returns the results of the given function, making
|
// do executes and returns the results of the given function, making
|
||||||
// sure that only one execution is in-flight for a given key at a
|
// sure that only one execution is in-flight for a given key at a
|
||||||
// time. If a duplicate comes in, the duplicate caller waits for the
|
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||||
// original to complete and receives the same results.
|
// original to complete and receives the same results.
|
||||||
func (g *singleflight) do(key models.RouteFilter, fn func() (interface{}, error)) (interface{}, error) {
|
func (g *singleflight) do(key interface{}, fn func() (interface{}, error)) (interface{}, error) {
|
||||||
g.mu.Lock()
|
g.mu.Lock()
|
||||||
if g.m == nil {
|
if g.m == nil {
|
||||||
g.m = make(map[models.RouteFilter]*call)
|
g.m = make(map[interface{}]*call)
|
||||||
}
|
}
|
||||||
if c, ok := g.m[key]; ok {
|
if c, ok := g.m[key]; ok {
|
||||||
g.mu.Unlock()
|
g.mu.Unlock()
|
||||||
|
|||||||
6
glide.lock
generated
6
glide.lock
generated
@@ -1,5 +1,5 @@
|
|||||||
hash: 91e44ada5fc186bf55c6e794c2ea7733ae1b915e850261e67f0e2ed1ba55eb8b
|
hash: ed88f1a46f149bac3eea6052d409a2a619f762ee51f2655b3fc22e8b2fa806ad
|
||||||
updated: 2017-07-17T12:03:47.884564997-07:00
|
updated: 2017-07-19T22:11:29.697513445-07:00
|
||||||
imports:
|
imports:
|
||||||
- name: code.cloudfoundry.org/bytefmt
|
- name: code.cloudfoundry.org/bytefmt
|
||||||
version: f4415fafc5619dd75599a54a7c91fb3948ad58bd
|
version: f4415fafc5619dd75599a54a7c91fb3948ad58bd
|
||||||
@@ -201,6 +201,8 @@ imports:
|
|||||||
subpackages:
|
subpackages:
|
||||||
- libcontainer/system
|
- libcontainer/system
|
||||||
- libcontainer/user
|
- libcontainer/user
|
||||||
|
- name: github.com/patrickmn/go-cache
|
||||||
|
version: 7ac151875ffb48b9f3ccce9ea20f020b0c1596c8
|
||||||
- name: github.com/pelletier/go-buffruneio
|
- name: github.com/pelletier/go-buffruneio
|
||||||
version: c37440a7cf42ac63b919c752ca73a85067e05992
|
version: c37440a7cf42ac63b919c752ca73a85067e05992
|
||||||
- name: github.com/pelletier/go-toml
|
- name: github.com/pelletier/go-toml
|
||||||
|
|||||||
@@ -72,4 +72,6 @@ testImport:
|
|||||||
- package: github.com/vrischmann/envconfig
|
- package: github.com/vrischmann/envconfig
|
||||||
- package: github.com/opencontainers/go-digest
|
- package: github.com/opencontainers/go-digest
|
||||||
branch: master
|
branch: master
|
||||||
|
- package: github.com/patrickmn/go-cache
|
||||||
|
branch: master
|
||||||
|
|
||||||
9
vendor/github.com/patrickmn/go-cache/CONTRIBUTORS
generated
vendored
Normal file
9
vendor/github.com/patrickmn/go-cache/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
This is a list of people who have contributed code to go-cache. They, or their
|
||||||
|
employers, are the copyright holders of the contributed code. Contributed code
|
||||||
|
is subject to the license restrictions listed in LICENSE (as they were when the
|
||||||
|
code was contributed.)
|
||||||
|
|
||||||
|
Dustin Sallings <dustin@spy.net>
|
||||||
|
Jason Mooberry <jasonmoo@me.com>
|
||||||
|
Sergey Shepelev <temotor@gmail.com>
|
||||||
|
Alex Edwards <ajmedwards@gmail.com>
|
||||||
19
vendor/github.com/patrickmn/go-cache/LICENSE
generated
vendored
Normal file
19
vendor/github.com/patrickmn/go-cache/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2012-2017 Patrick Mylund Nielsen and the go-cache contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
83
vendor/github.com/patrickmn/go-cache/README.md
generated
vendored
Normal file
83
vendor/github.com/patrickmn/go-cache/README.md
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# go-cache
|
||||||
|
|
||||||
|
go-cache is an in-memory key:value store/cache similar to memcached that is
|
||||||
|
suitable for applications running on a single machine. Its major advantage is
|
||||||
|
that, being essentially a thread-safe `map[string]interface{}` with expiration
|
||||||
|
times, it doesn't need to serialize or transmit its contents over the network.
|
||||||
|
|
||||||
|
Any object can be stored, for a given duration or forever, and the cache can be
|
||||||
|
safely used by multiple goroutines.
|
||||||
|
|
||||||
|
Although go-cache isn't meant to be used as a persistent datastore, the entire
|
||||||
|
cache can be saved to and loaded from a file (using `c.Items()` to retrieve the
|
||||||
|
items map to serialize, and `NewFrom()` to create a cache from a deserialized
|
||||||
|
one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats.)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
`go get github.com/patrickmn/go-cache`
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a cache with a default expiration time of 5 minutes, and which
|
||||||
|
// purges expired items every 10 minutes
|
||||||
|
c := cache.New(5*time.Minute, 10*time.Minute)
|
||||||
|
|
||||||
|
// Set the value of the key "foo" to "bar", with the default expiration time
|
||||||
|
c.Set("foo", "bar", cache.DefaultExpiration)
|
||||||
|
|
||||||
|
// Set the value of the key "baz" to 42, with no expiration time
|
||||||
|
// (the item won't be removed until it is re-set, or removed using
|
||||||
|
// c.Delete("baz")
|
||||||
|
c.Set("baz", 42, cache.NoExpiration)
|
||||||
|
|
||||||
|
// Get the string associated with the key "foo" from the cache
|
||||||
|
foo, found := c.Get("foo")
|
||||||
|
if found {
|
||||||
|
fmt.Println(foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since Go is statically typed, and cache values can be anything, type
|
||||||
|
// assertion is needed when values are being passed to functions that don't
|
||||||
|
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
|
||||||
|
// values which will only be used once--e.g. for passing to another
|
||||||
|
// function--is:
|
||||||
|
foo, found := c.Get("foo")
|
||||||
|
if found {
|
||||||
|
MyFunction(foo.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This gets tedious if the value is used several times in the same function.
|
||||||
|
// You might do either of the following instead:
|
||||||
|
if x, found := c.Get("foo"); found {
|
||||||
|
foo := x.(string)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
// or
|
||||||
|
var foo string
|
||||||
|
if x, found := c.Get("foo"); found {
|
||||||
|
foo = x.(string)
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
// foo can then be passed around freely as a string
|
||||||
|
|
||||||
|
// Want performance? Store pointers!
|
||||||
|
c.Set("foo", &MyStruct, cache.DefaultExpiration)
|
||||||
|
if x, found := c.Get("foo"); found {
|
||||||
|
foo := x.(*MyStruct)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
|
||||||
|
`godoc` or [http://godoc.org/github.com/patrickmn/go-cache](http://godoc.org/github.com/patrickmn/go-cache)
|
||||||
1161
vendor/github.com/patrickmn/go-cache/cache.go
generated
vendored
Normal file
1161
vendor/github.com/patrickmn/go-cache/cache.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1771
vendor/github.com/patrickmn/go-cache/cache_test.go
generated
vendored
Normal file
1771
vendor/github.com/patrickmn/go-cache/cache_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
192
vendor/github.com/patrickmn/go-cache/sharded.go
generated
vendored
Normal file
192
vendor/github.com/patrickmn/go-cache/sharded.go
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
insecurerand "math/rand"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is an experimental and unexported (for now) attempt at making a cache
|
||||||
|
// with better algorithmic complexity than the standard one, namely by
|
||||||
|
// preventing write locks of the entire cache when an item is added. As of the
|
||||||
|
// time of writing, the overhead of selecting buckets results in cache
|
||||||
|
// operations being about twice as slow as for the standard cache with small
|
||||||
|
// total cache sizes, and faster for larger ones.
|
||||||
|
//
|
||||||
|
// See cache_test.go for a few benchmarks.
|
||||||
|
|
||||||
|
type unexportedShardedCache struct {
|
||||||
|
*shardedCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type shardedCache struct {
|
||||||
|
seed uint32
|
||||||
|
m uint32
|
||||||
|
cs []*cache
|
||||||
|
janitor *shardedJanitor
|
||||||
|
}
|
||||||
|
|
||||||
|
// djb2 with better shuffling. 5x faster than FNV with the hash.Hash overhead.
|
||||||
|
func djb33(seed uint32, k string) uint32 {
|
||||||
|
var (
|
||||||
|
l = uint32(len(k))
|
||||||
|
d = 5381 + seed + l
|
||||||
|
i = uint32(0)
|
||||||
|
)
|
||||||
|
// Why is all this 5x faster than a for loop?
|
||||||
|
if l >= 4 {
|
||||||
|
for i < l-4 {
|
||||||
|
d = (d * 33) ^ uint32(k[i])
|
||||||
|
d = (d * 33) ^ uint32(k[i+1])
|
||||||
|
d = (d * 33) ^ uint32(k[i+2])
|
||||||
|
d = (d * 33) ^ uint32(k[i+3])
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch l - i {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
d = (d * 33) ^ uint32(k[i])
|
||||||
|
case 3:
|
||||||
|
d = (d * 33) ^ uint32(k[i])
|
||||||
|
d = (d * 33) ^ uint32(k[i+1])
|
||||||
|
case 4:
|
||||||
|
d = (d * 33) ^ uint32(k[i])
|
||||||
|
d = (d * 33) ^ uint32(k[i+1])
|
||||||
|
d = (d * 33) ^ uint32(k[i+2])
|
||||||
|
}
|
||||||
|
return d ^ (d >> 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) bucket(k string) *cache {
|
||||||
|
return sc.cs[djb33(sc.seed, k)%sc.m]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Set(k string, x interface{}, d time.Duration) {
|
||||||
|
sc.bucket(k).Set(k, x, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Add(k string, x interface{}, d time.Duration) error {
|
||||||
|
return sc.bucket(k).Add(k, x, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Replace(k string, x interface{}, d time.Duration) error {
|
||||||
|
return sc.bucket(k).Replace(k, x, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Get(k string) (interface{}, bool) {
|
||||||
|
return sc.bucket(k).Get(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Increment(k string, n int64) error {
|
||||||
|
return sc.bucket(k).Increment(k, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) IncrementFloat(k string, n float64) error {
|
||||||
|
return sc.bucket(k).IncrementFloat(k, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Decrement(k string, n int64) error {
|
||||||
|
return sc.bucket(k).Decrement(k, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Delete(k string) {
|
||||||
|
sc.bucket(k).Delete(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) DeleteExpired() {
|
||||||
|
for _, v := range sc.cs {
|
||||||
|
v.DeleteExpired()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the items in the cache. This may include items that have expired,
|
||||||
|
// but have not yet been cleaned up. If this is significant, the Expiration
|
||||||
|
// fields of the items should be checked. Note that explicit synchronization
|
||||||
|
// is needed to use a cache and its corresponding Items() return values at
|
||||||
|
// the same time, as the maps are shared.
|
||||||
|
func (sc *shardedCache) Items() []map[string]Item {
|
||||||
|
res := make([]map[string]Item, len(sc.cs))
|
||||||
|
for i, v := range sc.cs {
|
||||||
|
res[i] = v.Items()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *shardedCache) Flush() {
|
||||||
|
for _, v := range sc.cs {
|
||||||
|
v.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type shardedJanitor struct {
|
||||||
|
Interval time.Duration
|
||||||
|
stop chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *shardedJanitor) Run(sc *shardedCache) {
|
||||||
|
j.stop = make(chan bool)
|
||||||
|
tick := time.Tick(j.Interval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick:
|
||||||
|
sc.DeleteExpired()
|
||||||
|
case <-j.stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopShardedJanitor(sc *unexportedShardedCache) {
|
||||||
|
sc.janitor.stop <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func runShardedJanitor(sc *shardedCache, ci time.Duration) {
|
||||||
|
j := &shardedJanitor{
|
||||||
|
Interval: ci,
|
||||||
|
}
|
||||||
|
sc.janitor = j
|
||||||
|
go j.Run(sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShardedCache(n int, de time.Duration) *shardedCache {
|
||||||
|
max := big.NewInt(0).SetUint64(uint64(math.MaxUint32))
|
||||||
|
rnd, err := rand.Int(rand.Reader, max)
|
||||||
|
var seed uint32
|
||||||
|
if err != nil {
|
||||||
|
os.Stderr.Write([]byte("WARNING: go-cache's newShardedCache failed to read from the system CSPRNG (/dev/urandom or equivalent.) Your system's security may be compromised. Continuing with an insecure seed.\n"))
|
||||||
|
seed = insecurerand.Uint32()
|
||||||
|
} else {
|
||||||
|
seed = uint32(rnd.Uint64())
|
||||||
|
}
|
||||||
|
sc := &shardedCache{
|
||||||
|
seed: seed,
|
||||||
|
m: uint32(n),
|
||||||
|
cs: make([]*cache, n),
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
c := &cache{
|
||||||
|
defaultExpiration: de,
|
||||||
|
items: map[string]Item{},
|
||||||
|
}
|
||||||
|
sc.cs[i] = c
|
||||||
|
}
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
func unexportedNewSharded(defaultExpiration, cleanupInterval time.Duration, shards int) *unexportedShardedCache {
|
||||||
|
if defaultExpiration == 0 {
|
||||||
|
defaultExpiration = -1
|
||||||
|
}
|
||||||
|
sc := newShardedCache(shards, defaultExpiration)
|
||||||
|
SC := &unexportedShardedCache{sc}
|
||||||
|
if cleanupInterval > 0 {
|
||||||
|
runShardedJanitor(sc, cleanupInterval)
|
||||||
|
runtime.SetFinalizer(SC, stopShardedJanitor)
|
||||||
|
}
|
||||||
|
return SC
|
||||||
|
}
|
||||||
85
vendor/github.com/patrickmn/go-cache/sharded_test.go
generated
vendored
Normal file
85
vendor/github.com/patrickmn/go-cache/sharded_test.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// func TestDjb33(t *testing.T) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
var shardedKeys = []string{
|
||||||
|
"f",
|
||||||
|
"fo",
|
||||||
|
"foo",
|
||||||
|
"barf",
|
||||||
|
"barfo",
|
||||||
|
"foobar",
|
||||||
|
"bazbarf",
|
||||||
|
"bazbarfo",
|
||||||
|
"bazbarfoo",
|
||||||
|
"foobarbazq",
|
||||||
|
"foobarbazqu",
|
||||||
|
"foobarbazquu",
|
||||||
|
"foobarbazquux",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShardedCache(t *testing.T) {
|
||||||
|
tc := unexportedNewSharded(DefaultExpiration, 0, 13)
|
||||||
|
for _, v := range shardedKeys {
|
||||||
|
tc.Set(v, "value", DefaultExpiration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkShardedCacheGetExpiring(b *testing.B) {
|
||||||
|
benchmarkShardedCacheGet(b, 5*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkShardedCacheGetNotExpiring(b *testing.B) {
|
||||||
|
benchmarkShardedCacheGet(b, NoExpiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkShardedCacheGet(b *testing.B, exp time.Duration) {
|
||||||
|
b.StopTimer()
|
||||||
|
tc := unexportedNewSharded(exp, 0, 10)
|
||||||
|
tc.Set("foobarba", "zquux", DefaultExpiration)
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tc.Get("foobarba")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkShardedCacheGetManyConcurrentExpiring(b *testing.B) {
|
||||||
|
benchmarkShardedCacheGetManyConcurrent(b, 5*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkShardedCacheGetManyConcurrentNotExpiring(b *testing.B) {
|
||||||
|
benchmarkShardedCacheGetManyConcurrent(b, NoExpiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkShardedCacheGetManyConcurrent(b *testing.B, exp time.Duration) {
|
||||||
|
b.StopTimer()
|
||||||
|
n := 10000
|
||||||
|
tsc := unexportedNewSharded(exp, 0, 20)
|
||||||
|
keys := make([]string, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
k := "foo" + strconv.Itoa(n)
|
||||||
|
keys[i] = k
|
||||||
|
tsc.Set(k, "bar", DefaultExpiration)
|
||||||
|
}
|
||||||
|
each := b.N / n
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
wg.Add(n)
|
||||||
|
for _, v := range keys {
|
||||||
|
go func() {
|
||||||
|
for j := 0; j < each; j++ {
|
||||||
|
tsc.Get(v)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
b.StartTimer()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user