Skip to content

Commit 3706f3d

Browse files
authored
FFM-11873 Lock for entirety of safeMetricsRequestMap.get() (#356)
* FFM-11873 Lock for entirety of safeMetricsRequestMap.get() **What** - Performs a Read Lock for the entirety of `safeMetricsRequestMap.get()` **Why** - I saw an error log in QA saying `"fatal error: concurrent map iteration and map write"` and it pointed at this code. It turns out the copy that we were doing to reduce the amount of time the lock was held was a shallow copy of the maps reference and not a deep copy of its contents. **Testing** - Benchmarked performing a deep copy vs locking for the entirety of `get()`
1 parent 9efd567 commit 3706f3d

File tree

2 files changed

+112
-3
lines changed

2 files changed

+112
-3
lines changed

clients/metrics_service/safe_metrics_request_map.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,11 @@ func (s *safeMetricsRequestMap) get() map[string]domain.MetricsRequest {
9595
// Take a copy an unlock so we don't block other thread
9696
// marshaling to the response type
9797
s.RLock()
98-
cpy := s.detailed
99-
s.RUnlock()
98+
defer s.RUnlock()
10099

101100
result := map[string]domain.MetricsRequest{}
102101

103-
for envID, detailedMap := range cpy {
102+
for envID, detailedMap := range s.detailed {
104103

105104
slice := []clientgen.MetricsData{}
106105
for _, v := range detailedMap {

clients/metrics_service/safe_metrics_request_map_test.go

+110
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,113 @@ func Test_SafeMetricsRequestMap(t *testing.T) {
565565
})
566566
}
567567
}
568+
569+
func BenchmarkSafeMetricsRequestMapGetMethods(b *testing.B) {
570+
// Initialize your safeMetricsRequestMap with some data
571+
smr := newSafeMetricsRequestMap()
572+
573+
// Populate smr with test data
574+
populateTestData(smr) // Assuming you have a function to populate test data
575+
576+
// Table-driven benchmarks
577+
benchmarks := []struct {
578+
name string
579+
fn func() map[string]domain.MetricsRequest
580+
}{
581+
{name: "get", fn: smr.get},
582+
{name: "getNew", fn: smr.getDeepCopyBenchmarkOnly},
583+
}
584+
585+
for _, bm := range benchmarks {
586+
// Run the benchmark
587+
b.Run(bm.name, func(b *testing.B) {
588+
b.ReportAllocs()
589+
for i := 0; i < b.N; i++ {
590+
_ = bm.fn() // Call the function and discard the result
591+
}
592+
})
593+
}
594+
}
595+
596+
// populateTestData is a helper function to add test data to the map
597+
func populateTestData(smr *safeMetricsRequestMap) {
598+
// Populate the map with sample data for benchmarking
599+
// This function should create a reasonably sized map to reflect a typical workload
600+
// Example:
601+
smr.add(domain.MetricsRequest{
602+
Size: 2,
603+
EnvironmentID: "env1",
604+
Metrics: clientgen.Metrics{
605+
MetricsData: domain.ToPtr([]clientgen.MetricsData{
606+
{Count: 3, Attributes: []clientgen.KeyValue{
607+
{
608+
Key: "attr1",
609+
Value: "attr1",
610+
},
611+
}},
612+
{Count: 4, Attributes: []clientgen.KeyValue{
613+
{
614+
Key: "attr2",
615+
Value: "attr2",
616+
},
617+
}},
618+
}),
619+
},
620+
})
621+
622+
smr.add(domain.MetricsRequest{
623+
Size: 2,
624+
EnvironmentID: "env2",
625+
Metrics: clientgen.Metrics{
626+
MetricsData: domain.ToPtr([]clientgen.MetricsData{
627+
{Count: 3, Attributes: []clientgen.KeyValue{
628+
{
629+
Key: "attr3",
630+
Value: "attr3",
631+
},
632+
}},
633+
{Count: 4, Attributes: []clientgen.KeyValue{
634+
{
635+
Key: "attr4",
636+
Value: "attr4",
637+
},
638+
}},
639+
}),
640+
},
641+
})
642+
// Add more test data as needed
643+
}
644+
645+
func (s *safeMetricsRequestMap) getDeepCopyBenchmarkOnly() map[string]domain.MetricsRequest {
646+
s.RLock()
647+
// Deep copy of the outer map
648+
cpy := make(map[string]map[string]clientgen.MetricsData, len(s.detailed))
649+
650+
for envID, detailedMap := range s.detailed {
651+
// Deep copy of the nested map
652+
nestedCopy := make(map[string]clientgen.MetricsData, len(detailedMap))
653+
for key, value := range detailedMap {
654+
nestedCopy[key] = value
655+
}
656+
cpy[envID] = nestedCopy
657+
}
658+
s.RUnlock()
659+
660+
result := make(map[string]domain.MetricsRequest, len(cpy))
661+
662+
for envID, detailedMap := range cpy {
663+
slice := make([]clientgen.MetricsData, 0, len(detailedMap))
664+
for _, v := range detailedMap {
665+
slice = append(slice, v)
666+
}
667+
668+
result[envID] = domain.MetricsRequest{
669+
EnvironmentID: envID,
670+
Metrics: clientgen.Metrics{
671+
MetricsData: domain.ToPtr(slice),
672+
},
673+
}
674+
}
675+
676+
return result
677+
}

0 commit comments

Comments
 (0)