@@ -27,6 +27,7 @@ import (
2727 "fmt"
2828 "io/fs"
2929 "log"
30+ "maps"
3031 "os"
3132 "path/filepath"
3233 "strconv"
@@ -50,6 +51,7 @@ const (
5051var (
5152 cGroupCache = make (map [uint64 ]string )
5253 cGroupCacheLock sync.RWMutex
54+ cGroupRebuildMu sync.Mutex // serialises filesystem walks to avoid thundering herd
5355 cGroupInitOnce sync.Once
5456)
5557
@@ -74,8 +76,24 @@ func cGroupToPath(id uint64) string {
7476 return p
7577 }
7678
77- // Build a fresh cache outside any lock to avoid blocking readers during
78- // the filesystem walk, then swap it in under a brief write lock.
79+ // Serialise filesystem walks: only one goroutine rebuilds at a time.
80+ // This prevents the thundering-herd where N concurrent misses each do a
81+ // full walk and the last writer discards everyone else's results.
82+ cGroupRebuildMu .Lock ()
83+ defer cGroupRebuildMu .Unlock ()
84+
85+ // Re-check after acquiring the rebuild lock; a concurrent goroutine may
86+ // have already walked and populated the entry while we were waiting.
87+ cGroupCacheLock .RLock ()
88+ p , ok = cGroupCache [id ]
89+ cGroupCacheLock .RUnlock ()
90+
91+ if ok {
92+ return p
93+ }
94+
95+ // Build a fresh mapping outside any lock to avoid blocking readers during
96+ // the filesystem walk.
7997 fresh := make (map [uint64 ]string )
8098 _ = cGroupWalk (CGroupRootPath , fresh )
8199
@@ -86,8 +104,11 @@ func cGroupToPath(id uint64) string {
86104 fresh [id ] = p
87105 }
88106
107+ // Merge fresh entries into the existing cache rather than replacing it,
108+ // so that entries written by cGroupWatcher between the walk start and now
109+ // are preserved.
89110 cGroupCacheLock .Lock ()
90- cGroupCache = fresh
111+ maps . Copy ( cGroupCache , fresh )
91112 cGroupCacheLock .Unlock ()
92113
93114 return p
@@ -100,7 +121,9 @@ func cGroupToPath(id uint64) string {
100121// The function is safe to call concurrently.
101122func cGroupCacheInit () {
102123 cGroupInitOnce .Do (func () {
124+ cGroupCacheLock .Lock ()
103125 cGroupCache = make (map [uint64 ]string )
126+ cGroupCacheLock .Unlock ()
104127
105128 // initial cache refresh
106129 cgroupCacheRefresh (CGroupRootPath )
@@ -110,17 +133,21 @@ func cGroupCacheInit() {
110133// cgroupCacheRefresh refreshes the cache with the current cgroup paths.
111134//
112135// It walks the cgroup filesystem from the given directory and builds a new
113- // mapping outside of any lock. The global cache is then replaced atomically
114- // under a brief write lock, so concurrent readers are not blocked for the
115- // duration of the filesystem walk.
136+ // mapping outside of any lock. The fresh entries are then merged into the
137+ // existing cache under a brief write lock, preserving any entries that
138+ // cGroupWatcher wrote concurrently. cGroupRebuildMu is held for the duration
139+ // so that concurrent callers do not each trigger a redundant walk.
116140//
117141// The function is safe to call concurrently.
118142func cgroupCacheRefresh (dir string ) {
143+ cGroupRebuildMu .Lock ()
144+ defer cGroupRebuildMu .Unlock ()
145+
119146 fresh := make (map [uint64 ]string )
120147 _ = cGroupWalk (dir , fresh )
121148
122149 cGroupCacheLock .Lock ()
123- cGroupCache = fresh
150+ maps . Copy ( cGroupCache , fresh )
124151 cGroupCacheLock .Unlock ()
125152}
126153
0 commit comments