From aecce1b6d6f239189fa7471be748b9599786ba54 Mon Sep 17 00:00:00 2001 From: Pavel Karpy Date: Mon, 10 Jul 2023 23:55:48 +0300 Subject: [PATCH] node: Do not update cache twice Do not request morph values on morph cache misses concurrently. Closes #1248. Signed-off-by: Pavel Karpy --- CHANGELOG.md | 1 + cmd/neofs-node/cache.go | 59 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a030b41a9..11ad09dad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Changelog for NeoFS Node ### Fixed - `neo-go` RPC connection lost handling by IR (#1337) +- Concurrent morph cache misses (#1248) ### Removed - Deprecated `morph.rpc_endpoint` SN and `morph.endpoint.client` IR config sections (#2400) diff --git a/cmd/neofs-node/cache.go b/cmd/neofs-node/cache.go index f459666fce..a7d9ada834 100644 --- a/cmd/neofs-node/cache.go +++ b/cmd/neofs-node/cache.go @@ -24,9 +24,19 @@ type valueWithTime[V any] struct { e error } +// valueInProgress is a struct that contains +// values that are being fetched/updated. +type valueInProgress[V any] struct { + m sync.RWMutex + v V + e error +} + // entity that provides TTL cache interface. type ttlNetCache[K comparable, V any] struct { - ttl time.Duration + m *sync.RWMutex // protects progMap + progMap map[K]*valueInProgress[V] // contains fetch-in-progress keys + ttl time.Duration sz int @@ -41,13 +51,22 @@ func newNetworkTTLCache[K comparable, V any](sz int, ttl time.Duration, netRdr n fatalOnErr(err) return ttlNetCache[K, V]{ - ttl: ttl, - sz: sz, - cache: cache, - netRdr: netRdr, + ttl: ttl, + sz: sz, + cache: cache, + netRdr: netRdr, + m: &sync.RWMutex{}, + progMap: make(map[K]*valueInProgress[V]), } } +func waitForUpdate[V any](vip *valueInProgress[V]) (V, error) { + vip.m.RLock() + defer vip.m.RUnlock() + + return vip.v, vip.e +} + // reads value by the key. // // updates the value from the network on cache miss or by TTL. @@ -63,10 +82,38 @@ func (c *ttlNetCache[K, V]) get(key K) (V, error) { c.cache.Remove(key) } - netVal, err := c.netRdr(key) + c.m.RLock() + valInProg, ok := c.progMap[key] + c.m.RUnlock() + if ok { + return waitForUpdate(valInProg) + } + + c.m.Lock() + valInProg, ok = c.progMap[key] + if ok { + c.m.Unlock() + return waitForUpdate(valInProg) + } + + valInProg = &valueInProgress[V]{} + valInProg.m.Lock() + c.progMap[key] = valInProg + + c.m.Unlock() + + netVal, err := c.netRdr(key) c.set(key, netVal, err) + valInProg.v = netVal + valInProg.e = err + valInProg.m.Unlock() + + c.m.Lock() + delete(c.progMap, key) + c.m.Unlock() + return netVal, err }