github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/frontend/v2/frontend_scheduler_worker.go (about)

     1  package v2
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/go-kit/log"
    10  	"github.com/go-kit/log/level"
    11  	"github.com/grafana/dskit/backoff"
    12  	"github.com/grafana/dskit/services"
    13  	"github.com/pkg/errors"
    14  	"github.com/weaveworks/common/httpgrpc"
    15  	"google.golang.org/grpc"
    16  
    17  	"github.com/cortexproject/cortex/pkg/frontend/v2/frontendv2pb"
    18  	"github.com/cortexproject/cortex/pkg/scheduler/schedulerpb"
    19  	"github.com/cortexproject/cortex/pkg/util"
    20  )
    21  
    22  type frontendSchedulerWorkers struct {
    23  	services.Service
    24  
    25  	cfg             Config
    26  	log             log.Logger
    27  	frontendAddress string
    28  
    29  	// Channel with requests that should be forwarded to the scheduler.
    30  	requestsCh <-chan *frontendRequest
    31  
    32  	watcher services.Service
    33  
    34  	mu sync.Mutex
    35  	// Set to nil when stop is called... no more workers are created afterwards.
    36  	workers map[string]*frontendSchedulerWorker
    37  }
    38  
    39  func newFrontendSchedulerWorkers(cfg Config, frontendAddress string, requestsCh <-chan *frontendRequest, log log.Logger) (*frontendSchedulerWorkers, error) {
    40  	f := &frontendSchedulerWorkers{
    41  		cfg:             cfg,
    42  		log:             log,
    43  		frontendAddress: frontendAddress,
    44  		requestsCh:      requestsCh,
    45  		workers:         map[string]*frontendSchedulerWorker{},
    46  	}
    47  
    48  	w, err := util.NewDNSWatcher(cfg.SchedulerAddress, cfg.DNSLookupPeriod, f)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	f.watcher = w
    54  	f.Service = services.NewIdleService(f.starting, f.stopping)
    55  	return f, nil
    56  }
    57  
    58  func (f *frontendSchedulerWorkers) starting(ctx context.Context) error {
    59  	return services.StartAndAwaitRunning(ctx, f.watcher)
    60  }
    61  
    62  func (f *frontendSchedulerWorkers) stopping(_ error) error {
    63  	err := services.StopAndAwaitTerminated(context.Background(), f.watcher)
    64  
    65  	f.mu.Lock()
    66  	defer f.mu.Unlock()
    67  
    68  	for _, w := range f.workers {
    69  		w.stop()
    70  	}
    71  	f.workers = nil
    72  
    73  	return err
    74  }
    75  
    76  func (f *frontendSchedulerWorkers) AddressAdded(address string) {
    77  	f.mu.Lock()
    78  	ws := f.workers
    79  	w := f.workers[address]
    80  	f.mu.Unlock()
    81  
    82  	// Already stopped or we already have worker for this address.
    83  	if ws == nil || w != nil {
    84  		return
    85  	}
    86  
    87  	level.Info(f.log).Log("msg", "adding connection to scheduler", "addr", address)
    88  	conn, err := f.connectToScheduler(context.Background(), address)
    89  	if err != nil {
    90  		level.Error(f.log).Log("msg", "error connecting to scheduler", "addr", address, "err", err)
    91  		return
    92  	}
    93  
    94  	// No worker for this address yet, start a new one.
    95  	w = newFrontendSchedulerWorker(conn, address, f.frontendAddress, f.requestsCh, f.cfg.WorkerConcurrency, f.log)
    96  
    97  	f.mu.Lock()
    98  	defer f.mu.Unlock()
    99  
   100  	// Can be nil if stopping has been called already.
   101  	if f.workers != nil {
   102  		f.workers[address] = w
   103  		w.start()
   104  	}
   105  }
   106  
   107  func (f *frontendSchedulerWorkers) AddressRemoved(address string) {
   108  	level.Info(f.log).Log("msg", "removing connection to scheduler", "addr", address)
   109  
   110  	f.mu.Lock()
   111  	// This works fine if f.workers is nil already.
   112  	w := f.workers[address]
   113  	delete(f.workers, address)
   114  	f.mu.Unlock()
   115  
   116  	if w != nil {
   117  		w.stop()
   118  	}
   119  }
   120  
   121  // Get number of workers.
   122  func (f *frontendSchedulerWorkers) getWorkersCount() int {
   123  	f.mu.Lock()
   124  	defer f.mu.Unlock()
   125  
   126  	return len(f.workers)
   127  }
   128  
   129  func (f *frontendSchedulerWorkers) connectToScheduler(ctx context.Context, address string) (*grpc.ClientConn, error) {
   130  	// Because we only use single long-running method, it doesn't make sense to inject user ID, send over tracing or add metrics.
   131  	opts, err := f.cfg.GRPCClientConfig.DialOption(nil, nil)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	conn, err := grpc.DialContext(ctx, address, opts...)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	return conn, nil
   141  }
   142  
   143  // Worker managing single gRPC connection to Scheduler. Each worker starts multiple goroutines for forwarding
   144  // requests and cancellations to scheduler.
   145  type frontendSchedulerWorker struct {
   146  	log log.Logger
   147  
   148  	conn          *grpc.ClientConn
   149  	concurrency   int
   150  	schedulerAddr string
   151  	frontendAddr  string
   152  
   153  	// Context and cancellation used by individual goroutines.
   154  	ctx    context.Context
   155  	cancel context.CancelFunc
   156  	wg     sync.WaitGroup
   157  
   158  	// Shared between all frontend workers.
   159  	requestCh <-chan *frontendRequest
   160  
   161  	// Cancellation requests for this scheduler are received via this channel. It is passed to frontend after
   162  	// query has been enqueued to scheduler.
   163  	cancelCh chan uint64
   164  }
   165  
   166  func newFrontendSchedulerWorker(conn *grpc.ClientConn, schedulerAddr string, frontendAddr string, requestCh <-chan *frontendRequest, concurrency int, log log.Logger) *frontendSchedulerWorker {
   167  	w := &frontendSchedulerWorker{
   168  		log:           log,
   169  		conn:          conn,
   170  		concurrency:   concurrency,
   171  		schedulerAddr: schedulerAddr,
   172  		frontendAddr:  frontendAddr,
   173  		requestCh:     requestCh,
   174  		cancelCh:      make(chan uint64),
   175  	}
   176  	w.ctx, w.cancel = context.WithCancel(context.Background())
   177  
   178  	return w
   179  }
   180  
   181  func (w *frontendSchedulerWorker) start() {
   182  	client := schedulerpb.NewSchedulerForFrontendClient(w.conn)
   183  	for i := 0; i < w.concurrency; i++ {
   184  		w.wg.Add(1)
   185  		go func() {
   186  			defer w.wg.Done()
   187  			w.runOne(w.ctx, client)
   188  		}()
   189  	}
   190  }
   191  
   192  func (w *frontendSchedulerWorker) stop() {
   193  	w.cancel()
   194  	w.wg.Wait()
   195  	if err := w.conn.Close(); err != nil {
   196  		level.Error(w.log).Log("msg", "error while closing connection to scheduler", "err", err)
   197  	}
   198  }
   199  
   200  func (w *frontendSchedulerWorker) runOne(ctx context.Context, client schedulerpb.SchedulerForFrontendClient) {
   201  	backoffConfig := backoff.Config{
   202  		MinBackoff: 50 * time.Millisecond,
   203  		MaxBackoff: 1 * time.Second,
   204  	}
   205  
   206  	backoff := backoff.New(ctx, backoffConfig)
   207  	for backoff.Ongoing() {
   208  		loop, loopErr := client.FrontendLoop(ctx)
   209  		if loopErr != nil {
   210  			level.Error(w.log).Log("msg", "error contacting scheduler", "err", loopErr, "addr", w.schedulerAddr)
   211  			backoff.Wait()
   212  			continue
   213  		}
   214  
   215  		loopErr = w.schedulerLoop(loop)
   216  		if closeErr := loop.CloseSend(); closeErr != nil {
   217  			level.Debug(w.log).Log("msg", "failed to close frontend loop", "err", loopErr, "addr", w.schedulerAddr)
   218  		}
   219  
   220  		if loopErr != nil {
   221  			level.Error(w.log).Log("msg", "error sending requests to scheduler", "err", loopErr, "addr", w.schedulerAddr)
   222  			backoff.Wait()
   223  			continue
   224  		}
   225  
   226  		backoff.Reset()
   227  	}
   228  }
   229  
   230  func (w *frontendSchedulerWorker) schedulerLoop(loop schedulerpb.SchedulerForFrontend_FrontendLoopClient) error {
   231  	if err := loop.Send(&schedulerpb.FrontendToScheduler{
   232  		Type:            schedulerpb.INIT,
   233  		FrontendAddress: w.frontendAddr,
   234  	}); err != nil {
   235  		return err
   236  	}
   237  
   238  	if resp, err := loop.Recv(); err != nil || resp.Status != schedulerpb.OK {
   239  		if err != nil {
   240  			return err
   241  		}
   242  		return errors.Errorf("unexpected status received for init: %v", resp.Status)
   243  	}
   244  
   245  	ctx := loop.Context()
   246  
   247  	for {
   248  		select {
   249  		case <-ctx.Done():
   250  			// No need to report error if our internal context is canceled. This can happen during shutdown,
   251  			// or when scheduler is no longer resolvable. (It would be nice if this context reported "done" also when
   252  			// connection scheduler stops the call, but that doesn't seem to be the case).
   253  			//
   254  			// Reporting error here would delay reopening the stream (if the worker context is not done yet).
   255  			level.Debug(w.log).Log("msg", "stream context finished", "err", ctx.Err())
   256  			return nil
   257  
   258  		case req := <-w.requestCh:
   259  			err := loop.Send(&schedulerpb.FrontendToScheduler{
   260  				Type:            schedulerpb.ENQUEUE,
   261  				QueryID:         req.queryID,
   262  				UserID:          req.userID,
   263  				HttpRequest:     req.request,
   264  				FrontendAddress: w.frontendAddr,
   265  				StatsEnabled:    req.statsEnabled,
   266  			})
   267  
   268  			if err != nil {
   269  				req.enqueue <- enqueueResult{status: failed}
   270  				return err
   271  			}
   272  
   273  			resp, err := loop.Recv()
   274  			if err != nil {
   275  				req.enqueue <- enqueueResult{status: failed}
   276  				return err
   277  			}
   278  
   279  			switch resp.Status {
   280  			case schedulerpb.OK:
   281  				req.enqueue <- enqueueResult{status: waitForResponse, cancelCh: w.cancelCh}
   282  				// Response will come from querier.
   283  
   284  			case schedulerpb.SHUTTING_DOWN:
   285  				// Scheduler is shutting down, report failure to enqueue and stop this loop.
   286  				req.enqueue <- enqueueResult{status: failed}
   287  				return errors.New("scheduler is shutting down")
   288  
   289  			case schedulerpb.ERROR:
   290  				req.enqueue <- enqueueResult{status: waitForResponse}
   291  				req.response <- &frontendv2pb.QueryResultRequest{
   292  					HttpResponse: &httpgrpc.HTTPResponse{
   293  						Code: http.StatusInternalServerError,
   294  						Body: []byte(err.Error()),
   295  					},
   296  				}
   297  
   298  			case schedulerpb.TOO_MANY_REQUESTS_PER_TENANT:
   299  				req.enqueue <- enqueueResult{status: waitForResponse}
   300  				req.response <- &frontendv2pb.QueryResultRequest{
   301  					HttpResponse: &httpgrpc.HTTPResponse{
   302  						Code: http.StatusTooManyRequests,
   303  						Body: []byte("too many outstanding requests"),
   304  					},
   305  				}
   306  			}
   307  
   308  		case reqID := <-w.cancelCh:
   309  			err := loop.Send(&schedulerpb.FrontendToScheduler{
   310  				Type:    schedulerpb.CANCEL,
   311  				QueryID: reqID,
   312  			})
   313  
   314  			if err != nil {
   315  				return err
   316  			}
   317  
   318  			resp, err := loop.Recv()
   319  			if err != nil {
   320  				return err
   321  			}
   322  
   323  			// Scheduler may be shutting down, report that.
   324  			if resp.Status != schedulerpb.OK {
   325  				return errors.Errorf("unexpected status received for cancellation: %v", resp.Status)
   326  			}
   327  		}
   328  	}
   329  }