Files
orbit/sdk/go/worker_handler.go

131 lines
3.5 KiB
Go

package orbit
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"strconv"
"strings"
)
type handlerCompletion struct {
attemptID string
result WorkerFinish
}
func (w *Worker) invoke(ctx context.Context, handler WorkerHandler, execution WorkerExecution, ref WorkerAttempt, results chan<- handlerCompletion, runDone <-chan struct{}) {
out := WorkerFinish{Ref: ref, Outcome: WorkerUnknown, ErrorCode: "HANDLER_ABORTED", Result: json.RawMessage(`{}`)}
completed := false
defer func() {
if recover() != nil {
out = unknownHandlerResult(ref, "HANDLER_PANIC")
} else if !completed {
out = unknownHandlerResult(ref, "HANDLER_ABORTED")
}
w.running.Add(-1)
select {
case results <- handlerCompletion{attemptID: ref.AttemptID, result: out}:
case <-runDone:
}
}()
if ctx.Err() != nil {
out = unknownHandlerResult(ref, "LEASE_LOST")
completed = true
return
}
w.started.Add(1)
result, err := handler.Handle(ctx, execution)
if err == nil {
result = append(json.RawMessage(nil), result...)
if validHandlerResult(result) {
out = WorkerFinish{Ref: ref, Outcome: WorkerSucceeded, Result: result}
} else {
out = unknownHandlerResult(ref, "INVALID_HANDLER_RESULT")
}
} else {
var known *WorkerFailure
if errors.As(err, &known) && known != nil && workerName(known.Code, 64) {
details := append(json.RawMessage(nil), known.Details...)
if len(details) == 0 {
details = json.RawMessage(`{}`)
}
if validHandlerResult(details) {
out = WorkerFinish{Ref: ref, Outcome: WorkerFailed, ErrorCode: known.Code, Result: details, Retryable: known.Retryable}
} else {
out = unknownHandlerResult(ref, "INVALID_HANDLER_RESULT")
}
} else if errors.Is(context.Cause(ctx), ErrExecutionLeaseLost) {
out = unknownHandlerResult(ref, "LEASE_LOST")
} else {
out = unknownHandlerResult(ref, "HANDLER_ERROR")
}
}
completed = true
}
func unknownHandlerResult(ref WorkerAttempt, code string) WorkerFinish {
return WorkerFinish{Ref: ref, Outcome: WorkerUnknown, ErrorCode: code, Result: json.RawMessage(`{}`)}
}
// validHandlerResult 提前拒绝服务端无法持久化的结果,避免失效报告无限重报。
// 只检验数值范围;不把精确业务数字改写成 float64。
func validHandlerResult(raw json.RawMessage) bool {
if len(raw) == 0 || len(raw) > 256<<10 || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return false
}
// 预留 task.finish 的信封/body 嵌套,避免合法结果单独校验后在帧校验阶段超深。
frame := append([]byte(`{"body":{"result":`), raw...)
frame = append(frame, []byte(`}}`)...)
if validateWorkerJSON(frame) != nil {
return false
}
d := json.NewDecoder(bytes.NewReader(raw))
d.UseNumber()
for {
value, err := d.Token()
if err == io.EOF {
return true
}
if err != nil {
return false
}
number, ok := value.(json.Number)
if !ok {
continue
}
s := string(number)
if len(s) > 256 {
return false
}
if _, err := strconv.ParseFloat(s, 64); err != nil {
return false
}
s = strings.TrimPrefix(s, "-")
exponent := 0
if i := strings.IndexAny(s, "eE"); i >= 0 {
var err error
exponent, err = strconv.Atoi(s[i+1:])
if err != nil || exponent < -308 || exponent > 308 {
return false
}
s = s[:i]
}
if i := strings.IndexByte(s, '.'); i >= 0 {
exponent -= len(s) - i - 1
s = s[:i] + s[i+1:]
}
s = strings.TrimLeft(s, "0")
if s == "" {
continue
}
for strings.HasSuffix(s, "0") {
s = s[:len(s)-1]
exponent++
}
if exponent < -308 || exponent > 308 || exponent+len(s)-1 > 308 {
return false
}
}
}