Files
tasks/service/matching/task.go
zeekay 40351cce6f rip: delete temporal fork — native ZAP-only tasksd
Goes from 135 MB binary with 1153 files importing go.temporal.io and 224
importing google.golang.org/grpc to a 10 MB native binary with ZERO of
either.

Deleted:
- tasks/, tasktests/, service/, chasm/, client/, api/, proto/, tools/
- cmd/tools/, components/, docker/
- schema/{cassandra,elasticsearch,mysql,postgresql,sqlite}/
- 40 tainted common/ subdirs + 7 top-level common files
- tests/testutils (no longer referenced after surviving deps drop common)

New cmd/tasksd (~100 lines) boots tasks.Embed (zap.Node) + http.Server.
New pkg/tasks/embed.go registers ZAP handlers for the SDK opcode surface
(0x0060-0x00A5). Workflow handlers return a 501 "not yet implemented"
envelope; native engine lands behind this contract.

Build:  go build ./cmd/tasksd → 10 MB
Vet:    go vet ./... → clean
Boot:   tasksd --zap-port 9999 --http :7243
Probes: /healthz, /v1/tasks/health → 200
UI:     /_/tasks/, / → 200 (embedded Vite bundle)
ZAP:    _tasks._tcp on :9999 (mDNS disabled; direct connect)

go.mod: tidy. No go.temporal.io. No google.golang.org/grpc.
2026-04-26 14:06:21 -07:00

376 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
)
var (
// sentinel values for task.removeFromMatcher
removeFuncNotAddedYet = func() {}
removeFuncEvicted = func() {}
)
func (res taskResponse) err() error {
if res.forwarded {
return res.forwardErr
}
return res.startErr
}
func newInternalTaskForSyncMatch(
info *persistencespb.TaskInfo,
forwardInfo *taskqueuespb.TaskForwardInfo,
taskDispatchRevisionNumber int64,
targetVersion *deploymentspb.WorkerDeploymentVersion,
) *internalTask {
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
source = forwardInfo.TaskSource
redirectInfo = forwardInfo.GetRedirectInfo()
}
return &internalTask{
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),
) *internalTask {
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,
) *internalTask {
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()),
}
}
func getCreateTime(f *taskqueuespb.TaskForwardInfo) *timestamppb.Timestamp {
if t := f.GetCreateTime(); t != nil {
return t
}
return timestamppb.Now()
}
func newInternalNexusTask(
taskID string,
deadline time.Time,
operationDeadline time.Time,
request *matchingservice.DispatchNexusTaskRequest,
) *internalTask {
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,
}
}
func newInternalStartedTask(info *startedTaskInfo) *internalTask {
return &internalTask{started: info}
}
func newPollForwarderTask(p priorityKey, t pollForwarderType) *internalTask {
return &internalTask{effectivePriority: p, pollForwarderType: t}
}
func (task *internalTask) isPollForwarder() bool {
return task.pollForwarderType != notPollForwarder
}
// isQuery returns true if the underlying task is a query task
func (task *internalTask) isQuery() bool {
return task.query != nil
}
// isNexus returns true if the underlying task is a nexus task
func (task *internalTask) isNexus() bool {
return task.nexus != nil
}
// isStarted is true when this task is already marked as started
func (task *internalTask) isStarted() bool {
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
func (task *internalTask) isForwarded() bool {
return task.forwardInfo != nil
}
func (task *internalTask) isSyncMatchTask() bool {
return task.responseC != nil
}
func (task *internalTask) getCreateTime() *timestamppb.Timestamp {
if task.forwardInfo.GetCreateTime() != nil {
return task.forwardInfo.GetCreateTime()
} else if task.event != nil {
return task.event.Data.GetCreateTime()
} else if task.query != nil {
return task.query.createTime
} else if task.nexus != nil {
return task.nexus.createTime
}
return timestamppb.Now()
}
func (task *internalTask) workflowExecution() *commonpb.WorkflowExecution {
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 {
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 {
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.
func (task *internalTask) getResponse() (taskResponse, bool) {
if task.responseC == nil {
return taskResponse{}, false
}
return <-task.responseC, true
}
func (task *internalTask) getPriority() *commonpb.Priority {
if task.event != nil {
return task.event.AllocatedTaskInfo.GetData().GetPriority()
} else if task.query != nil {
return task.query.request.GetPriority()
}
// nexus tasks don't have priorities for now
return nil
}
func (task *internalTask) fairLevel() fairLevel {
return fairLevelFromAllocatedTask(task.event.AllocatedTaskInfo)
}
// resetMatcherState must be called before adding or re-adding a backlog task to priMatcher.
func (task *internalTask) resetMatcherState() {
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.
func (task *internalTask) setRemoveFunc(remove func()) bool {
return task.removeFromMatcher.CompareAndSwap(&removeFuncNotAddedYet, &remove)
}
// setEvicted marks the task as evicted. If it was added to a matcher it will be removed.
func (task *internalTask) setEvicted() {
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 wasValid=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.
func (task *internalTask) finish(err error, wasValid bool) {
res := taskResponse{startErr: err}
task.finishInternal(res, wasValid)
}
// finishForward must be called after forwarding a task.
func (task *internalTask) finishForward(forwardRes any, forwardErr error, wasValid bool) {
res := taskResponse{forwarded: true, forwardRes: forwardRes, forwardErr: forwardErr}
task.finishInternal(res, wasValid)
}
func (task *internalTask) finishInternal(res taskResponse, wasValid bool) {
if !wasValid && task.recycleToken != nil {
task.recycleToken(task)
}
switch {
case task.responseC != nil:
task.responseC <- res
case task.event.completionFunc != nil:
// TODO: this probably should not be done synchronously in PollWorkflow/ActivityTaskQueue
task.event.completionFunc(task, res)
}
}