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

289 lines
8.2 KiB
Go

package cassandra
import (
"context"
"fmt"
"strings"
"sync"
"time"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/api/serviceerror"
"go.temporal.io/server/common/convert"
p "go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
"go.temporal.io/server/common/primitives/timestamp"
)
// matchingTaskVersion represents the task schema version
type matchingTaskVersion int
const (
matchingTaskVersion1 matchingTaskVersion = 1
matchingTaskVersion2 matchingTaskVersion = 2
)
var switchTasksTableV1Cache sync.Map
// switchTasksTable switches table names from tasks to tasks_v2 and modifies queries for v2 schema
func switchTasksTable(baseQuery string, v matchingTaskVersion) string {
if v == matchingTaskVersion2 {
return baseQuery
} else if v != matchingTaskVersion1 {
panic("invalid task schema version") // nolint:forbidigo // hardcoded constants
}
if v1query, ok := switchTasksTableV1Cache.Load(baseQuery); ok {
return v1query.(string) // nolint:revive
}
v1query := strings.ReplaceAll(baseQuery, " tasks_v2 ", " tasks ")
v1query = strings.ReplaceAll(v1query, " AND pass = 0 ", " ")
v1query = strings.ReplaceAll(v1query, "type, pass, task_id", "type, task_id")
v1query = strings.ReplaceAll(v1query, "?, 0, ?", "?, ?")
switchTasksTableV1Cache.Store(baseQuery, v1query)
return v1query
}
// Task queue management queries, written for v2 (rewritten for v1 by switchTasksTable)
const (
templateGetTaskQueueQuery = `SELECT ` +
`range_id, ` +
`task_queue, ` +
`task_queue_encoding ` +
`FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`AND task_queue_name = ? ` +
`AND task_queue_type = ? ` +
`AND type = ? ` +
`AND pass = 0 ` +
`AND task_id = ?`
templateInsertTaskQueueQuery = `INSERT INTO tasks_v2 ` +
`(namespace_id, task_queue_name, task_queue_type, type, pass, task_id, range_id, task_queue, task_queue_encoding) ` +
`VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?) IF NOT EXISTS`
templateUpdateTaskQueueQuery = `UPDATE tasks_v2 SET ` +
`range_id = ?, ` +
`task_queue = ?, ` +
`task_queue_encoding = ? ` +
`WHERE namespace_id = ? ` +
`AND task_queue_name = ? ` +
`AND task_queue_type = ? ` +
`AND type = ? ` +
`AND pass = 0 ` +
`AND task_id = ? ` +
`IF range_id = ?`
// templateCheckRangeIDQuery is a lightweight transaction that checks
// the range_id without updating the metadata blob. Used for write
// fencing on task appends when a full metadata update is not needed.
templateCheckRangeIDQuery = `UPDATE tasks_v2 SET ` +
`range_id = ? ` +
`WHERE namespace_id = ? ` +
`AND task_queue_name = ? ` +
`AND task_queue_type = ? ` +
`AND type = ? ` +
`AND pass = 0 ` +
`AND task_id = ? ` +
`IF range_id = ?`
templateUpdateTaskQueueQueryWithTTLPart1 = `INSERT INTO tasks_v2 ` +
`(namespace_id, task_queue_name, task_queue_type, type, pass, task_id) ` +
`VALUES (?, ?, ?, ?, 0, ?) USING TTL ?`
templateUpdateTaskQueueQueryWithTTLPart2 = `UPDATE tasks_v2 USING TTL ? SET ` +
`range_id = ?, ` +
`task_queue = ?, ` +
`task_queue_encoding = ? ` +
`WHERE namespace_id = ? ` +
`AND task_queue_name = ? ` +
`AND task_queue_type = ? ` +
`AND type = ? ` +
`AND pass = 0 ` +
`AND task_id = ? ` +
`IF range_id = ?`
templateDeleteTaskQueueQuery = `DELETE FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`AND task_queue_name = ? ` +
`AND task_queue_type = ? ` +
`AND type = ? ` +
`AND pass = 0 ` +
`AND task_id = ? ` +
`IF range_id = ?`
)
// taskQueueStore handles unified task queue operations for both v1 and v2
type taskQueueStore struct {
Session gocql.Session
version matchingTaskVersion
}
func (d *taskQueueStore) CreateTaskQueue(
ctx context.Context,
request *p.InternalCreateTaskQueueRequest,
) error {
query := d.Session.Query(switchTasksTable(templateInsertTaskQueueQuery, d.version),
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowTypeTaskQueue,
taskQueueTaskID,
request.RangeID,
request.TaskQueueInfo.Data,
request.TaskQueueInfo.EncodingType.String(),
).WithContext(ctx)
previous := make(map[string]any)
applied, err := query.MapScanCAS(previous)
if err != nil {
return gocql.ConvertError("CreateTaskQueue", err)
}
if !applied {
previousRangeID := previous["range_id"]
return &p.ConditionFailedError{
Msg: fmt.Sprintf("CreateTaskQueue: TaskQueue:%v, TaskQueueType:%v, PreviousRangeID:%v",
request.TaskQueue, request.TaskType, previousRangeID),
}
}
return nil
}
func (d *taskQueueStore) GetTaskQueue(
ctx context.Context,
request *p.InternalGetTaskQueueRequest,
) (*p.InternalGetTaskQueueResponse, error) {
query := d.Session.Query(switchTasksTable(templateGetTaskQueueQuery, d.version),
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowTypeTaskQueue,
taskQueueTaskID,
).WithContext(ctx)
var rangeID int64
var tlBytes []byte
var tlEncoding string
if err := query.Scan(&rangeID, &tlBytes, &tlEncoding); err != nil {
return nil, gocql.ConvertError("GetTaskQueue", err)
}
return &p.InternalGetTaskQueueResponse{
RangeID: rangeID,
TaskQueueInfo: p.NewDataBlob(tlBytes, tlEncoding),
}, nil
}
func (d *taskQueueStore) UpdateTaskQueue(
ctx context.Context,
request *p.InternalUpdateTaskQueueRequest,
) (*p.UpdateTaskQueueResponse, error) {
var err error
var applied bool
previous := make(map[string]any)
if d.version == matchingTaskVersion1 && request.TaskQueueKind == enumspb.TASK_QUEUE_KIND_STICKY {
// V1 TTL logic - only applies to V1
if request.ExpiryTime == nil {
return nil, serviceerror.NewInternal("ExpiryTime cannot be nil for sticky task queue")
}
expiryTTL := convert.Int64Ceil(time.Until(timestamp.TimeValue(request.ExpiryTime)).Seconds())
if expiryTTL >= maxCassandraTTL {
expiryTTL = maxCassandraTTL
}
batch := d.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
batch.Query(switchTasksTable(templateUpdateTaskQueueQueryWithTTLPart1, d.version),
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowTypeTaskQueue,
taskQueueTaskID,
expiryTTL,
)
batch.Query(switchTasksTable(templateUpdateTaskQueueQueryWithTTLPart2, d.version),
expiryTTL,
request.RangeID,
request.TaskQueueInfo.Data,
request.TaskQueueInfo.EncodingType.String(),
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowTypeTaskQueue,
taskQueueTaskID,
request.PrevRangeID,
)
applied, _, err = d.Session.MapExecuteBatchCAS(batch, previous)
} else {
// Regular update logic for both V1 and V2
query := d.Session.Query(switchTasksTable(templateUpdateTaskQueueQuery, d.version),
request.RangeID,
request.TaskQueueInfo.Data,
request.TaskQueueInfo.EncodingType.String(),
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowTypeTaskQueue,
taskQueueTaskID,
request.PrevRangeID,
).WithContext(ctx)
applied, err = query.MapScanCAS(previous)
}
if err != nil {
return nil, gocql.ConvertError("UpdateTaskQueue", err)
}
if !applied {
var columns []string
for k, v := range previous {
columns = append(columns, fmt.Sprintf("%s=%v", k, v))
}
return nil, &p.ConditionFailedError{
Msg: fmt.Sprintf("Failed to update task queue. name: %v, type: %v, rangeID: %v, columns: (%v)",
request.TaskQueue, request.TaskType, request.RangeID, strings.Join(columns, ",")),
}
}
return &p.UpdateTaskQueueResponse{}, nil
}
func (d *taskQueueStore) ListTaskQueue(
_ context.Context,
_ *p.ListTaskQueueRequest,
) (*p.InternalListTaskQueueResponse, error) {
return nil, serviceerror.NewUnavailable("unsupported operation")
}
func (d *taskQueueStore) DeleteTaskQueue(
ctx context.Context,
request *p.DeleteTaskQueueRequest,
) error {
query := d.Session.Query(switchTasksTable(templateDeleteTaskQueueQuery, d.version),
request.TaskQueue.NamespaceID,
request.TaskQueue.TaskQueueName,
request.TaskQueue.TaskQueueType,
rowTypeTaskQueue,
taskQueueTaskID,
request.RangeID,
).WithContext(ctx)
previous := make(map[string]any)
applied, err := query.MapScanCAS(previous)
if err != nil {
return gocql.ConvertError("DeleteTaskQueue", err)
}
if !applied {
return &p.ConditionFailedError{
Msg: fmt.Sprintf("DeleteTaskQueue operation failed: expected_range_id=%v but found %+v", request.RangeID, previous),
}
}
return nil
}