Repository: prometheus/alertmanager
File: dispatch/dispatch.go (Go)

Code before:
	Alerts   types.AlertSlice
	Labels   model.LabelSet
	Receiver string
	GroupKey string
	RouteID  string
}

type AlertGroups []*AlertGroup

func (ag AlertGroups) Swap(i, j int) { ag[i], ag[j] = ag[j], ag[i] }
func (ag AlertGroups) Less(i, j int) bool {
	if ag[i].Labels.Equal(ag[j].Labels) {
		return ag[i].Receiver < ag[j].Receiver
	}
	return ag[i].Labels.Before(ag[j].Labels)
}
func (ag AlertGroups) Len() int { return len(ag) }

// Groups returns a slice of AlertGroups from the dispatcher's internal state.
func (d *Dispatcher) Groups(routeFilter func(*Route) bool, alertFilter func(*types.Alert, time.Time) bool) (AlertGroups, map[model.Fingerprint][]string) {
	groups := AlertGroups{}

	d.mtx.RLock()
	aggrGroupsPerRoute := map[*Route]map[model.Fingerprint]*aggrGroup{}
	for route, ags := range d.aggrGroupsPerRoute {
		copiedMap := map[model.Fingerprint]*aggrGroup{}
		for fp, ag := range ags {
			copiedMap[fp] = ag
		}
		aggrGroupsPerRoute[route] = copiedMap
	}
	d.mtx.RUnlock()

	// Keep a list of receivers for an alert to prevent checking each alert
	// again against all routes. The alert has already matched against this
	// route on ingestion.
	receivers := map[model.Fingerprint][]string{}

	now := time.Now()
	for route, ags := range aggrGroupsPerRoute {
		if !routeFilter(route) {
			continue
		}

		for _, ag := range ags {
			receiver := route.RouteOpts.Receiver
			alertGroup := &AlertGroup{
				Labels:   ag.labels,
				Receiver: receiver,
				GroupKey: ag.GroupKey(),
				RouteID:  ag.routeID,

Review comment (suggestion):
I think since aggrGroup{} is a pointer, if the underlying aggr changes after copy, could it potentially lead to unintended behavior? I am wondering if we need to add more concurrent tests to ensure correctness. 

Code after:
type AlertGroup struct {
	Alerts   types.AlertSlice
	Labels   model.LabelSet
	Receiver string
	GroupKey string
	RouteID  string
}

type AlertGroups []*AlertGroup

func (ag AlertGroups) Swap(i, j int) { ag[i], ag[j] = ag[j], ag[i] }
func (ag AlertGroups) Less(i, j int) bool {
	if ag[i].Labels.Equal(ag[j].Labels) {
		return ag[i].Receiver < ag[j].Receiver
	}
	return ag[i].Labels.Before(ag[j].Labels)
}
func (ag AlertGroups) Len() int { return len(ag) }

// Groups returns a slice of AlertGroups from the dispatcher's internal state.
func (d *Dispatcher) Groups(routeFilter func(*Route) bool, alertFilter func(*types.Alert, time.Time) bool) (AlertGroups, map[model.Fingerprint][]string) {
	groups := AlertGroups{}

	// Make a snapshot of the aggrGroupsPerRoute map to use for this function.
	// This ensures that we hold the Dispatcher.mtx for as little time as
	// possible.
	// It also prevents us from holding the any locks in alertFilter or routeFilter
	// while we hold the dispatcher lock
	d.mtx.RLock()
	aggrGroupsPerRoute := map[*Route]map[model.Fingerprint]*aggrGroup{}
	for route, ags := range d.aggrGroupsPerRoute {
		// Since other goroutines could modify d.aggrGroupsPerRoute, we need to
		// copy it. We DON'T need to copy the aggrGroup objects because they each
		// have a mutex protecting their internal state.
		// The aggrGroup methods use the internal lock. It is important to avoid
		// accessing internal fields on the aggrGroup objects.
		aggrGroupsPerRoute[route] = maps.Clone(ags)
	}
	d.mtx.RUnlock()

	// Keep a list of receivers for an alert to prevent checking each alert
	// again against all routes. The alert has already matched against this
	// route on ingestion.
	receivers := map[model.Fingerprint][]string{}

	now := time.Now()
	for route, ags := range aggrGroupsPerRoute {
		if !routeFilter(route) {
			continue
		}