Files
zeekay 7006ef3638 feat(sdk/worker): real wire-backed workflow env + FailureCommand
Replaces StubEnv use in dispatch.go with workerEnv, a real
CoroutineEnv implementation. Activity dispatch now goes over the
wire via ScheduleActivity (opcode 0x006B) + long-poll
WaitActivityResult (0x006C), not a pre-populated in-memory map.
NewTimer uses time.NewTimer in a per-timer goroutine; Select uses
a fan-in wake channel with a 20ms watcher poll (no per-iteration
allocation). Root cancel scope propagates to timers, activity polls,
and child scopes on return.

Workflow return errors are no longer swallowed — non-nil fn returns
get encoded via temporal.Encode into a single FailureCommand entry
in the commands envelope so the frontend can fail the execution
per its retry policy.

Phase-1 note documented in struct comment: every dispatch re-runs
the workflow from the top with the same input — activities must be
idempotent. Phase 2 replaces this with event-sourced replay
without changing the public surface.

Adds WorkerTransport.ScheduleActivity / WaitActivityResult /
StartChildWorkflow with v1 JSON-in-envelope bodies. Schema
schema/tasks.zap gains the 0x006B/0x006C struct definitions.
2026-04-23 12:01:27 -07:00

46 lines
1.2 KiB
Go

package workflow
import (
"reflect"
"runtime"
"strings"
)
// ActivityName returns the registered wire name for an activity
// value. String activities are returned as-is; function values are
// reflected to the short Go identifier (pkg.Fn → Fn). Nil yields
// "nil". Mirrors the upstream Temporal default.
func ActivityName(v any) string {
if v == nil {
return "nil"
}
if s, ok := v.(string); ok {
return s
}
return funcName(v)
}
// funcName returns the best-effort name for an activity function
// value. Used by the stub env to key its registered responses and
// by the worker at Phase 2 to dispatch by registered name.
//
// Kept in its own file so the reflect dependency is obvious and
// easy to audit — this is the only place the workflow package
// reflects.
func funcName(v any) string {
rv := reflect.ValueOf(v)
if !rv.IsValid() || rv.Kind() != reflect.Func {
return "unknown"
}
rf := runtime.FuncForPC(rv.Pointer())
if rf == nil {
return "unknown"
}
full := rf.Name() // e.g. github.com/x/y/z.DoWork
// Trim to "DoWork" — what Temporal also does by default.
if i := strings.LastIndex(full, "."); i >= 0 && i < len(full)-1 {
return full[i+1:]
}
return full
}