package queryrangebase import ( "context" "net/http" "github.com/weaveworks/common/httpgrpc" "github.com/grafana/dskit/tenant" "github.com/grafana/loki/pkg/util/validation" ) // RequestResponse contains a request response and the respective request that was used. type RequestResponse struct { Request Request Response Response } // DoRequests executes a list of requests in parallel. The limits parameters is used to limit parallelism per single request. func DoRequests(ctx context.Context, downstream Handler, reqs []Request, limits Limits) ([]RequestResponse, error) { tenantIDs, err := tenant.TenantIDs(ctx) if err != nil { return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) } // If one of the requests fail, we want to be able to cancel the rest of them. ctx, cancel := context.WithCancel(ctx) defer cancel() // Feed all requests to a bounded intermediate channel to limit parallelism. intermediate := make(chan Request) go func() { for _, req := range reqs { intermediate <- req } close(intermediate) }() respChan, errChan := make(chan RequestResponse), make(chan error) parallelism := validation.SmallestPositiveIntPerTenant(tenantIDs, limits.MaxQueryParallelism) if parallelism > len(reqs) { parallelism = len(reqs) } for i := 0; i < parallelism; i++ { go func() { for req := range intermediate { resp, err := downstream.Do(ctx, req) if err != nil { errChan <- err } else { respChan <- RequestResponse{req, resp} } } }() } resps := make([]RequestResponse, 0, len(reqs)) var firstErr error for range reqs { select { case resp := <-respChan: resps = append(resps, resp) case err := <-errChan: if firstErr == nil { cancel() firstErr = err } } } return resps, firstErr }