@@ -238,47 +238,99 @@ func newRootScope(opts ScopeOptions, interval time.Duration) *scope {
238238
239239// report dumps all aggregated stats into the reporter. Should be called automatically by the root scope periodically.
240240func (s * scope ) report (r StatsReporter ) {
241- s .counters .Range (func (key , value interface {}) bool {
242- c := value .(* counter )
243- c .report (s .fullyQualifiedName (key .(string )), s .tags , r )
244- return true
245- })
241+ // Copy slices under their individual mutexes to avoid race with clearMetrics
242+ s .countersSliceMux .Lock ()
243+ var countersCopy []* counter
244+ if s .countersSlice != nil {
245+ countersCopy = make ([]* counter , len (s .countersSlice ))
246+ copy (countersCopy , s .countersSlice )
247+ }
248+ s .countersSliceMux .Unlock ()
246249
247- s .gauges .Range (func (key , value interface {}) bool {
248- g := value .(* gauge )
249- g .report (s .fullyQualifiedName (key .(string )), s .tags , r )
250- return true
251- })
250+ s .gaugesSliceMux .Lock ()
251+ var gaugesCopy []* gauge
252+ if s .gaugesSlice != nil {
253+ gaugesCopy = make ([]* gauge , len (s .gaugesSlice ))
254+ copy (gaugesCopy , s .gaugesSlice )
255+ }
256+ s .gaugesSliceMux .Unlock ()
257+
258+ s .histogramsSliceMux .Lock ()
259+ var histogramsCopy []* histogram
260+ if s .histogramsSlice != nil {
261+ histogramsCopy = make ([]* histogram , len (s .histogramsSlice ))
262+ copy (histogramsCopy , s .histogramsSlice )
263+ }
264+ s .histogramsSliceMux .Unlock ()
265+
266+ // Now iterate over the copies without holding any mutexes
267+ for _ , c := range countersCopy {
268+ if c != nil {
269+ c .report (c .name , s .tags , r )
270+ }
271+ }
272+
273+ for _ , g := range gaugesCopy {
274+ if g != nil {
275+ g .report (g .name , s .tags , r )
276+ }
277+ }
252278
253279 // We do nothing for timers here because timers report directly to the StatsReporter without buffering
254280
255- s . histograms . Range ( func ( key , value interface {}) bool {
256- h := value .( * histogram )
257- h .report (s . fullyQualifiedName ( key .( string )) , s .tags , r )
258- return true
259- })
281+ for _ , h := range histogramsCopy {
282+ if h != nil {
283+ h .report (h . name , s .tags , r )
284+ }
285+ }
260286}
261287
262288func (s * scope ) cachedReport () {
263- s .counters .Range (func (key , value interface {}) bool {
264- c := value .(* counter )
265- c .cachedReport ()
266- return true
267- })
289+ // Copy slices under their individual mutexes to avoid race with clearMetrics
290+ s .countersSliceMux .Lock ()
291+ var countersCopy []* counter
292+ if s .countersSlice != nil {
293+ countersCopy = make ([]* counter , len (s .countersSlice ))
294+ copy (countersCopy , s .countersSlice )
295+ }
296+ s .countersSliceMux .Unlock ()
268297
269- s .gauges .Range (func (key , value interface {}) bool {
270- g := value .(* gauge )
271- g .cachedReport ()
272- return true
273- })
298+ s .gaugesSliceMux .Lock ()
299+ var gaugesCopy []* gauge
300+ if s .gaugesSlice != nil {
301+ gaugesCopy = make ([]* gauge , len (s .gaugesSlice ))
302+ copy (gaugesCopy , s .gaugesSlice )
303+ }
304+ s .gaugesSliceMux .Unlock ()
305+
306+ s .histogramsSliceMux .Lock ()
307+ var histogramsCopy []* histogram
308+ if s .histogramsSlice != nil {
309+ histogramsCopy = make ([]* histogram , len (s .histogramsSlice ))
310+ copy (histogramsCopy , s .histogramsSlice )
311+ }
312+ s .histogramsSliceMux .Unlock ()
313+
314+ // Now iterate over the copies without holding any mutexes
315+ for _ , c := range countersCopy {
316+ if c != nil {
317+ c .cachedReport ()
318+ }
319+ }
320+
321+ for _ , g := range gaugesCopy {
322+ if g != nil {
323+ g .cachedReport ()
324+ }
325+ }
274326
275327 // We do nothing for timers here because timers report directly to the StatsReporter without buffering
276328
277- s . histograms . Range ( func ( key , value interface {}) bool {
278- h := value .( * histogram )
279- h .cachedReport ()
280- return true
281- })
329+ for _ , h := range histogramsCopy {
330+ if h != nil {
331+ h .cachedReport ()
332+ }
333+ }
282334}
283335
284336// reportLoop is used by the root scope for periodic reporting
@@ -333,7 +385,7 @@ func (s *scope) Counter(name string) Counter {
333385 )
334386 }
335387
336- c := newCounter (cachedCounter )
388+ c := newCounter (s . fullyQualifiedName ( name ), cachedCounter )
337389
338390 // Attempt to store it, or get the existing one if another goroutine created it first
339391 actual , loaded := s .counters .LoadOrStore (name , c )
@@ -380,7 +432,7 @@ func (s *scope) Gauge(name string) Gauge {
380432 )
381433 }
382434
383- g := newGauge (cachedGauge )
435+ g := newGauge (s . fullyQualifiedName ( name ), cachedGauge )
384436
385437 // Attempt to store it, or get the existing one if another goroutine created it first
386438 actual , loaded := s .gauges .LoadOrStore (name , g )
@@ -691,24 +743,27 @@ func (s *scope) Close() error {
691743}
692744
693745func (s * scope ) clearMetrics () {
694- s .clearMux .Lock ()
695- defer s .clearMux .Unlock ()
696-
697746 var numCounters , numGauges , numHistograms int64
698747
699748 s .counters .Range (func (key , value interface {}) bool {
700749 numCounters ++
701750 s .counters .Delete (key )
702751 return true
703752 })
753+
754+ s .countersSliceMux .Lock ()
704755 s .countersSlice = nil
756+ s .countersSliceMux .Unlock ()
705757
706758 s .gauges .Range (func (key , value interface {}) bool {
707759 numGauges ++
708760 s .gauges .Delete (key )
709761 return true
710762 })
763+
764+ s .gaugesSliceMux .Lock ()
711765 s .gaugesSlice = nil
766+ s .gaugesSliceMux .Unlock ()
712767
713768 s .timers .Range (func (key , value interface {}) bool {
714769 s .timers .Delete (key )
@@ -720,7 +775,10 @@ func (s *scope) clearMetrics() {
720775 s .histograms .Delete (key )
721776 return true
722777 })
778+
779+ s .histogramsSliceMux .Lock ()
723780 s .histogramsSlice = nil
781+ s .histogramsSliceMux .Unlock ()
724782
725783 // Atomically decrement the cardinality counters in the registry.
726784 if s .registry != nil {
0 commit comments