diff options
author | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
---|---|---|
committer | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
commit | 22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch) | |
tree | bffa27765faf54126ad44bcafa89fadecb7a73d7 /library/go/yandex/tvm/tvmtool/internal | |
parent | 332b99e2173f0425444abb759eebcb2fafaa9209 (diff) | |
download | ydb-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.go | 128 | ||||
-rw-r--r-- | library/go/yandex/tvm/tvmtool/internal/cache/cache_test.go | 125 |
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) + }) +} |