Files
tasks/common/persistence/cassandra/matching_task_store_v2.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

256 lines
7.2 KiB
Go

package cassandra
import (
"context"
"fmt"
"math"
"go.temporal.io/api/serviceerror"
p "go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
)
const (
templateCreateTaskQuery_v2 = `INSERT INTO tasks_v2 (` +
`namespace_id, task_queue_name, task_queue_type, type, pass, task_id, task, task_encoding) ` +
`VALUES(?, ?, ?, ?, ?, ?, ?, ?)`
templateGetTasksQuery_v2 = `SELECT task_id, task, task_encoding ` +
`FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`and task_queue_name = ? ` +
`and task_queue_type = ? ` +
`and (type, pass, task_id) >= (?, ?, ?) ` +
`and (type, pass, task_id) < (?, ?, ?)`
templateGetTasksQuery_v2_limit = `SELECT task_id, task, task_encoding ` +
`FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`and task_queue_name = ? ` +
`and task_queue_type = ? ` +
`and (type, pass, task_id) >= (?, ?, ?) ` +
`and (type, pass, task_id) < (?, ?, ?) ` +
`LIMIT ?`
templateCompleteTasksLessThanQuery_v2 = `DELETE FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`AND task_queue_name = ? ` +
`AND task_queue_type = ? ` +
`AND (type, pass, task_id) >= (?, ?, ?) ` +
`AND (type, pass, task_id) < (?, ?, ?)`
)
// matchingTaskStoreV2 is a fork of matchingTaskStoreV1 that uses a new task schema.
// Task queue operations are now unified with V1 through query rewriting.
type matchingTaskStoreV2 struct {
Session gocql.Session
userDataStore
taskQueueStore
}
func newMatchingTaskStoreV2(
session gocql.Session,
) *matchingTaskStoreV2 {
return &matchingTaskStoreV2{
Session: session,
userDataStore: userDataStore{Session: session},
taskQueueStore: taskQueueStore{Session: session, version: matchingTaskVersion2},
}
}
// CreateTasks add tasks
func (d *matchingTaskStoreV2) CreateTasks(
ctx context.Context,
request *p.InternalCreateTasksRequest,
) (*p.CreateTasksResponse, error) {
batch := d.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
namespaceID := request.NamespaceID
taskQueue := request.TaskQueue
taskQueueType := request.TaskType
for _, task := range request.Tasks {
if task.TaskPass == 0 {
return nil, serviceerror.NewInternal("invalid fair queue task missing pass number")
}
batch.Query(templateCreateTaskQuery_v2,
namespaceID,
taskQueue,
taskQueueType,
rowTypeTaskInSubqueue(task.Subqueue),
task.TaskPass,
task.TaskId,
task.Task.Data,
task.Task.EncodingType.String())
}
// The following query is used to ensure that range_id didn't change.
// When UpdateMetadata is true, we also write the metadata blob (backlog counts, etc.).
// When false, we only check the range_id for write fencing.
if request.UpdateMetadata {
batch.Query(switchTasksTable(templateUpdateTaskQueueQuery, matchingTaskVersion2),
request.RangeID,
request.TaskQueueInfo.Data,
request.TaskQueueInfo.EncodingType.String(),
namespaceID,
taskQueue,
taskQueueType,
rowTypeTaskQueue,
taskQueueTaskID,
request.RangeID,
)
} else {
batch.Query(switchTasksTable(templateCheckRangeIDQuery, matchingTaskVersion2),
request.RangeID,
namespaceID,
taskQueue,
taskQueueType,
rowTypeTaskQueue,
taskQueueTaskID,
request.RangeID,
)
}
previous := make(map[string]any)
applied, _, err := d.Session.MapExecuteBatchCAS(batch, previous)
if err != nil {
return nil, gocql.ConvertError("CreateTasks", err)
}
if !applied {
rangeID := previous["range_id"]
return nil, &p.ConditionFailedError{
Msg: fmt.Sprintf("Failed to create task. TaskQueue: %v, taskQueueType: %v, rangeID: %v, db rangeID: %v",
taskQueue, taskQueueType, request.RangeID, rangeID),
}
}
return &p.CreateTasksResponse{UpdatedMetadata: request.UpdateMetadata}, nil
}
// GetTasks get a task
func (d *matchingTaskStoreV2) GetTasks(
ctx context.Context,
request *p.GetTasksRequest,
) (*p.InternalGetTasksResponse, error) {
if request.InclusiveMinPass < 1 {
return nil, serviceerror.NewInternal("invalid GetTasks request on fair queue: InclusiveMinPass must be >= 1")
}
if request.ExclusiveMaxTaskID != math.MaxInt64 {
// ExclusiveMaxTaskID is not supported in fair queue.
return nil, serviceerror.NewInternal("invalid GetTasks request on fair queue: ExclusiveMaxTaskID is not supported")
}
// Reading taskqueue tasks need to be quorum level consistent, otherwise we could lose tasks
var query gocql.Query
rowType := rowTypeTaskInSubqueue(request.Subqueue)
if request.UseLimit {
query = d.Session.Query(templateGetTasksQuery_v2_limit,
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowType,
request.InclusiveMinPass,
request.InclusiveMinTaskID,
rowType,
int64(math.MaxInt64),
int64(math.MaxInt64),
request.PageSize,
)
} else {
query = d.Session.Query(templateGetTasksQuery_v2,
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowType,
request.InclusiveMinPass,
request.InclusiveMinTaskID,
rowType,
int64(math.MaxInt64),
int64(math.MaxInt64),
)
}
iter := query.WithContext(ctx).PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
response := &p.InternalGetTasksResponse{}
task := make(map[string]any)
for iter.MapScan(task) {
_, ok := task["task_id"]
if !ok { // no tasks, but static column record returned
continue
}
rawTask, ok := task["task"]
if !ok {
return nil, newFieldNotFoundError("task", task)
}
taskVal, ok := rawTask.([]byte)
if !ok {
var byteSliceType []byte
return nil, newPersistedTypeMismatchError("task", byteSliceType, rawTask, task)
}
rawEncoding, ok := task["task_encoding"]
if !ok {
return nil, newFieldNotFoundError("task_encoding", task)
}
encodingVal, ok := rawEncoding.(string)
if !ok {
var byteSliceType []byte
return nil, newPersistedTypeMismatchError("task_encoding", byteSliceType, rawEncoding, task)
}
response.Tasks = append(response.Tasks, p.NewDataBlob(taskVal, encodingVal))
task = make(map[string]any) // Reinitialize map as initialized fails on unmarshalling
}
if len(iter.PageState()) > 0 {
response.NextPageToken = iter.PageState()
}
if err := iter.Close(); err != nil {
return nil, gocql.ConvertError("GetTasks", err)
}
return response, nil
}
// CompleteTasksLessThan deletes all tasks less than the given task id. This API ignores the
// Limit request parameter i.e. either all tasks leq the task_id will be deleted or an error will
// be returned to the caller
func (d *matchingTaskStoreV2) CompleteTasksLessThan(
ctx context.Context,
request *p.CompleteTasksLessThanRequest,
) (int, error) {
// Require starting from pass 1.
if request.ExclusiveMaxPass < 1 {
return 0, serviceerror.NewInternal("invalid CompleteTasksLessThan request on fair queue")
}
rowType := rowTypeTaskInSubqueue(request.Subqueue)
query := d.Session.Query(
templateCompleteTasksLessThanQuery_v2,
request.NamespaceID,
request.TaskQueueName,
request.TaskType,
rowType,
int64(0),
int64(0),
rowType,
request.ExclusiveMaxPass,
request.ExclusiveMaxTaskID,
).WithContext(ctx)
err := query.Exec()
if err != nil {
return 0, gocql.ConvertError("CompleteTasksLessThan", err)
}
return p.UnknownNumRowsAffected, nil
}
func (d *matchingTaskStoreV2) GetName() string {
return cassandraPersistenceName
}
func (d *matchingTaskStoreV2) Close() {
if d.Session != nil {
d.Session.Close()
}
}