aboutsummaryrefslogtreecommitdiffstats
path: root/library/go/yandex/tvm/tvmtool/internal
diff options
context:
space:
mode:
authorqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
committerqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
commit22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch)
treebffa27765faf54126ad44bcafa89fadecb7a73d7 /library/go/yandex/tvm/tvmtool/internal
parent332b99e2173f0425444abb759eebcb2fafaa9209 (diff)
downloadydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz
validate canons without yatest_common
Diffstat (limited to 'library/go/yandex/tvm/tvmtool/internal')
-rw-r--r--library/go/yandex/tvm/tvmtool/internal/cache/cache.go128
-rw-r--r--library/go/yandex/tvm/tvmtool/internal/cache/cache_test.go125
2 files changed, 253 insertions, 0 deletions
diff --git a/library/go/yandex/tvm/tvmtool/internal/cache/cache.go b/library/go/yandex/tvm/tvmtool/internal/cache/cache.go
new file mode 100644
index 0000000000..b625ca774f
--- /dev/null
+++ b/library/go/yandex/tvm/tvmtool/internal/cache/cache.go
@@ -0,0 +1,128 @@
+package cache
+
+import (
+ "sync"
+ "time"
+
+ "a.yandex-team.ru/library/go/yandex/tvm"
+)
+
+const (
+ Hit Status = iota
+ Miss
+ GonnaMissy
+)
+
+type (
+ Status int
+
+ Cache struct {
+ ttl time.Duration
+ maxTTL time.Duration
+ tickets map[tvm.ClientID]entry
+ aliases map[string]tvm.ClientID
+ lock sync.RWMutex
+ }
+
+ entry struct {
+ value *string
+ born time.Time
+ }
+)
+
+func New(ttl, maxTTL time.Duration) *Cache {
+ return &Cache{
+ ttl: ttl,
+ maxTTL: maxTTL,
+ tickets: make(map[tvm.ClientID]entry, 1),
+ aliases: make(map[string]tvm.ClientID, 1),
+ }
+}
+
+func (c *Cache) Gc() {
+ now := time.Now()
+
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ for clientID, ticket := range c.tickets {
+ if ticket.born.Add(c.maxTTL).After(now) {
+ continue
+ }
+
+ delete(c.tickets, clientID)
+ for alias, aClientID := range c.aliases {
+ if clientID == aClientID {
+ delete(c.aliases, alias)
+ }
+ }
+ }
+}
+
+func (c *Cache) ClientIDs() []tvm.ClientID {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ clientIDs := make([]tvm.ClientID, 0, len(c.tickets))
+ for clientID := range c.tickets {
+ clientIDs = append(clientIDs, clientID)
+ }
+ return clientIDs
+}
+
+func (c *Cache) Aliases() []string {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ aliases := make([]string, 0, len(c.aliases))
+ for alias := range c.aliases {
+ aliases = append(aliases, alias)
+ }
+ return aliases
+}
+
+func (c *Cache) Load(clientID tvm.ClientID) (*string, Status) {
+ c.lock.RLock()
+ e, ok := c.tickets[clientID]
+ c.lock.RUnlock()
+ if !ok {
+ return nil, Miss
+ }
+
+ now := time.Now()
+ exp := e.born.Add(c.ttl)
+ if exp.After(now) {
+ return e.value, Hit
+ }
+
+ exp = e.born.Add(c.maxTTL)
+ if exp.After(now) {
+ return e.value, GonnaMissy
+ }
+
+ c.lock.Lock()
+ delete(c.tickets, clientID)
+ c.lock.Unlock()
+ return nil, Miss
+}
+
+func (c *Cache) LoadByAlias(alias string) (*string, Status) {
+ c.lock.RLock()
+ clientID, ok := c.aliases[alias]
+ c.lock.RUnlock()
+ if !ok {
+ return nil, Miss
+ }
+
+ return c.Load(clientID)
+}
+
+func (c *Cache) Store(clientID tvm.ClientID, alias string, value *string) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ c.aliases[alias] = clientID
+ c.tickets[clientID] = entry{
+ value: value,
+ born: time.Now(),
+ }
+}
diff --git a/library/go/yandex/tvm/tvmtool/internal/cache/cache_test.go b/library/go/yandex/tvm/tvmtool/internal/cache/cache_test.go
new file mode 100644
index 0000000000..d9a1780108
--- /dev/null
+++ b/library/go/yandex/tvm/tvmtool/internal/cache/cache_test.go
@@ -0,0 +1,125 @@
+package cache_test
+
+import (
+ "sort"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "a.yandex-team.ru/library/go/yandex/tvm"
+ "a.yandex-team.ru/library/go/yandex/tvm/tvmtool/internal/cache"
+)
+
+var (
+ testDst = "test_dst"
+ testDstAlias = "test_dst_alias"
+ testDstID = tvm.ClientID(1)
+ testValue = "test_val"
+)
+
+func TestNewAtHour(t *testing.T) {
+ c := cache.New(time.Hour, 11*time.Hour)
+ assert.NotNil(t, c, "failed to create cache")
+}
+
+func TestCache_Load(t *testing.T) {
+
+ c := cache.New(time.Second, time.Hour)
+ c.Store(testDstID, testDst, &testValue)
+ // checking before
+ {
+ r, hit := c.Load(testDstID)
+ assert.Equal(t, cache.Hit, hit, "failed to get '%d' from cache before deadline", testDstID)
+ assert.NotNil(t, r, "failed to get '%d' from cache before deadline", testDstID)
+ assert.Equal(t, testValue, *r)
+
+ r, hit = c.LoadByAlias(testDst)
+ assert.Equal(t, cache.Hit, hit, "failed to get '%s' from cache before deadline", testDst)
+ assert.NotNil(t, r, "failed to get %q from tickets before deadline", testDst)
+ assert.Equal(t, testValue, *r)
+ }
+ {
+ r, hit := c.Load(999833321)
+ assert.Equal(t, cache.Miss, hit, "got tickets for '999833321', but that key must be never existed")
+ assert.Nil(t, r, "got tickets for '999833321', but that key must be never existed")
+
+ r, hit = c.LoadByAlias("kek")
+ assert.Equal(t, cache.Miss, hit, "got tickets for 'kek', but that key must be never existed")
+ assert.Nil(t, r, "got tickets for 'kek', but that key must be never existed")
+ }
+
+ time.Sleep(3 * time.Second)
+ // checking after
+ {
+ r, hit := c.Load(testDstID)
+ assert.Equal(t, cache.GonnaMissy, hit)
+ assert.Equal(t, testValue, *r)
+
+ r, hit = c.LoadByAlias(testDst)
+ assert.Equal(t, cache.GonnaMissy, hit)
+ assert.Equal(t, testValue, *r)
+ }
+}
+
+func TestCache_Keys(t *testing.T) {
+ c := cache.New(time.Second, time.Hour)
+ c.Store(testDstID, testDst, &testValue)
+ c.Store(testDstID, testDstAlias, &testValue)
+
+ t.Run("aliases", func(t *testing.T) {
+ aliases := c.Aliases()
+ sort.Strings(aliases)
+ require.Equal(t, 2, len(aliases), "not correct length of aliases")
+ require.EqualValues(t, []string{testDst, testDstAlias}, aliases)
+ })
+
+ t.Run("client_ids", func(t *testing.T) {
+ ids := c.ClientIDs()
+ require.Equal(t, 1, len(ids), "not correct length of client ids")
+ require.EqualValues(t, []tvm.ClientID{testDstID}, ids)
+ })
+}
+
+func TestCache_ExpiredKeys(t *testing.T) {
+ c := cache.New(time.Second, 10*time.Second)
+ c.Store(testDstID, testDst, &testValue)
+ c.Store(testDstID, testDstAlias, &testValue)
+
+ time.Sleep(3 * time.Second)
+ c.Gc()
+
+ var (
+ newDst = "new_dst"
+ newDstID = tvm.ClientID(2)
+ )
+ c.Store(newDstID, newDst, &testValue)
+
+ t.Run("aliases", func(t *testing.T) {
+ aliases := c.Aliases()
+ require.Equal(t, 3, len(aliases), "not correct length of aliases")
+ require.ElementsMatch(t, []string{testDst, testDstAlias, newDst}, aliases)
+ })
+
+ t.Run("client_ids", func(t *testing.T) {
+ ids := c.ClientIDs()
+ require.Equal(t, 2, len(ids), "not correct length of client ids")
+ require.ElementsMatch(t, []tvm.ClientID{testDstID, newDstID}, ids)
+ })
+
+ time.Sleep(8 * time.Second)
+ c.Gc()
+
+ t.Run("aliases", func(t *testing.T) {
+ aliases := c.Aliases()
+ require.Equal(t, 1, len(aliases), "not correct length of aliases")
+ require.ElementsMatch(t, []string{newDst}, aliases)
+ })
+
+ t.Run("client_ids", func(t *testing.T) {
+ ids := c.ClientIDs()
+ require.Equal(t, 1, len(ids), "not correct length of client ids")
+ require.ElementsMatch(t, []tvm.ClientID{newDstID}, ids)
+ })
+}