Files
tasks/chasm/lib/scheduler/handler.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

283 lines
8.0 KiB
Go

package scheduler
import (
"context"
"errors"
"fmt"
"go.temporal.io/api/serviceerror"
"go.temporal.io/api/workflowservice/v1"
"go.temporal.io/server/chasm"
"go.temporal.io/server/chasm/lib/scheduler/gen/schedulerpb/v1"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
legacyscheduler "go.temporal.io/server/service/worker/scheduler"
)
type handler struct {
schedulerpb.UnimplementedSchedulerServiceServer
logger log.Logger
specBuilder *legacyscheduler.SpecBuilder
}
func newHandler(logger log.Logger, specBuilder *legacyscheduler.SpecBuilder) *handler {
return &handler{
logger: logger,
specBuilder: specBuilder,
}
}
func (h *handler) CreateSchedule(ctx context.Context, req *schedulerpb.CreateScheduleRequest) (resp *schedulerpb.CreateScheduleResponse, err error) {
defer log.CapturePanic(h.logger, &err)
_, err = chasm.StartExecution(
ctx,
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.FrontendRequest.ScheduleId,
},
CreateScheduler,
req,
chasm.WithRequestID(req.FrontendRequest.RequestId),
)
var alreadyStartedErr *chasm.ExecutionAlreadyStartedError
if errors.As(err, &alreadyStartedErr) {
// Check if the existing schedule is a sentinel.
//
// TODO lina@ - this can be removed (as well as all other sentinel business)
// after fully migrated to CHASM schedulers.
_, readErr := chasm.ReadComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.FrontendRequest.ScheduleId,
},
),
func(s *Scheduler, ctx chasm.Context, _ *struct{}) (*struct{}, error) {
if s.IsSentinel() {
return nil, ErrSentinel
}
return nil, nil
},
(*struct{})(nil),
)
if readErr != nil {
return nil, readErr // Returns ErrSentinel (404) if sentinel
}
return nil, serviceerror.NewAlreadyExistsf("schedule %q is already registered", req.FrontendRequest.ScheduleId)
}
return &schedulerpb.CreateScheduleResponse{
FrontendResponse: &workflowservice.CreateScheduleResponse{
ConflictToken: initialSerializedConflictToken,
},
}, err
}
// CreateFromMigrationState creates a CHASM schedule from migrated V1 state.
// Used during migration from workflow-backed schedules to CHASM schedules.
func (h *handler) CreateFromMigrationState(ctx context.Context, req *schedulerpb.CreateFromMigrationStateRequest) (resp *schedulerpb.CreateFromMigrationStateResponse, err error) {
defer log.CapturePanic(h.logger, &err)
scheduleID := req.GetState().GetSchedulerState().GetScheduleId()
_, err = chasm.StartExecution(
ctx,
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: scheduleID,
},
CreateSchedulerFromMigration,
req,
)
var alreadyStartedErr *chasm.ExecutionAlreadyStartedError
if errors.As(err, &alreadyStartedErr) {
// Check if the existing schedule is a sentinel. Sentinels are
// auto-deleted SentinelIdleTime after schedule creation; the
// V1 schedule will keep retrying migration until it expires.
_, readErr := chasm.ReadComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: scheduleID,
},
),
func(s *Scheduler, ctx chasm.Context, _ *struct{}) (*struct{}, error) {
if s.IsSentinel() {
return nil, ErrSentinel
}
return nil, nil
},
(*struct{})(nil),
)
if readErr != nil {
if errors.Is(readErr, ErrSentinel) {
h.logger.Warn(
fmt.Sprintf("Migration blocked by sentinel schedule; sentinel will auto-delete %v after schedule creation", SentinelIdleTime),
tag.NewStringTag("schedule-id", scheduleID),
)
return nil, ErrSentinelBlocked
}
return nil, readErr
}
return nil, serviceerror.NewAlreadyExistsf("schedule %q is already registered", scheduleID)
}
return &schedulerpb.CreateFromMigrationStateResponse{}, err
}
func (h *handler) CreateSentinel(ctx context.Context, req *schedulerpb.CreateSentinelRequest) (resp *schedulerpb.CreateSentinelResponse, err error) {
defer log.CapturePanic(h.logger, &err)
_, err = chasm.StartExecution(
ctx,
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.ScheduleId,
},
CreateSentinelFn,
req,
)
var alreadyStartedErr *chasm.ExecutionAlreadyStartedError
if errors.As(err, &alreadyStartedErr) {
// If a sentinel already exists, succeed idempotently.
// If a real scheduler exists, fail.
_, readErr := chasm.ReadComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.ScheduleId,
},
),
func(s *Scheduler, ctx chasm.Context, _ *struct{}) (*struct{}, error) {
if s.IsSentinel() {
return nil, nil
}
return nil, serviceerror.NewAlreadyExistsf("schedule %q is already registered", req.ScheduleId)
},
(*struct{})(nil),
)
return &schedulerpb.CreateSentinelResponse{}, readErr
}
if err != nil {
return nil, err
}
return &schedulerpb.CreateSentinelResponse{}, nil
}
func (h *handler) UpdateSchedule(ctx context.Context, req *schedulerpb.UpdateScheduleRequest) (resp *schedulerpb.UpdateScheduleResponse, err error) {
defer log.CapturePanic(h.logger, &err)
resp, _, err = chasm.UpdateComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.FrontendRequest.ScheduleId,
},
),
(*Scheduler).Update,
req,
)
return resp, err
}
func (h *handler) PatchSchedule(ctx context.Context, req *schedulerpb.PatchScheduleRequest) (resp *schedulerpb.PatchScheduleResponse, err error) {
defer log.CapturePanic(h.logger, &err)
resp, _, err = chasm.UpdateComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.FrontendRequest.ScheduleId,
},
),
(*Scheduler).Patch,
req,
)
return resp, err
}
func (h *handler) DeleteSchedule(ctx context.Context, req *schedulerpb.DeleteScheduleRequest) (resp *schedulerpb.DeleteScheduleResponse, err error) {
defer log.CapturePanic(h.logger, &err)
resp, _, err = chasm.UpdateComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.FrontendRequest.ScheduleId,
},
),
(*Scheduler).Delete,
req,
)
return resp, err
}
func (h *handler) MigrateToWorkflow(ctx context.Context, req *schedulerpb.MigrateToWorkflowRequest) (resp *schedulerpb.MigrateToWorkflowResponse, err error) {
defer log.CapturePanic(h.logger, &err)
resp, _, err = chasm.UpdateComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.ScheduleId,
},
),
(*Scheduler).MigrateToWorkflow,
req,
)
if errors.Is(err, ErrSentinel) {
return nil, ErrSentinelBlocked
}
return resp, err
}
func (h *handler) DescribeSchedule(ctx context.Context, req *schedulerpb.DescribeScheduleRequest) (resp *schedulerpb.DescribeScheduleResponse, err error) {
defer log.CapturePanic(h.logger, &err)
resp, err = chasm.ReadComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.FrontendRequest.ScheduleId,
},
),
func(s *Scheduler, ctx chasm.Context, req *schedulerpb.DescribeScheduleRequest) (*schedulerpb.DescribeScheduleResponse, error) {
return s.Describe(ctx, req, h.specBuilder)
},
req,
)
return resp, err
}
func (h *handler) ListScheduleMatchingTimes(ctx context.Context, req *schedulerpb.ListScheduleMatchingTimesRequest) (resp *schedulerpb.ListScheduleMatchingTimesResponse, err error) {
defer log.CapturePanic(h.logger, &err)
resp, err = chasm.ReadComponent(
ctx,
chasm.NewComponentRef[*Scheduler](
chasm.ExecutionKey{
NamespaceID: req.NamespaceId,
BusinessID: req.FrontendRequest.ScheduleId,
},
),
func(s *Scheduler, ctx chasm.Context, req *schedulerpb.ListScheduleMatchingTimesRequest) (*schedulerpb.ListScheduleMatchingTimesResponse, error) {
return s.ListMatchingTimes(ctx, req, h.specBuilder)
},
req,
)
return resp, err
}