go.temporal.io/server/service/matching/task.go
394 LOC · 159 covered · 235 uncovered · 48 ranges · 863 concepts · 40 introducers · 414 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.
package matching
import (
"context"
"sync/atomic"
"time"
commonpb "go.temporal.io/api/common/v1"
taskqueuepb "go.temporal.io/api/taskqueue/v1"
deploymentspb "go.temporal.io/server/api/deployment/v1"
enumsspb "go.temporal.io/server/api/enums/v1"
"go.temporal.io/server/api/matchingservice/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
taskqueuespb "go.temporal.io/server/api/taskqueue/v1"
"go.temporal.io/server/common/namespace"
"google.golang.org/protobuf/types/known/timestamppb"
)
type (
// genericTaskInfo contains the info for an activity or workflow task
genericTaskInfo struct {
*persistencespb.AllocatedTaskInfo
completionFunc func(*internalTask, taskResponse)
}
// queryTaskInfo contains the info for a query task
queryTaskInfo struct {
taskID string
createTime *timestamppb.Timestamp
request *matchingservice.QueryWorkflowRequest
}
// nexusTaskInfo contains the info for a nexus task
nexusTaskInfo struct {
taskID string
createTime *timestamppb.Timestamp
deadline time.Time
operationDeadline time.Time
request *matchingservice.DispatchNexusTaskRequest
}
// startedTaskInfo contains info for any task received from
// another matching host. This type of task is already marked as started
startedTaskInfo struct {
workflowTaskInfo *matchingservice.PollWorkflowTaskQueueResponse
activityTaskInfo *matchingservice.PollActivityTaskQueueResponse
nexusTaskInfo *matchingservice.PollNexusTaskQueueResponse
}
// internalTask represents an activity, workflow, query or started (received from another host).
// this struct is more like a union and only one of [ query, event, forwarded ] is
// non-nil for any given task
// TODO(pri): after deprecating classic matcher, we can consolidate backlogCountHint, recycleToken,
// and removeFromMatcher into a single *physicalTaskQueueManager field.
internalTask struct {
event *genericTaskInfo // non-nil for activity or workflow task that's locally generated
query *queryTaskInfo // non-nil for a query task that's locally sync matched
nexus *nexusTaskInfo // non-nil for a nexus task that's locally sync matched
started *startedTaskInfo // non-nil for a task received from a parent partition which is already started
namespace namespace.Name
source enumsspb.TaskSource
responseC chan taskResponse // non-nil only where there is a caller waiting for response (sync match)
backlogCountHint func() int64
// forwardInfo contains information about forward source partition and versioning decisions made by it
// a parent partition receiving forwarded tasks makes no versioning decisions and only follows what the source
// partition instructed.
forwardInfo *taskqueuespb.TaskForwardInfo
// redirectInfo is only set when redirect rule is applied on the task. for forwarded tasks,
// this is populated based on forwardInfo (V2 versioning).
redirectInfo *taskqueuespb.BuildIdRedirectInfo
// redirectedFromBacklog is true if this task was redirectedFromBacklog from the backlog it was read from
// (V2 and V3 versioning).
redirectedFromBacklog bool
// pollerScalingDecision is assigned when the queue has advice to give to the poller about whether
// it should adjust its poller count
pollerScalingDecision *taskqueuepb.PollerScalingDecision
recycleToken func(*internalTask)
removeFromMatcher atomic.Pointer[func()]
// taskDispatchRevisionNumber represents the revision number used by the task and is
// max(taskDirectiveRevisionNumber, routingConfigRevisionNumber) for the task.
taskDispatchRevisionNumber int64
targetWorkerDeploymentVersion *deploymentspb.WorkerDeploymentVersion
// The following fields are for use by priMatcher/matcherData:
waitableMatchResult
forwardCtx context.Context // non-nil for sync match task only
// effectivePriority is initialized from an explicit task priority if present, or the
// default for the task queue. It can also be the special pollForwarderPriority (higher
// than normal priorities) to indicate the poll forwarder. In some other cases (e.g.
// migration) it may be adjusted from the explicit task priority.
// The scale of effectivePriority is 10× the normal scale to allow inserting forwards
// in between priority levels.
effectivePriority priorityKey
pollForwarderType pollForwarderType
}
// taskResponse is used to report the result of either a match with a local poller,
// or forwarding a task, query, or nexus task.
taskResponse struct {
// If forwarded is true, then forwardRes and forwardErr have the result of forwarding.
// If it's false, then startErr has the result of RecordTaskStarted.
forwarded bool
forwardRes any // note this may be a non-nil "any" containing a nil pointer
forwardErr error
startErr error
// dropReason, when set, marks a dropped backlog task;
// reader.completeTask records it in tasks_dropped.
dropReason dropReason
}
// taskFinishResult describes how a task finished. It is passed to internalTask.finish.
taskFinishResult struct {
// err is the result of RecordTaskStarted (or forwarding); nil on success or drop.
err error
// consumedToken reports whether the task consumed its rate-limit token (see finish).
consumedToken bool
// dropReason, when set, indicates the task is being dropped rather than dispatched
// and is recorded in tasks_dropped.
dropReason dropReason
}
)
var (
// sentinel values for task.removeFromMatcher
removeFuncNotAddedYet = func() {}
removeFuncEvicted = func() {}
)
if res.forwarded {
}
}
func newInternalTaskForSyncMatch(
info *persistencespb.TaskInfo,
forwardInfo *taskqueuespb.TaskForwardInfo,
taskDispatchRevisionNumber int64,
targetVersion *deploymentspb.WorkerDeploymentVersion,
var redirectInfo *taskqueuespb.BuildIdRedirectInfo
// if this task is not forwarded, source can only be history
source := enumsspb.TASK_SOURCE_HISTORY
if forwardInfo != nil {
// if task is forwarded, it may be history or backlog. setting based on forward info
message.pb.go ×1
source = forwardInfo.TaskSource
redirectInfo = forwardInfo.GetRedirectInfo()
}
event: &genericTaskInfo{
AllocatedTaskInfo: &persistencespb.AllocatedTaskInfo{
Data: info,
TaskId: syncMatchTaskId,
},
},
forwardInfo: forwardInfo,
source: source,
redirectInfo: redirectInfo,
responseC: make(chan taskResponse, 1),
taskDispatchRevisionNumber: taskDispatchRevisionNumber,
targetWorkerDeploymentVersion: targetVersion,
effectivePriority: effectivePriorityFactor * priorityKey(info.GetPriority().GetPriorityKey()),
}
}
func newInternalTaskFromBacklog(
info *persistencespb.AllocatedTaskInfo,
completionFunc func(*internalTask, taskResponse),
return &internalTask{
event: &genericTaskInfo{
AllocatedTaskInfo: info,
completionFunc: completionFunc,
},
source: enumsspb.TASK_SOURCE_DB_BACKLOG,
effectivePriority: effectivePriorityFactor * priorityKey(info.GetData().GetPriority().GetPriorityKey()),
}
}
func newInternalQueryTask(
taskID string,
request *matchingservice.QueryWorkflowRequest,
return &internalTask{
query: &queryTaskInfo{
taskID: taskID,
createTime: getCreateTime(request.GetForwardInfo()),
request: request,
},
forwardInfo: request.GetForwardInfo(),
responseC: make(chan taskResponse, 1),
source: enumsspb.TASK_SOURCE_HISTORY,
effectivePriority: effectivePriorityFactor * priorityKey(request.GetPriority().GetPriorityKey()),
}
}
if t := f.GetCreateTime(); t != nil {
return t
}
}
func newInternalNexusTask(
taskID string,
deadline time.Time,
operationDeadline time.Time,
request *matchingservice.DispatchNexusTaskRequest,
return &internalTask{
nexus: &nexusTaskInfo{
taskID: taskID,
createTime: getCreateTime(request.GetForwardInfo()),
deadline: deadline,
operationDeadline: operationDeadline,
request: request,
},
forwardInfo: request.GetForwardInfo(),
responseC: make(chan taskResponse, 1),
source: enumsspb.TASK_SOURCE_HISTORY,
}
}
return &internalTask{started: info}
}
return &internalTask{effectivePriority: p, pollForwarderType: t}
}
return task.pollForwarderType != notPollForwarder
}
// isQuery returns true if the underlying task is a query task
return task.query != nil
}
// isNexus returns true if the underlying task is a nexus task
return task.nexus != nil
}
// isStarted is true when this task is already marked as started
return task.started != nil
}
// isForwarded returns true if the underlying task is forwarded by a remote matching host
// forwarded tasks are already marked as started in history
return task.forwardInfo != nil
}
return task.responseC != nil
}
if task.forwardInfo.GetCreateTime() != nil {
}
return timestamppb.Now()
}
switch {
case task.event != nil:
return &commonpb.WorkflowExecution{WorkflowId: task.event.Data.GetWorkflowId(), RunId: task.event.Data.GetRunId()}
case task.query != nil:
return task.query.request.GetQueryRequest().GetExecution()
case task.started != nil && task.started.workflowTaskInfo != nil:
return task.started.workflowTaskInfo.WorkflowExecution
case task.started != nil && task.started.activityTaskInfo != nil:
return task.started.activityTaskInfo.WorkflowExecution
}
return &commonpb.WorkflowExecution{}
}
// pollWorkflowTaskQueueResponse returns the poll response for a workflow task that is
// already marked as started. This method should only be called when isStarted() is true
func (task *internalTask) pollWorkflowTaskQueueResponse() *matchingservice.PollWorkflowTaskQueueResponse {
task.go ×1
if task.isStarted() {
return task.started.workflowTaskInfo
}
return nil
}
// pollActivityTaskQueueResponse returns the poll response for an activity task that is
// already marked as started. This method should only be called when isStarted() is true
func (task *internalTask) pollActivityTaskQueueResponse() *matchingservice.PollActivityTaskQueueResponse {
task.go ×1
if task.isStarted() {
return task.started.activityTaskInfo
}
return nil
}
// pollNexusTaskQueueResponse returns the poll response for a nexus task that is ready for dispatching. This method
// should only be called when isStarted() is true
func (task *internalTask) pollNexusTaskQueueResponse() *matchingservice.PollNexusTaskQueueResponse {
if task.isStarted() {
if task.started.nexusTaskInfo.Response != nil {
task.started.nexusTaskInfo.Response.PollerScalingDecision = task.pollerScalingDecision
}
return task.started.nexusTaskInfo
}
return nil
}
// getResponse waits for a response on the task's response channel.
if task.responseC == nil {
return taskResponse{}, false
}
}
if task.event != nil {
}
// nexus tasks don't have priorities for now
}
return fairLevelFromAllocatedTask(task.event.AllocatedTaskInfo)
}
// resetMatcherState must be called before adding or re-adding a backlog task to priMatcher.
task.removeFromMatcher.Store(&removeFuncNotAddedYet)
}
// setRemoveFunc sets the function to remove the task from the matcher.
// It returns true if the task is still valid and the function was set,
// false if the task was evicted already and should not be added.
return task.removeFromMatcher.CompareAndSwap(&removeFuncNotAddedYet, &remove)
}
// setEvicted marks the task as evicted. If it was added to a matcher it will be removed.
remove := task.removeFromMatcher.Swap(&removeFuncEvicted)
(*remove)()
}
// finish marks a task as finished. Must be called after a poller picks up a task
// and marks it as started. If the task is unable to marked as started, then this
// method should be called with a non-nil error argument.
//
// If the task took a rate limit token and didn't "use" it by actually dispatching the task,
// finish will be called with consumedToken=false and task.recycleToken=clockedRateLimiter.RecycleToken,
// so finish will call the rate limiter's RecycleToken to give the unused token back to any process
// that is waiting on the token, if one exists.
//
// When a backlog task is being dropped rather than dispatched, set r.dropReason; it is
// carried on the taskResponse and counted in tasks_dropped by the backlog completion
// callback (reader.completeTask).
task.finishInternal(taskResponse{
startErr: r.err,
dropReason: r.dropReason,
}, r.consumedToken)
}
// finishForward must be called after forwarding a task.
func (task *internalTask) finishForward(forwardRes any, forwardErr error, consumedToken bool) {
task.go ×1
task.finishInternal(taskResponse{forwarded: true, forwardRes: forwardRes, forwardErr: forwardErr}, consumedToken)
}
if !consumedToken && task.recycleToken != nil {
}
task.responseC <- res
// TODO: this probably should not be done synchronously in PollWorkflow/ActivityTaskQueue
task.event.completionFunc(task, res)
}
}