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 }