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

Code before:
// flush sends notifications for all new alerts.
func (ag *aggrGroup) flush(notify func(...*types.Alert) bool) {
	if ag.empty() {
		return
	}

	var (
		alerts      = ag.alerts.List()
		alertsSlice = make(types.AlertSlice, 0, len(alerts))
		now         = time.Now()
	)
	for _, alert := range alerts {
		a := *alert
		// Ensure that alerts don't resolve as time move forwards.
		if !a.ResolvedAt(now) {
			a.EndsAt = time.Time{}
		}
		alertsSlice = append(alertsSlice, &a)
	}
	sort.Stable(alertsSlice)

	level.Debug(ag.logger).Log("msg", "flushing", "alerts", fmt.Sprintf("%v", alertsSlice))

	if notify(alertsSlice...) {
		for _, a := range alertsSlice {
			// Only delete if the fingerprint has not been inserted again since we notified about it.
			if err := ag.alerts.DeleteIf(a.Fingerprint(), func(b *types.Alert) bool {
				return a.Resolved() && a.UpdatedAt == b.UpdatedAt
			}); err != nil {
				level.Error(ag.logger).Log("msg", "error on delete alert", "err", err, "alert", a.String())
			}
		}
	}
}

type nilLimits struct{}

func (n nilLimits) MaxNumberOfAggregationGroups() int { return 0 }


Review comment (bug):
[nit] How do you feel about making the whole operation atomic? You'd only take the lock once guaranteed that all the alerts are cleared in one go. I feel this makes it easier to reason about it as there's no use case where an alert could get updated once we start checking. 

```git
diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go
index d78fbf68..6e44fbc2 100644
--- a/dispatch/dispatch.go
+++ b/dispatch/dispatch.go
@@ -516,14 +516,7 @@ func (ag *aggrGroup) flush(notify func(...*types.Alert) bool) {
 	level.Debug(ag.logger).Log("msg", "flushing", "alerts", fmt.Sprintf("%v", alertsSlice))
 
 	if notify(alertsSlice...) {
-		for _, a := range alertsSlice {
-			// Only delete if the fingerprint has not been inserted again since we notified about it.
-			if err := ag.alerts.DeleteIf(a.Fingerprint(), func(b *types.Alert) bool {
-				return a.Resolved() && a.UpdatedAt == b.UpdatedAt
-			}); err != nil {
-				level.Error(ag.logger).Log("msg", "error on delete alert", "err", err, "alert", a.String())
-			}
-		}
+		ag.alerts.DeleteResolved(alertsSlice)
 	}
 }
 
diff --git a/store/store.go b/store/store.go
index bffa4eb6..4110a971 100644
--- a/store/store.go
+++ b/store/store.go
@@ -154,3 +154,19 @@ func (a *Alerts) Empty() bool {
 
 	return len(a.c) == 0
 }
+
+func (a *Alerts) DeleteResolved(slice types.AlertSlice) {
+	a.Lock()
+	defer a.Unlock()
+
+	for _, as := range slice {
+		fp := as.Fingerprint()
+		alert, ok := a.c[as.Fingerprint()]
+		if !ok {
+		}
+
+		if as.Resolved() && as.UpdatedAt == alert.UpdatedAt {
+			delete(a.c, fp)
+		}
+	}
+}

```

Code after:
// flush sends notifications for all new alerts.
func (ag *aggrGroup) flush(notify func(...*types.Alert) bool) {
	if ag.empty() {
		return
	}

	var (
		alerts        = ag.alerts.List()
		alertsSlice   = make(types.AlertSlice, 0, len(alerts))
		resolvedSlice = make(types.AlertSlice, 0, len(alerts))
		now           = time.Now()
	)
	for _, alert := range alerts {
		a := *alert
		// Ensure that alerts don't resolve as time move forwards.
		if a.ResolvedAt(now) {
			resolvedSlice = append(resolvedSlice, &a)
		} else {
			a.EndsAt = time.Time{}
		}
		alertsSlice = append(alertsSlice, &a)
	}
	sort.Stable(alertsSlice)

	level.Debug(ag.logger).Log("msg", "flushing", "alerts", fmt.Sprintf("%v", alertsSlice))

	if notify(alertsSlice...) {
		// Delete all resolved alerts as we just sent a notification for them,
		// and we don't want to send another one. However, we need to make sure
		// that each resolved alert has not fired again during the flush as then
		// we would delete an active alert thinking it was resolved.
		if err := ag.alerts.DeleteIfNotModified(resolvedSlice); err != nil {
			level.Error(ag.logger).Log("msg", "error on delete alerts", "err", err)
		}
	}
}

type nilLimits struct{}

func (n nilLimits) MaxNumberOfAggregationGroups() int { return 0 }