ronantakizawa/codereview-bench huggingface dataset:
Repository: prometheus/alertmanager
File: provider/mem/mem.go (Go)

Code before:
// limitations under the License.

package mem

import (
	"context"
	"sync"
	"time"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"

	"github.com/prometheus/alertmanager/provider"
	"github.com/prometheus/alertmanager/store"
	"github.com/prometheus/alertmanager/types"
)

const alertChannelLength = 200

// Alerts gives access to a set of alerts. All methods are goroutine-safe.
type Alerts struct {
	cancel context.CancelFunc

	mtx sync.RWMutex

	alerts *store.Alerts
	marker types.AlertMarker

	listeners map[int]listeningAlerts
	next      int

	callback AlertStoreCallback

	logger log.Logger
}

type AlertStoreCallback interface {
	// PreStore is called before alert is stored into the store. If this method returns error,
	// alert is not stored.
	// Existing flag indicates whether alert has existed before (and is only updated) or not.
	// If alert has existed before, then alert passed to PreStore is result of merging existing alert with new alert.
	PreStore(alert *types.Alert, existing bool) error

	// PostStore is called after alert has been put into store.
	PostStore(alert *types.Alert, existing bool)

	// PostDelete is called after alert has been removed from the store due to alert garbage collection.
	PostDelete(alert *types.Alert)
}

Review comment (bug):
Let's keep this as a `sync.Mutex` so we can keep the PR focused on fixing the race condition. I think this change is a bit of a distraction because you also need to change the mutex in `store.go` from `sync.Mutex` to `sync.RWMutex` for this to have an effect.

Code after:
// limitations under the License.

package mem

import (
	"context"
	"sync"
	"time"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"

	"github.com/prometheus/alertmanager/provider"
	"github.com/prometheus/alertmanager/store"
	"github.com/prometheus/alertmanager/types"
)

const alertChannelLength = 200

// Alerts gives access to a set of alerts. All methods are goroutine-safe.
type Alerts struct {
	cancel context.CancelFunc

	mtx sync.Mutex

	alerts *store.Alerts
	marker types.AlertMarker

	listeners map[int]listeningAlerts
	next      int

	callback AlertStoreCallback

	logger log.Logger
}

type AlertStoreCallback interface {
	// PreStore is called before alert is stored into the store. If this method returns error,
	// alert is not stored.
	// Existing flag indicates whether alert has existed before (and is only updated) or not.
	// If alert has existed before, then alert passed to PreStore is result of merging existing alert with new alert.
	PreStore(alert *types.Alert, existing bool) error

	// PostStore is called after alert has been put into store.
	PostStore(alert *types.Alert, existing bool)

	// PostDelete is called after alert has been removed from the store due to alert garbage collection.
	PostDelete(alert *types.Alert)
}
Repository: prometheus/alertmanager
File: provider/mem/mem.go (Go)

Code before:
	var (
		done   = make(chan struct{})
		alerts = a.alerts.List()
		ch     = make(chan *types.Alert, max(len(alerts), alertChannelLength))
	)

	for _, a := range alerts {
		ch <- a
	}

	a.listeners[a.next] = listeningAlerts{alerts: ch, done: done}
	a.next++

	return provider.NewAlertIterator(ch, done, nil)
}

// GetPending returns an iterator over all the alerts that have
// pending notifications.
func (a *Alerts) GetPending() provider.AlertIterator {
	var (
		ch   = make(chan *types.Alert, alertChannelLength)
		done = make(chan struct{})
	)
	a.mtx.RLock()
	defer a.mtx.RUnlock()
	alerts := a.alerts.List()

	go func() {
		defer close(ch)
		for _, a := range alerts {
			select {
			case ch <- a:
			case <-done:
				return
			}
		}
	}()

	return provider.NewAlertIterator(ch, done, nil)
}

// Get returns the alert for a given fingerprint.
func (a *Alerts) Get(fp model.Fingerprint) (*types.Alert, error) {
	a.mtx.RLock()
	defer a.mtx.RUnlock()
	return a.alerts.Get(fp)
}

// Put adds the given alert to the set.
func (a *Alerts) Put(alerts ...*types.Alert) error {
	a.mtx.Lock()

Review comment (suggestion):
I think the important change here is that moving the alerts out of the goroutine means `alerts` contains the set of alerts at the time `Pending()` was called, not the set of alerts at the time the goroutine was scheduled. This can be different if a `Put` or GC operation interleaves a call to `Pending()` and the start of the goroutine.

Code after:
	var (
		done   = make(chan struct{})
		alerts = a.alerts.List()
		ch     = make(chan *types.Alert, max(len(alerts), alertChannelLength))
	)

	for _, a := range alerts {
		ch <- a
	}

	a.listeners[a.next] = listeningAlerts{alerts: ch, done: done}
	a.next++

	return provider.NewAlertIterator(ch, done, nil)
}

// GetPending returns an iterator over all the alerts that have
// pending notifications.
func (a *Alerts) GetPending() provider.AlertIterator {
	var (
		ch   = make(chan *types.Alert, alertChannelLength)
		done = make(chan struct{})
	)
	a.mtx.Lock()
	defer a.mtx.Unlock()
	alerts := a.alerts.List()

	go func() {
		defer close(ch)
		for _, a := range alerts {
			select {
			case ch <- a:
			case <-done:
				return
			}
		}
	}()

	return provider.NewAlertIterator(ch, done, nil)
}

// Get returns the alert for a given fingerprint.
func (a *Alerts) Get(fp model.Fingerprint) (*types.Alert, error) {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	return a.alerts.Get(fp)
}

// Put adds the given alert to the set.
func (a *Alerts) Put(alerts ...*types.Alert) error {
	a.mtx.Lock()
Repository: prometheus/alertmanager
File: provider/mem/mem.go (Go)

Code before:
		}
		a.mtx.Unlock()
	})

	if r != nil {
		a.registerMetrics(r)
	}

	go a.alerts.Run(ctx, intervalGC)

	return a, nil
}

// Close the alert provider.
func (a *Alerts) Close() {
	if a.cancel != nil {
		a.cancel()
	}
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// Subscribe returns an iterator over active alerts that have not been
// resolved and successfully notified about.
// They are not guaranteed to be in chronological order.
func (a *Alerts) Subscribe() provider.AlertIterator {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	var (
		done   = make(chan struct{})
		alerts = a.alerts.List()
		ch     = make(chan *types.Alert, max(len(alerts), alertChannelLength))
	)

	for _, a := range alerts {
		ch <- a
	}

	a.listeners[a.next] = listeningAlerts{alerts: ch, done: done}
	a.next++

	return provider.NewAlertIterator(ch, done, nil)
}

// GetPending returns an iterator over all the alerts that have
// pending notifications.

Review comment (suggestion):
```suggestion
			// Listener is not closed yet, hence proceed.
```
You can address it in the next PR.

Code after:
		case <-t.C:
			a.gc()
		}
	}
}

func (a *Alerts) gc() {
	a.mtx.Lock()
	defer a.mtx.Unlock()

	deleted := a.alerts.GC()
	for _, alert := range deleted {
		// As we don't persist alerts, we no longer consider them after
		// they are resolved. Alerts waiting for resolved notifications are
		// held in memory in aggregation groups redundantly.
		a.marker.Delete(alert.Fingerprint())
		a.callback.PostDelete(&alert)
	}

	for i, l := range a.listeners {
		select {
		case <-l.done:
			delete(a.listeners, i)
			close(l.alerts)
		default:
			// listener is not closed yet, hence proceed.
		}
	}
}

// Close the alert provider.
func (a *Alerts) Close() {
	if a.cancel != nil {
		a.cancel()
	}
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// Subscribe returns an iterator over active alerts that have not been
// resolved and successfully notified about.
// They are not guaranteed to be in chronological order.
func (a *Alerts) Subscribe() provider.AlertIterator {
	a.mtx.Lock()
	defer a.mtx.Unlock()
	var (
Repository: prometheus/alertmanager
File: timeinterval/timeinterval.go (Go)

Code before:
// limitations under the License.

package timeinterval

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"gopkg.in/yaml.v2"
)

// Intervener determines whether a given time and active route time interval should mute outgoing notifications.
// It implements the TimeMuter interface.
type Intervener struct {
	intervals map[string][]TimeInterval
}

func (i *Intervener) Mutes(names []string, now time.Time) (bool, []string, error) {
	var in []string
	for _, name := range names {
		interval, ok := i.intervals[name]
		if !ok {
			return false, nil, fmt.Errorf("time interval %s doesn't exist in config", name)
		}

		for _, ti := range interval {
			if ti.ContainsTime(now.UTC()) {
				in = append(in, name)
			}
		}
	}

	return len(in) > 0, in, nil
}

func NewIntervener(ti map[string][]TimeInterval) *Intervener {
	return &Intervener{
		intervals: ti,
	}
}

// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained
// within the interval.
type TimeInterval struct {

Review comment (suggestion):
```suggestion
// Mutes implements the TimeMuter interface.
func (i *Intervener) Mutes(names []string, now time.Time) (bool, []string, error) {
```

Code after:
// limitations under the License.

package timeinterval

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"gopkg.in/yaml.v2"
)

// Intervener determines whether a given time and active route time interval should mute outgoing notifications.
// It implements the TimeMuter interface.
type Intervener struct {
	intervals map[string][]TimeInterval
}

// Mutes implements the TimeMuter interface.
func (i *Intervener) Mutes(names []string, now time.Time) (bool, []string, error) {
	var in []string
	for _, name := range names {
		interval, ok := i.intervals[name]
		if !ok {
			return false, nil, fmt.Errorf("time interval %s doesn't exist in config", name)
		}

		for _, ti := range interval {
			if ti.ContainsTime(now.UTC()) {
				in = append(in, name)
			}
		}
	}

	return len(in) > 0, in, nil
}

func NewIntervener(ti map[string][]TimeInterval) *Intervener {
	return &Intervener{
		intervals: ti,
	}
}

// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained
// within the interval.
Repository: prometheus/alertmanager
File: dispatch/dispatch.go (Go)

Code before:
				}
				return
			}

			level.Debug(d.logger).Log("msg", "Received alert", "alert", alert)

			// Log errors but keep trying.
			if err := it.Err(); err != nil {
				level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				continue
			}

			now := time.Now()
			for _, r := range d.route.Match(alert.Labels) {
				d.processAlert(alert, r)
			}
			d.metrics.processingDuration.Observe(time.Since(now).Seconds())

		case <-cleanup.C:
			d.mtx.Lock()

			for _, groups := range d.aggrGroupsPerRoute {
				for _, ag := range groups {
					if ag.empty() {
						ag.stop()
						d.marker.DeleteByGroupKey(ag.GroupKey())
						delete(groups, ag.fingerprint())
						d.aggrGroupsNum--
						d.metrics.aggrGroups.Dec()
					}
				}
			}

			d.mtx.Unlock()

		case <-d.ctx.Done():
			return
		}
	}
}

// AlertGroup represents how alerts exist within an aggrGroup.
type AlertGroup struct {
	Alerts   types.AlertSlice
	Labels   model.LabelSet
	Receiver string
}

type AlertGroups []*AlertGroup

func (ag AlertGroups) Swap(i, j int) { ag[i], ag[j] = ag[j], ag[i] }

Review comment (suggestion):
We need to delete the marker when the aggregation group is deleted. There is a similar mechanism in `provider/mem/mem.go`: https://github.com/prometheus/alertmanager/blob/14cbe6301c732658d6fe877ec55ad5b738abcf06/provider/mem/mem.go#L108

Code after:
				}
				return
			}

			level.Debug(d.logger).Log("msg", "Received alert", "alert", alert)

			// Log errors but keep trying.
			if err := it.Err(); err != nil {
				level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				continue
			}

			now := time.Now()
			for _, r := range d.route.Match(alert.Labels) {
				d.processAlert(alert, r)
			}
			d.metrics.processingDuration.Observe(time.Since(now).Seconds())

		case <-maintenance.C:
			d.doMaintenance()
		case <-d.ctx.Done():
			return
		}
	}
}

func (d *Dispatcher) doMaintenance() {
	d.mtx.Lock()
	defer d.mtx.Unlock()
	for _, groups := range d.aggrGroupsPerRoute {
		for _, ag := range groups {
			if ag.empty() {
				ag.stop()
				d.marker.DeleteByGroupKey(ag.routeID, ag.GroupKey())
				delete(groups, ag.fingerprint())
				d.aggrGroupsNum--
				d.metrics.aggrGroups.Dec()
			}
		}
	}
}

// AlertGroup represents how alerts exist within an aggrGroup.
type AlertGroup struct {
	Alerts   types.AlertSlice
	Labels   model.LabelSet
	Receiver string
}

type AlertGroups []*AlertGroup
Repository: prometheus/alertmanager
File: dispatch/dispatch.go (Go)

Code before:
	defer it.Close()

	for {
		select {
		case alert, ok := <-it.Next():
			if !ok {
				// Iterator exhausted for some reason.
				if err := it.Err(); err != nil {
					level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				}
				return
			}

			level.Debug(d.logger).Log("msg", "Received alert", "alert", alert)

			// Log errors but keep trying.
			if err := it.Err(); err != nil {
				level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				continue
			}

			now := time.Now()
			for _, r := range d.route.Match(alert.Labels) {
				d.processAlert(alert, r)
			}
			d.metrics.processingDuration.Observe(time.Since(now).Seconds())

		case <-maintenance.C:
			d.doMaintenance()
		case <-d.ctx.Done():
			return
		}
	}
}

func (d *Dispatcher) doMaintenance() {
	d.mtx.Lock()
	for _, groups := range d.aggrGroupsPerRoute {
		for _, ag := range groups {
			if ag.empty() {
				ag.stop()
				d.marker.DeleteByGroupKey(ag.routeID, ag.GroupKey())
				delete(groups, ag.fingerprint())
				d.aggrGroupsNum--
				d.metrics.aggrGroups.Dec()
			}
		}
	}
	d.mtx.Unlock()
}


Review comment (suggestion):
I moved this logic to a method called `doMaintenance`. The main purpose of this was being able to test it.

Code after:
	defer it.Close()

	for {
		select {
		case alert, ok := <-it.Next():
			if !ok {
				// Iterator exhausted for some reason.
				if err := it.Err(); err != nil {
					level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				}
				return
			}

			level.Debug(d.logger).Log("msg", "Received alert", "alert", alert)

			// Log errors but keep trying.
			if err := it.Err(); err != nil {
				level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				continue
			}

			now := time.Now()
			for _, r := range d.route.Match(alert.Labels) {
				d.processAlert(alert, r)
			}
			d.metrics.processingDuration.Observe(time.Since(now).Seconds())

		case <-maintenance.C:
			d.doMaintenance()
		case <-d.ctx.Done():
			return
		}
	}
}

func (d *Dispatcher) doMaintenance() {
	d.mtx.Lock()
	defer d.mtx.Unlock()
	for _, groups := range d.aggrGroupsPerRoute {
		for _, ag := range groups {
			if ag.empty() {
				ag.stop()
				d.marker.DeleteByGroupKey(ag.routeID, ag.GroupKey())
				delete(groups, ag.fingerprint())
				d.aggrGroupsNum--
				d.metrics.aggrGroups.Dec()
			}
		}
	}
}
Repository: prometheus/alertmanager
File: dispatch/dispatch.go (Go)

Code before:
			}

			level.Debug(d.logger).Log("msg", "Received alert", "alert", alert)

			// Log errors but keep trying.
			if err := it.Err(); err != nil {
				level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				continue
			}

			now := time.Now()
			for _, r := range d.route.Match(alert.Labels) {
				d.processAlert(alert, r)
			}
			d.metrics.processingDuration.Observe(time.Since(now).Seconds())

		case <-maintenance.C:
			d.doMaintenance()
		case <-d.ctx.Done():
			return
		}
	}
}

func (d *Dispatcher) doMaintenance() {
	d.mtx.Lock()
	for _, groups := range d.aggrGroupsPerRoute {
		for _, ag := range groups {
			if ag.empty() {
				ag.stop()
				d.marker.DeleteByGroupKey(ag.routeID, ag.GroupKey())
				delete(groups, ag.fingerprint())
				d.aggrGroupsNum--
				d.metrics.aggrGroups.Dec()
			}
		}
	}
	d.mtx.Unlock()
}

// AlertGroup represents how alerts exist within an aggrGroup.
type AlertGroup struct {
	Alerts   types.AlertSlice
	Labels   model.LabelSet
	Receiver 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 {

Review comment (suggestion):
nit: we tend to have this prefernece in the AM - it made sense before to have them split but not now.

```suggestion
	d.mtx.Lock()
	defer d.mtx.Unlock()
```

Code after:
			}

			level.Debug(d.logger).Log("msg", "Received alert", "alert", alert)

			// Log errors but keep trying.
			if err := it.Err(); err != nil {
				level.Error(d.logger).Log("msg", "Error on alert update", "err", err)
				continue
			}

			now := time.Now()
			for _, r := range d.route.Match(alert.Labels) {
				d.processAlert(alert, r)
			}
			d.metrics.processingDuration.Observe(time.Since(now).Seconds())

		case <-maintenance.C:
			d.doMaintenance()
		case <-d.ctx.Done():
			return
		}
	}
}

func (d *Dispatcher) doMaintenance() {
	d.mtx.Lock()
	defer d.mtx.Unlock()
	for _, groups := range d.aggrGroupsPerRoute {
		for _, ag := range groups {
			if ag.empty() {
				ag.stop()
				d.marker.DeleteByGroupKey(ag.routeID, ag.GroupKey())
				delete(groups, ag.fingerprint())
				d.aggrGroupsNum--
				d.metrics.aggrGroups.Dec()
			}
		}
	}
}

// AlertGroup represents how alerts exist within an aggrGroup.
type AlertGroup struct {
	Alerts   types.AlertSlice
	Labels   model.LabelSet
	Receiver 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 {
Repository: prometheus/alertmanager
File: api/v2/api.go (Go)

Code before:
	// resolveTimeout represents the default resolve timeout that an alert is
	// assigned if no end time is specified.
	alertmanagerConfig *config.Config
	route              *dispatch.Route
	setAlertStatus     setAlertStatusFn

	logger log.Logger
	m      *metrics.Alerts

	Handler http.Handler
}

type (
	groupsFn         func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string)
	getAlertStatusFn func(prometheus_model.Fingerprint) types.AlertStatus
	setAlertStatusFn func(prometheus_model.LabelSet)
)

// NewAPI returns a new Alertmanager API v2.
func NewAPI(
	alerts provider.Alerts,
	gf groupsFn,
	sf getAlertStatusFn,
	silences *silence.Silences,
	peer cluster.ClusterPeer,
	l log.Logger,
	r prometheus.Registerer,
) (*API, error) {
	api := API{
		alerts:         alerts,
		getAlertStatus: sf,
		alertGroups:    gf,
		peer:           peer,
		silences:       silences,
		logger:         l,
		m:              metrics.NewAlerts(r),
		uptime:         time.Now(),
	}

	// Load embedded swagger file.
	swaggerSpec, swaggerSpecAnalysis, err := getSwaggerSpec()
	if err != nil {
		return nil, err
	}

	// Create new service API.
	openAPI := operations.NewAlertmanagerAPI(swaggerSpec)

	// Skip the  redoc middleware, only serving the OpenAPI specification and
	// the API itself via RoutesHandler. See:
	// https://github.com/go-swagger/go-swagger/issues/1779

Review comment (suggestion):
How about this for consistent naming:

```suggestion
	asf alertStatusFn,
	gmf groupMutedFn,
```

Code after:
	mtx sync.RWMutex
	// resolveTimeout represents the default resolve timeout that an alert is
	// assigned if no end time is specified.
	alertmanagerConfig *config.Config
	route              *dispatch.Route
	setAlertStatus     setAlertStatusFn

	logger log.Logger
	m      *metrics.Alerts

	Handler http.Handler
}

type (
	groupsFn         func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string)
	groupMutedFunc   func(routeID, groupKey string) ([]string, bool)
	getAlertStatusFn func(prometheus_model.Fingerprint) types.AlertStatus
	setAlertStatusFn func(prometheus_model.LabelSet)
)

// NewAPI returns a new Alertmanager API v2.
func NewAPI(
	alerts provider.Alerts,
	gf groupsFn,
	asf getAlertStatusFn,
	gmf groupMutedFunc,
	silences *silence.Silences,
	peer cluster.ClusterPeer,
	l log.Logger,
	r prometheus.Registerer,
) (*API, error) {
	api := API{
		alerts:         alerts,
		getAlertStatus: asf,
		alertGroups:    gf,
		groupMutedFunc: gmf,
		peer:           peer,
		silences:       silences,
		logger:         l,
		m:              metrics.NewAlerts(r),
		uptime:         time.Now(),
	}

	// Load embedded swagger file.
	swaggerSpec, swaggerSpecAnalysis, err := getSwaggerSpec()
	if err != nil {
		return nil, err
	}

	// Create new service API.
	openAPI := operations.NewAlertmanagerAPI(swaggerSpec)
Repository: prometheus/alertmanager
File: store/store.go (Go)

Code before:

	alert, prs := a.c[fp]
	if !prs {
		return nil, ErrNotFound
	}
	return alert, nil
}

// Set unconditionally sets the alert in memory.
func (a *Alerts) Set(alert *types.Alert) error {
	a.Lock()
	defer a.Unlock()

	a.c[alert.Fingerprint()] = alert
	return nil
}

// Delete removes the Alert with the matching fingerprint from the store.
func (a *Alerts) Delete(fp model.Fingerprint) error {
	a.Lock()
	defer a.Unlock()

	delete(a.c, fp)
	return nil
}

// DeleteIf removes the Alert with the matching fingerprint from the store
// if fn returns true.
func (a *Alerts) DeleteIf(fp model.Fingerprint, fn func(*types.Alert) bool) error {
	a.Lock()
	defer a.Unlock()
	if alert, ok := a.c[fp]; ok && fn(alert) {
		delete(a.c, fp)
	}
	return nil
}

// List returns a slice of Alerts currently held in memory.
func (a *Alerts) List() []*types.Alert {
	a.Lock()
	defer a.Unlock()

	alerts := make([]*types.Alert, 0, len(a.c))
	for _, alert := range a.c {
		alerts = append(alerts, alert)
	}

	return alerts
}

// Empty returns true if the store is empty.

Review comment (suggestion):
If we chose to go with my approach or your approach - we'd need to remove the `Delete` method as it is no longer used. 

Code after:

	alert, prs := a.c[fp]
	if !prs {
		return nil, ErrNotFound
	}
	return alert, nil
}

// Set unconditionally sets the alert in memory.
func (a *Alerts) Set(alert *types.Alert) error {
	a.Lock()
	defer a.Unlock()

	a.c[alert.Fingerprint()] = alert
	return nil
}

// DeleteIfNotModified deletes the slice of Alerts from the store if not
// modified.
func (a *Alerts) DeleteIfNotModified(alerts types.AlertSlice) error {
	a.Lock()
	defer a.Unlock()
	for _, alert := range alerts {
		fp := alert.Fingerprint()
		if other, ok := a.c[fp]; ok && alert.UpdatedAt == other.UpdatedAt {
			delete(a.c, fp)
		}
	}
	return nil
}

// List returns a slice of Alerts currently held in memory.
func (a *Alerts) List() []*types.Alert {
	a.Lock()
	defer a.Unlock()

	alerts := make([]*types.Alert, 0, len(a.c))
	for _, alert := range a.c {
		alerts = append(alerts, alert)
	}

	return alerts
}

// Empty returns true if the store is empty.
func (a *Alerts) Empty() bool {
	a.Lock()
	defer a.Unlock()

	return len(a.c) == 0
}