|
|
|
|
@ -2,8 +2,11 @@ package annotationsimpl |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"context" |
|
|
|
|
"fmt" |
|
|
|
|
"sort" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/dskit/concurrency" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/annotations" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/annotations/accesscontrol" |
|
|
|
|
@ -11,20 +14,29 @@ import ( |
|
|
|
|
|
|
|
|
|
// CompositeStore is a read store that combines two or more read stores, and queries all stores in parallel.
|
|
|
|
|
type CompositeStore struct { |
|
|
|
|
logger log.Logger |
|
|
|
|
readers []readStore |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewCompositeStore(readers ...readStore) *CompositeStore { |
|
|
|
|
func NewCompositeStore(logger log.Logger, readers ...readStore) *CompositeStore { |
|
|
|
|
return &CompositeStore{ |
|
|
|
|
logger: logger, |
|
|
|
|
readers: readers, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Satisfy the commonStore interface, in practice this is not used.
|
|
|
|
|
func (c *CompositeStore) Type() string { |
|
|
|
|
return "composite" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Get returns annotations from all stores, and combines the results.
|
|
|
|
|
func (c *CompositeStore) Get(ctx context.Context, query *annotations.ItemQuery, accessResources *accesscontrol.AccessResources) ([]*annotations.ItemDTO, error) { |
|
|
|
|
itemCh := make(chan []*annotations.ItemDTO, len(c.readers)) |
|
|
|
|
|
|
|
|
|
err := concurrency.ForEachJob(ctx, len(c.readers), len(c.readers), func(ctx context.Context, i int) error { |
|
|
|
|
err := concurrency.ForEachJob(ctx, len(c.readers), len(c.readers), func(ctx context.Context, i int) (err error) { |
|
|
|
|
defer handleJobPanic(c.logger, c.readers[i].Type(), &err) |
|
|
|
|
|
|
|
|
|
items, err := c.readers[i].Get(ctx, query, accessResources) |
|
|
|
|
itemCh <- items |
|
|
|
|
return err |
|
|
|
|
@ -47,7 +59,9 @@ func (c *CompositeStore) Get(ctx context.Context, query *annotations.ItemQuery, |
|
|
|
|
func (c *CompositeStore) GetTags(ctx context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) { |
|
|
|
|
resCh := make(chan annotations.FindTagsResult, len(c.readers)) |
|
|
|
|
|
|
|
|
|
err := concurrency.ForEachJob(ctx, len(c.readers), len(c.readers), func(ctx context.Context, i int) error { |
|
|
|
|
err := concurrency.ForEachJob(ctx, len(c.readers), len(c.readers), func(ctx context.Context, i int) (err error) { |
|
|
|
|
defer handleJobPanic(c.logger, c.readers[i].Type(), &err) |
|
|
|
|
|
|
|
|
|
res, err := c.readers[i].GetTags(ctx, query) |
|
|
|
|
resCh <- res |
|
|
|
|
return err |
|
|
|
|
@ -65,3 +79,20 @@ func (c *CompositeStore) GetTags(ctx context.Context, query *annotations.TagsQue |
|
|
|
|
|
|
|
|
|
return annotations.FindTagsResult{Tags: res}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// handleJobPanic is a helper function that recovers from a panic in a concurrent job.,
|
|
|
|
|
// It will log the error and set the job error if it is not nil.
|
|
|
|
|
func handleJobPanic(logger log.Logger, storeType string, jobErr *error) { |
|
|
|
|
if r := recover(); r != nil { |
|
|
|
|
logger.Error("Annotation store panic", "error", r, "store", storeType, "stack", log.Stack(1)) |
|
|
|
|
errMsg := "concurrent job panic" |
|
|
|
|
|
|
|
|
|
if jobErr != nil { |
|
|
|
|
err := fmt.Errorf(errMsg) |
|
|
|
|
if panicErr, ok := r.(error); ok { |
|
|
|
|
err = fmt.Errorf("%s: %w", errMsg, panicErr) |
|
|
|
|
} |
|
|
|
|
*jobErr = err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|