From 084319fb22f84c1ee305d44aa6d0eac13f7c42cc Mon Sep 17 00:00:00 2001 From: Ben Grewell Date: Fri, 31 Jul 2026 00:51:27 +0000 Subject: [PATCH 1/3] fix(nrfcache): stop stalling cache hits behind an in-flight NRF query handleLookup held the cache write lock across the NRF round trip. A sync.RWMutex queues new readers behind a waiting writer, so an in-flight discovery blocked every concurrent cache *hit* for that NF type until the network call returned. For an NF type that misses persistently the stall is continuous, and it serialises discovery core-wide: on SD-Core this capped registration at roughly one attach per discovery round trip. Serialise discovery on a dedicated discoveryMutex instead, and take the cache lock only to read the entry and to store the result. A burst of concurrent misses still collapses into a single NRF query, because the goroutine that wins discoveryMutex populates the cache and the rest find the entry on the re-check, so the existing single-callback behaviour is unchanged. Cache hits no longer wait on the network. Signed-off-by: Ben Grewell --- nrfcache/nrfcache.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/nrfcache/nrfcache.go b/nrfcache/nrfcache.go index 23adb015..5e2686ee 100644 --- a/nrfcache/nrfcache.go +++ b/nrfcache/nrfcache.go @@ -132,6 +132,7 @@ type NrfCache struct { nrfDiscoveryQueryCb NrfDiscoveryQueryCb // nrf query callback evictionInterval time.Duration // timer interval in which the cache is checked for eviction of expired entries mutex sync.RWMutex + discoveryMutex sync.Mutex } // handleLookup - Checks if the cache has nf cache entry corresponding to the parameters specified. @@ -154,11 +155,21 @@ func (c *NrfCache) handleLookup(ctx context.Context, nrfUri string, targetNfType return models.SearchResult{NfInstances: nfInstances}, nil } - // Cache miss - acquire write lock - c.mutex.Lock() - defer c.mutex.Unlock() + // Only one discovery for this NF type runs at a time, so a burst of + // concurrent misses collapses into a single NRF query. discoveryMutex is + // held instead of the cache lock: a sync.RWMutex queues new readers behind + // a waiting writer, so holding the cache write lock across the round trip + // stalls every concurrent cache *hit* for this NF type for a full RTT. + // With a permanently-missing NF type that stall is unbounded and caps + // registration throughput core-wide. + c.discoveryMutex.Lock() + defer c.discoveryMutex.Unlock() + // The winner of discoveryMutex may already have populated the entry. + c.mutex.RLock() nfInstances = c.get(param) + c.mutex.RUnlock() + if len(nfInstances) > 0 { return models.SearchResult{NfInstances: nfInstances}, nil } @@ -174,9 +185,12 @@ func (c *NrfCache) handleLookup(ctx context.Context, nrfUri string, targetNfType } ttl := time.Duration(searchResult.ValidityPeriod) * time.Second + + c.mutex.Lock() for i := range searchResult.NfInstances { c.set(&searchResult.NfInstances[i], ttl) } + c.mutex.Unlock() return *searchResult, nil } From 08bcc269b58a7bee8858ce7ab288354f0eb9ca90 Mon Sep 17 00:00:00 2001 From: Ben Grewell Date: Thu, 6 Aug 2026 16:58:38 -0700 Subject: [PATCH 2/3] Update nrfcache/nrfcache.go Co-authored-by: Gabriel Arrobo Signed-off-by: Ben Grewell --- nrfcache/nrfcache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrfcache/nrfcache.go b/nrfcache/nrfcache.go index 5e2686ee..531ee9e7 100644 --- a/nrfcache/nrfcache.go +++ b/nrfcache/nrfcache.go @@ -165,7 +165,7 @@ func (c *NrfCache) handleLookup(ctx context.Context, nrfUri string, targetNfType c.discoveryMutex.Lock() defer c.discoveryMutex.Unlock() - // The winner of discoveryMutex may already have populated the entry. + // Re-check in case another goroutine already populated the entry. c.mutex.RLock() nfInstances = c.get(param) c.mutex.RUnlock() From 8b1c9b7300fc64cf2dce4d4578c5d8758caf3eb2 Mon Sep 17 00:00:00 2001 From: Ben Grewell Date: Thu, 6 Aug 2026 16:58:46 -0700 Subject: [PATCH 3/3] Update nrfcache/nrfcache.go Co-authored-by: Gabriel Arrobo Signed-off-by: Ben Grewell --- nrfcache/nrfcache.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/nrfcache/nrfcache.go b/nrfcache/nrfcache.go index 531ee9e7..4c6cd8e8 100644 --- a/nrfcache/nrfcache.go +++ b/nrfcache/nrfcache.go @@ -155,13 +155,7 @@ func (c *NrfCache) handleLookup(ctx context.Context, nrfUri string, targetNfType return models.SearchResult{NfInstances: nfInstances}, nil } - // Only one discovery for this NF type runs at a time, so a burst of - // concurrent misses collapses into a single NRF query. discoveryMutex is - // held instead of the cache lock: a sync.RWMutex queues new readers behind - // a waiting writer, so holding the cache write lock across the round trip - // stalls every concurrent cache *hit* for this NF type for a full RTT. - // With a permanently-missing NF type that stall is unbounded and caps - // registration throughput core-wide. + // discoveryMutex serializes NRF round-trips without blocking concurrent cache hits. c.discoveryMutex.Lock() defer c.discoveryMutex.Unlock()