go.temporal.io/server/service/matching/matcher.go

655 LOC · 378 covered · 277 uncovered · 130 ranges · 300 concepts · 93 introducers · 202 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 package matching
2
3 import (
4 "context"
5 "errors"
6 "math"
7 "sync"
8 "sync/atomic"
9 "time"
10
11 enumsspb "go.temporal.io/server/api/enums/v1"
12 "go.temporal.io/server/api/matchingservice/v1"
13 "go.temporal.io/server/common/metrics"
14 "go.temporal.io/server/common/primitives/timestamp"
15 "go.temporal.io/server/common/quotas"
16 "google.golang.org/grpc/codes"
17 "google.golang.org/grpc/status"
18 )
19
20 // TaskMatcher matches a task producer with a task consumer
21 // Producers are usually rpc calls from history or taskReader
22 // that drains backlog from db. Consumers are the task queue pollers
23 type TaskMatcher struct {
24 config *taskQueueConfig
25
26 // synchronous task channel to match producer/consumer
27 taskC chan *internalTask
28 // synchronous task channel to match query task - the reason to have a
29 // separate channel for this is that there are cases where consumers
30 // are interested in queryTasks but not others. One example is when a
31 // namespace is not active in a cluster.
32 queryTaskC chan *internalTask
33 // channel closed when task queue is closed, to interrupt pollers
34 closeC chan struct{}
35
36 // rateLimiter that limits the rate at which tasks can be dispatched to consumers
37 rateLimiter quotas.RateLimiter
38
39 fwdr *Forwarder
40 metricsHandler metrics.Handler // namespace metric scope
41 backlogTasksCreateTime map[int64]int // task creation time (unix nanos) -> number of tasks with that time
42 backlogTasksLock sync.Mutex
43 lastPoller atomic.Int64 // unix nanos of most recent poll start time
44 waitingPollerCount atomic.Int64
45 }
46
47 var (
48 // Sentinel error to redirect while blocked in matcher.
49 errInterrupted = errors.New("interrupted offer")
50 errNoRecentPoller = status.Error(codes.FailedPrecondition, "no poller seen for task queue recently, worker may be down")
51 )
52
53 // newTaskMatcher returns a task matcher instance. The returned instance can be used by task producers and consumers to
54 // find a match. Both sync matches and non-sync matches should use this implementation
55 > func newTaskMatcher(config *taskQueueConfig, fwdr *Forwarder, metricsHandler metrics.Handler, rateLimiter quotas.RateLimiter) *TaskMatcher { matcher.go ×1
56 > return &TaskMatcher{
57 > config: config,
58 > rateLimiter: rateLimiter,
59 > metricsHandler: metricsHandler,
60 > fwdr: fwdr,
61 > taskC: make(chan *internalTask),
62 > queryTaskC: make(chan *internalTask),
63 > closeC: make(chan struct{}),
64 > backlogTasksCreateTime: make(map[int64]int),
65 > }
66 > }
67
68 > func (tm *TaskMatcher) Start() { matcher.go ×1
69 > }
70
71 > func (tm *TaskMatcher) Stop() { matcher.go ×1
72 > close(tm.closeC)
73 > }
74
75 > func (tm *TaskMatcher) recycleToken(*internalTask) { clocked_rate_limiter.go ×1
76 > tm.rateLimiter.RecycleToken()
77 > }
78
79 // Offer offers a task to a potential consumer (poller)
80 // If the task is successfully matched with a consumer, this
81 // method will return true and no error. If the task is matched
82 // but consumer returned error, then this method will return
83 // true and error message. This method should not be used for query
84 // task. This method should ONLY be used for sync match.
85 //
86 // When a local poller is not available and forwarding to a parent
87 // task queue partition is possible, this method will attempt forwarding
88 // to the parent partition.
89 //
90 // Cases when this method will block:
91 //
92 // Ratelimit:
93 // When a ratelimit token is not available, this method might block
94 // waiting for a token until the provided context timeout. Rate limits are
95 // not enforced for forwarded tasks from child partition.
96 //
97 // Forwarded tasks that originated from db backlog:
98 // When this method is called with a task that is forwarded from a
99 // remote partition and if (1) this task queue is root (2) task
100 // was from db backlog - this method will block until context timeout
101 // trying to match with a poller. The caller is expected to set the
102 // correct context timeout.
103 //
104 // returns error when:
105 // - ratelimit is exceeded (does not apply to query task)
106 // - context deadline is exceeded
107 // - task is matched and consumer returns error in response channel
108 > func (tm *TaskMatcher) Offer(ctx context.Context, task *internalTask) (bool, error) { matcher.go ×1
109 > if !tm.isBacklogNegligible() {
110 > // To ensure better dispatch ordering, we block sync match when a significant backlog is present. matcher.go ×1
111 > // Note that this check does not make a noticeable difference for history tasks, as they do not wait for a
112 > // poller to become available. In presence of a backlog the chance of a poller being available when sync match
113 > // request comes is almost zero.
114 > // This check is mostly effective for the sync match requests that come from child partitions for spooled tasks.
115 > return false, nil
116 > }
117
118 > if !task.isForwarded() { matcher.go ×1
119 > if err := tm.rateLimiter.Wait(ctx); err != nil { matcher.go ×1
120 > metrics.SyncThrottlePerTaskQueueCounter.With(tm.metricsHandler).Record(1) matcher.go ×2
121 > return false, err
122 > }
123 // because we waited on the rate limiter to offer this task,
124 // attach the rate limiter's RecycleToken func to the task
125 // so that if the task is later determined to be invalid,
126 // we can recycle the token it used.
127 > task.recycleToken = tm.recycleToken matcher.go ×1
128 }
129
130 > select { matcher.go ×1
131 > case tm.taskC <- task: // poller picked up the task matcher.go ×2
132 > if task.responseC != nil {
133 > // if there is a response channel, block until resp is received
134 > // and return error if the response contains error
135 > err := <-task.responseC
136 >
137 > if err.startErr == nil && !task.isForwarded() {
138 > tm.emitDispatchLatency(task, false) matcher.go ×1
139 > }
140 > return true, err.startErr matcher.go ×2
141 }
142 return false, nil
143 > default: matcher.go ×1
144 > // no poller waiting for tasks, try forwarding this task to the
145 > // root partition if possible
146 > select {
147 > case token := <-tm.fwdrAddReqTokenC(): matcher.go ×1
148 > if err := tm.fwdr.ForwardTask(ctx, task); err == nil {
149 > // task was remotely sync matched on the parent partition matcher.go ×2
150 > token.release()
151 > if !task.isForwarded() {
152 // if there are multiple forwarding hops, only the initial source partition emits this metric
153 tm.emitDispatchLatency(task, true)
154 }
155 > return true, nil matcher.go ×2
156 }
157 > token.release() matcher.go ×1
158 > default: matcher.go ×2
159 > if !tm.isForwardingAllowed() && // we are the root partition and forwarding is not possible
160 > task.source == enumsspb.TASK_SOURCE_DB_BACKLOG && // task was from backlog (stored in db)
161 > task.isForwarded() { // task came from a child partition
162 > // a forwarded backlog task from a child partition, block trying matcher.go ×3
163 > // to match with a poller until ctx timeout
164 > return tm.offerOrTimeout(ctx, task)
165 > }
166 }
167
168 > return false, nil matcher.go ×1
169 }
170 }
171
172 > func (tm *TaskMatcher) offerOrTimeout(ctx context.Context, task *internalTask) (bool, error) { matcher.go ×3
173 > select {
174 > case tm.taskC <- task: // poller picked up the task
175 > if task.responseC != nil {
176 > select {
177 > case err := <-task.responseC:
178 > return true, err.startErr
179 case <-ctx.Done():
180 return false, nil
181 }
182 }
183 return false, nil
184 case <-ctx.Done():
185 return false, nil
186 }
187 }
188
189 func syncOfferTask[T any](
190 ctx context.Context,
191 tm *TaskMatcher,
192 task *internalTask,
193 taskChan chan *internalTask,
194 forwardFunc func(context.Context, *internalTask) (T, error),
195 returnNoPollerErr bool,
196 > ) (T, error) { matcher.go ×1
197 > var t T
198 > select {
199 > case taskChan <- task: matcher.go ×1
200 > <-task.responseC
201 > return t, nil
202 > default: matcher.go ×3
203 }
204
205 > fwdrTokenC := tm.fwdrAddReqTokenC() matcher.go ×3
206 > var noPollerC <-chan time.Time
207 >
208 > for {
209 > if returnNoPollerErr {
210 > returnNoPollerErr = false // only do this once matcher.go ×1
211 > if deadline, ok := ctx.Deadline(); ok && fwdrTokenC == nil {
212 > // Reserving 1sec to customize the timeout error if user is querying a workflow matcher.go ×2
213 > // without having started the workers.
214 > noPollerTimeout := time.Until(deadline) - returnEmptyTaskTimeBudget
215 > t := time.NewTimer(noPollerTimeout)
216 > noPollerC = t.C
217 > defer t.Stop()
218 > }
219 }
220
221 > select { matcher.go ×3
222 > case taskChan <- task: matcher.go ×2
223 > <-task.responseC
224 > return t, nil
225 > case token := <-fwdrTokenC: matcher.go ×1
226 > resp, err := forwardFunc(ctx, task)
227 > token.release()
228 > if err == nil {
229 > return resp, nil request_response.pb.go ×1
230 > }
231 > if errors.Is(err, errForwarderSlowDown) { matcher.go ×1
232 > // if we are rate limited, try only local match for the remainder of the context timeout matcher.go ×2
233 > // left
234 > fwdrTokenC = nil
235 > continue
236 }
237 > return t, err matcher.go ×1
238 > case <-noPollerC: matcher.go ×2
239 > // only error if there has not been a recent poller. Otherwise, let it wait for the remaining time
240 > // hopping for a match, or ultimately returning the default CDE error.
241 > if tm.timeSinceLastPoll() > tm.config.QueryPollerUnavailableWindow() {
242 > return t, errNoRecentPoller matcher.go ×1
243 > }
244 > continue matcher.go ×1
245 > case <-ctx.Done(): matcher.go ×1
246 > return t, ctx.Err()
247 }
248 }
249 }
250
251 // OfferQuery will either match task to local poller or will forward query task.
252 // Local match is always attempted before forwarding is attempted. If local match occurs
253 // response and error are both nil, if forwarding occurs then response or error is returned.
254 > func (tm *TaskMatcher) OfferQuery(ctx context.Context, task *internalTask) (*matchingservice.QueryWorkflowResponse, error) { matcher.go ×1
255 > return syncOfferTask(ctx, tm, task, tm.queryTaskC, tm.fwdr.ForwardQueryTask, true)
256 > }
257
258 // OfferNexusTask either matchs a task to a local poller or forwards it if no local pollers available.
259 // Local match is always attempted before forwarding. If local match occurs response and error are both nil, if
260 // forwarding occurs then response or error is returned.
261 > func (tm *TaskMatcher) OfferNexusTask(ctx context.Context, task *internalTask) (*matchingservice.DispatchNexusTaskResponse, error) { matcher.go ×1
262 > return syncOfferTask(ctx, tm, task, tm.taskC, tm.fwdr.ForwardNexusTask, false)
263 > }
264
265 // MustOffer blocks until a consumer is found to handle this task
266 // Returns error only when context is canceled or the ratelimit is set to zero (allow nothing)
267 // The passed in context MUST NOT have a deadline associated with it
268 // Note that calling MustOffer is the only way that matcher knows there are spooled tasks in the
269 // backlog, in absence of a pending MustOffer call, the forwarding logic assumes that backlog is empty.
270 > func (tm *TaskMatcher) MustOffer(ctx context.Context, task *internalTask, interruptCh <-chan struct{}) error { matcher.go ×3
271 > tm.registerBacklogTask(task)
272 > defer tm.unregisterBacklogTask(task)
273 >
274 > if err := tm.rateLimiter.Wait(ctx); err != nil {
275 > return err matcher.go ×2
276 > }
277
278 // because we waited on the rate limiter to offer this task,
279 // attach the rate limiter's RecycleToken func to the task
280 // so that if the task is later determined to be invalid,
281 // we can recycle the token it used.
282 > task.recycleToken = tm.recycleToken matcher.go ×3
283 >
284 > // attempt a match with local poller first. When that
285 > // doesn't succeed, try both local match and remote match
286 > select {
287 > case tm.taskC <- task: matcher.go ×1
288 > tm.emitDispatchLatency(task, false)
289 > return nil
290 > case <-ctx.Done(): matcher.go ×1
291 > return ctx.Err()
292 > default: matcher.go ×4
293 }
294
295 > var reconsiderFwdTimer *time.Timer matcher.go ×4
296 > defer func() {
297 > if reconsiderFwdTimer != nil { matcher.go ×1
298 > reconsiderFwdTimer.Stop() matcher.go ×1
299 > }
300 }()
301
302 > forLoop: matcher.go ×4
303 > for {
304 > fwdTokenC := tm.fwdrAddReqTokenC()
305 > reconsiderFwdTimer = nil
306 > var reconsiderFwdTimerC <-chan time.Time
307 > if fwdTokenC != nil && !tm.isBacklogNegligible() {
308 > // If there is a non-negligible backlog, we stop forwarding to make sure matcher.go ×2
309 > // root and leaf partitions are treated equally and can process their
310 > // backlog at the same rate. Stopping task forwarding, prevent poll
311 > // forwarding as well (in presence of a backlog). This ensures all partitions
312 > // receive polls and tasks at the same rate.
313 >
314 > // Exception: we allow forward if this partition has not got any polls
315 > // recently. This is helpful when there are very few pollers and they
316 > // and they are all stuck in the wrong (root) partition. (Note that since
317 > // frontend balanced the number of pending pollers per partition this only
318 > // becomes an issue when the pollers are fewer than the partitions)
319 > lp := tm.timeSinceLastPoll()
320 > maxWaitForLocalPoller := tm.config.MaxWaitForPollerBeforeFwd()
321 > if lp < maxWaitForLocalPoller {
322 > fwdTokenC = nil
323 > reconsiderFwdTimer = time.NewTimer(maxWaitForLocalPoller - lp)
324 > reconsiderFwdTimerC = reconsiderFwdTimer.C
325 > }
326 }
327
328 > select { matcher.go ×4
329 > case tm.taskC <- task: matcher.go ×1
330 > tm.emitDispatchLatency(task, false)
331 > return nil
332 > case token := <-fwdTokenC: matcher.go ×1
333 > childCtx, cancel := context.WithTimeout(ctx, time.Second*2)
334 > err := tm.fwdr.ForwardTask(childCtx, task)
335 > token.release()
336 > if err != nil {
337 > metrics.ForwardTaskErrorsPerTaskQueue.With(tm.metricsHandler).Record(1) matcher.go ×1
338 > // forwarder returns error only when the call is rate limited. To
339 > // avoid a busy loop on such rate limiting events, we only attempt to make
340 > // the next forwarded call after this childCtx expires. Till then, we block
341 > // hoping for a local poller match
342 > select {
343 case tm.taskC <- task:
344 cancel()
345 tm.emitDispatchLatency(task, false)
346 return nil
347 > case <-childCtx.Done(): matcher.go ×2
348 > case <-ctx.Done(): matcher.go ×2
349 > cancel()
350 > return ctx.Err()
351 case <-interruptCh:
352 cancel()
353 return errInterrupted
354 }
355 > cancel() matcher.go ×2
356 > continue forLoop
357 }
358 > cancel() matcher.go ×2
359 > // at this point, we forwarded the task to a parent partition which
360 > // in turn dispatched the task to a poller, because there was no error.
361 > // Make sure we delete the task from the database.
362 > task.finish(taskFinishResult{consumedToken: true})
363 > tm.emitDispatchLatency(task, true)
364 > return nil
365 > case <-ctx.Done(): matcher.go ×1
366 > return ctx.Err()
367 > case <-reconsiderFwdTimerC: matcher.go ×1
368 > continue forLoop
369 case <-interruptCh:
370 return errInterrupted
371 }
372 }
373 }
374
375 > func (tm *TaskMatcher) emitDispatchLatency(task *internalTask, forwarded bool) { matcher.go ×1
376 > if tm.config.EmitTaskDispatchLatencyAtPoll() {
377 > return // metric will be emitted at poll response
378 > }
379 if task.event.Data.CreateTime == nil {
380 return // should not happen but for safety
381 }
382
383 metrics.TaskDispatchLatencyPerTaskQueue.With(tm.metricsHandler).Record(
384 time.Since(timestamp.TimeValue(task.event.Data.CreateTime)),
385 metrics.StringTag("source", task.source.String()),
386 metrics.ForwardedTag(forwarded),
387 metrics.StringTag(metrics.TaskPriorityTagName, ""),
388 )
389 }
390
391 // Poll blocks until a task is found or context deadline is exceeded
392 // On success, the returned task could be a query task or a regular task
393 // Returns errNoTasks when context deadline is exceeded
394 > func (tm *TaskMatcher) Poll(ctx context.Context, pollMetadata *pollMetadata) (*internalTask, error) { matcher.go ×1
395 > task, _, err := tm.poll(ctx, pollMetadata, false)
396 > return task, err
397 > }
398
399 // PollForQuery blocks until a *query* task is found or context deadline is exceeded
400 // Returns errNoTasks when context deadline is exceeded
401 > func (tm *TaskMatcher) PollForQuery(ctx context.Context, pollMetadata *pollMetadata) (*internalTask, error) { matcher.go ×2
402 > task, _, err := tm.poll(ctx, pollMetadata, true)
403 > return task, err
404 > }
405
406 > func (tm *TaskMatcher) ReprocessAllTasks() { matcher.go ×1
407 > // unused in old matcher
408 > }
409
410 func (tm *TaskMatcher) poll(
411 ctx context.Context, pollMetadata *pollMetadata, queryOnly bool,
412 > ) (task *internalTask, forwardedPoll bool, err error) { matcher.go ×6
413 > taskC, queryTaskC := tm.taskC, tm.queryTaskC
414 > if queryOnly {
415 > taskC = nil matcher.go ×2
416 > }
417
418 > start := time.Now() matcher.go ×6
419 > tm.lastPoller.Store(start.UnixNano())
420 >
421 > defer func() {
422 > if pollMetadata.forwardedFrom == "" {
423 > // Only recording for original polls
424 > var pollResult string
425 > if err == nil {
426 > pollResult = "success" matcher.go ×3
427 > } else if errors.Is(err, errNoTasks) { matcher.go ×6
428 > pollResult = "timeout" matcher.go ×1
429 > } else {
430 pollResult = "failed"
431 }
432 > metrics.PollLatencyPerTaskQueue.With(tm.metricsHandler).Record( matcher.go ×6
433 > time.Since(start),
434 > metrics.ForwardedTag(forwardedPoll),
435 > metrics.StringTag(metrics.TaskPriorityTagName, ""),
436 > metrics.PollResultTag(pollResult),
437 > )
438 }
439
440 > if err == nil { matcher.go ×6
441 > tm.emitForwardedSourceStats(task.isForwarded(), pollMetadata.forwardedFrom, forwardedPoll) matcher.go ×3
442 > }
443 }()
444
445 // We want to effectively do a prioritized select, but Go select is random
446 // if multiple cases are ready, so split into multiple selects.
447 // The priority order is:
448 // 1. ctx.Done or tm.closeC
449 // 2. taskC and queryTaskC
450 // 3. forwarding
451 // 4. block looking locally for remainder of context lifetime
452 // To correctly handle priorities and allow any case to succeed, all select
453 // statements except for the last one must be non-blocking, and the last one
454 // must include all the previous cases.
455
456 // 1. ctx.Done
457 > select { matcher.go ×6
458 > case <-ctx.Done(): matcher.go ×1
459 > metrics.PollTimeoutPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
460 > return nil, false, errNoTasks
461 > case <-tm.closeC: matcher.go ×1
462 > return nil, false, errNoTasks
463 > default: matcher.go ×2
464 }
465
466 // 2. taskC and queryTaskC
467 > select { matcher.go ×2
468 > case task := <-taskC: matcher.go ×2
469 > if task.responseC != nil {
470 > metrics.PollSuccessWithSyncPerTaskQueueCounter.With(tm.metricsHandler).Record(1) matcher.go ×3
471 > }
472 > metrics.PollSuccessPerTaskQueueCounter.With(tm.metricsHandler).Record(1) matcher.go ×2
473 > return task, false, nil
474 case task := <-queryTaskC:
475 metrics.PollSuccessWithSyncPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
476 metrics.PollSuccessPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
477 return task, false, nil
478 > default: matcher.go ×2
479 }
480
481 // From here on the goroutine will block on taskC (in step 3 or 4), so it
482 // is ready to receive a sync-matched task.
483 > tm.waitingPollerCount.Add(1) matcher.go ×2
484 > defer tm.waitingPollerCount.Add(-1)
485 >
486 > if tm.isBacklogNegligible() {
487 > // 3. forwarding (and all other clauses repeated) matcher.go ×2
488 > // We don't forward pollers if there is a non-negligible backlog in this partition.
489 > select {
490 > case <-ctx.Done(): matcher.go ×1
491 > metrics.PollTimeoutPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
492 > return nil, false, errNoTasks
493 > case <-tm.closeC: matcher.go ×1
494 > return nil, false, errNoTasks
495 > case task := <-taskC: matcher.go ×2
496 > if task.responseC != nil {
497 > metrics.PollSuccessWithSyncPerTaskQueueCounter.With(tm.metricsHandler).Record(1) matcher.go ×1
498 > }
499 > metrics.PollSuccessPerTaskQueueCounter.With(tm.metricsHandler).Record(1) matcher.go ×2
500 > return task, false, nil
501 > case task := <-queryTaskC: matcher.go ×1
502 > metrics.PollSuccessWithSyncPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
503 > metrics.PollSuccessPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
504 > return task, false, nil
505 > case token := <-tm.fwdrPollReqTokenC(): matcher.go ×3
506 > // Arrange to cancel this request if closeC is closed
507 > fwdCtx, cancel := contextWithCancelOnChannelClose(ctx, tm.closeC)
508 > task, err := tm.fwdr.ForwardPoll(fwdCtx, pollMetadata)
509 > cancel()
510 > token.release()
511 > if err == nil {
512 > return task, true, nil matcher.go ×2
513 > }
514 }
515 }
516
517 // 4. blocking local poll
518 > select { matcher.go ×1
519 > case <-ctx.Done(): matcher.go ×1
520 > metrics.PollTimeoutPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
521 > return nil, false, errNoTasks
522 case <-tm.closeC:
523 return nil, false, errNoTasks
524 > case task := <-taskC: matcher.go ×2
525 > if task.responseC != nil {
526 > metrics.PollSuccessWithSyncPerTaskQueueCounter.With(tm.metricsHandler).Record(1) matcher.go ×2
527 > }
528 > metrics.PollSuccessPerTaskQueueCounter.With(tm.metricsHandler).Record(1) matcher.go ×2
529 > return task, false, nil
530 case task := <-queryTaskC:
531 metrics.PollSuccessWithSyncPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
532 metrics.PollSuccessPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
533 return task, false, nil
534 }
535 }
536
537 > func (tm *TaskMatcher) fwdrPollReqTokenC() <-chan *ForwarderReqToken { matcher.go ×2
538 > if tm.fwdr == nil {
539 > return nil matcher.go ×1
540 > }
541 > return tm.fwdr.PollReqTokenC() matcher.go ×1
542 }
543
544 > func (tm *TaskMatcher) fwdrAddReqTokenC() <-chan *ForwarderReqToken { matcher.go ×1
545 > if tm.fwdr == nil {
546 > return nil matcher.go ×1
547 > }
548 > return tm.fwdr.AddReqTokenC() matcher.go ×1
549 }
550
551 > func (tm *TaskMatcher) isForwardingAllowed() bool { matcher.go ×2
552 > return tm.fwdr != nil
553 > }
554
555 // isBacklogNegligible returns true of the age of backlog is less than the threshold. Note that this relies on
556 // MustOffer being called when there is a backlog, otherwise we'd not know.
557 > func (tm *TaskMatcher) isBacklogNegligible() bool { matcher.go ×1
558 > return tm.getBacklogAge() < tm.config.BacklogNegligibleAge()
559 > }
560
561 > func (tm *TaskMatcher) registerBacklogTask(task *internalTask) { matcher.go ×3
562 > if task.event.Data.CreateTime == nil {
563 > return // should not happen but for safety matcher.go ×1
564 > }
565
566 > tm.backlogTasksLock.Lock() matcher.go ×2
567 > defer tm.backlogTasksLock.Unlock()
568 >
569 > ts := timestamp.TimeValue(task.event.Data.CreateTime).UnixNano()
570 > tm.backlogTasksCreateTime[ts] += 1
571 }
572
573 > func (tm *TaskMatcher) unregisterBacklogTask(task *internalTask) { matcher.go ×1
574 > if task.event.Data.CreateTime == nil {
575 > return // should not happen but for safety matcher.go ×1
576 > }
577
578 > tm.backlogTasksLock.Lock() matcher.go ×2
579 > defer tm.backlogTasksLock.Unlock()
580 >
581 > ts := timestamp.TimeValue(task.event.Data.CreateTime).UnixNano()
582 > counter := tm.backlogTasksCreateTime[ts]
583 > if counter == 1 {
584 > delete(tm.backlogTasksCreateTime, ts)
585 > } else {
586 > tm.backlogTasksCreateTime[ts] = counter - 1 matcher.go ×1
587 > }
588 }
589
590 // getBacklogAge is the latest age across all backlogs re-directing to this matcher; may momentarily
591 // be 0 cause of race conditions when no reader pushes a task into the matcher at this moment
592 > func (tm *TaskMatcher) getBacklogAge() time.Duration { matcher.go ×1
593 > tm.backlogTasksLock.Lock()
594 > defer tm.backlogTasksLock.Unlock()
595 >
596 > if len(tm.backlogTasksCreateTime) == 0 {
597 > return emptyBacklogAge matcher.go ×1
598 > }
599
600 > oldest := int64(math.MaxInt64) matcher.go ×2
601 > for createTime := range tm.backlogTasksCreateTime {
602 > oldest = min(oldest, createTime)
603 > }
604
605 > return time.Since(time.Unix(0, oldest)) matcher.go ×2
606 }
607
608 func (tm *TaskMatcher) emitForwardedSourceStats(
609 isTaskForwarded bool,
610 pollForwardedSource string,
611 forwardedPoll bool,
612 > ) { matcher.go ×3
613 > if forwardedPoll {
614 > // This means we forwarded the poll to another partition. Skipping this to prevent duplicate emits. matcher.go ×2
615 > // Only the partition in which the match happened should emit this metric.
616 > return
617 > }
618
619 > isPollForwarded := len(pollForwardedSource) > 0 matcher.go ×1
620 > switch {
621 case isTaskForwarded && isPollForwarded:
622 metrics.RemoteToRemoteMatchPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
623 > case isTaskForwarded: matcher.go ×1
624 > metrics.RemoteToLocalMatchPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
625 case isPollForwarded:
626 metrics.LocalToRemoteMatchPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
627 > default: matcher.go ×1
628 > metrics.LocalToLocalMatchPerTaskQueueCounter.With(tm.metricsHandler).Record(1)
629 }
630 }
631
632 > func (tm *TaskMatcher) timeSinceLastPoll() time.Duration { matcher.go ×1
633 > return time.Since(time.Unix(0, tm.lastPoller.Load()))
634 > }
635
636 // HasWaitingPoller returns true if it there's a poller ready and waiting
637 // this is mostly useful in testing to avoid test races on setup
638 > func (tm *TaskMatcher) HasWaitingPoller() bool { matcher.go ×1
639 > return tm.waitingPollerCount.Load() > 0
640 > }
641
642 // contextWithCancelOnChannelClose returns a child Context and CancelFunc just like
643 // context.WithCancel, but additionally propagates cancellation from another channel (besides
644 // the parent's cancellation channel).
645 > func contextWithCancelOnChannelClose(parent context.Context, closeC <-chan struct{}) (context.Context, context.CancelFunc) { matcher.go ×3
646 > ctx, cancel := context.WithCancel(parent)
647 > go func() {
648 > select {
649 > case <-closeC: matcher.go ×1
650 > cancel()
651 > case <-ctx.Done(): matcher.go ×1
652 }
653 }()
654 > return ctx, cancel matcher.go ×3
655 }