feat: add automatic Worker runtime with lease recovery and bounded shutdown
This commit is contained in:
130
sdk/go/worker_handler.go
Normal file
130
sdk/go/worker_handler.go
Normal file
@@ -0,0 +1,130 @@
|
||||
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}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user