feat: support durable task cancellation without foreign keys
This commit is contained in:
@@ -47,16 +47,17 @@ type workerFrame struct {
|
||||
// 本类型不自动重连、不自动续租、不运行处理器。关闭连接不等于取消业务效果。
|
||||
// 最多 32 个在途控制请求、8 个未读派发;超时关闭连接以便按原消息身份核对。
|
||||
type WorkerConnection struct {
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
welcome WorkerWelcome
|
||||
registration WorkerRegistration
|
||||
assignments chan WorkerAssignment
|
||||
done chan struct{}
|
||||
mu sync.Mutex
|
||||
err error
|
||||
pending map[string]chan workerFrame
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
welcome WorkerWelcome
|
||||
registration WorkerRegistration
|
||||
assignments chan WorkerAssignment
|
||||
cancellations chan WorkerCancellation
|
||||
done chan struct{}
|
||||
mu sync.Mutex
|
||||
err error
|
||||
pending map[string]chan workerFrame
|
||||
}
|
||||
|
||||
// DialWorker 使用 Authorization 和固定子协议连接;ctx 只控制握手。
|
||||
@@ -116,7 +117,7 @@ func (c *Client) DialWorker(ctx context.Context, registration WorkerRegistration
|
||||
return nil, ErrWorkerProtocol
|
||||
}
|
||||
lifetime, cancel := context.WithCancel(context.Background())
|
||||
w := &WorkerConnection{conn: conn, ctx: lifetime, cancel: cancel, welcome: welcome, registration: r, assignments: make(chan WorkerAssignment, 8), done: make(chan struct{}), pending: make(map[string]chan workerFrame)}
|
||||
w := &WorkerConnection{conn: conn, ctx: lifetime, cancel: cancel, welcome: welcome, registration: r, assignments: make(chan WorkerAssignment, 8), cancellations: make(chan WorkerCancellation, 32), done: make(chan struct{}), pending: make(map[string]chan workerFrame)}
|
||||
ok = true
|
||||
go w.readLoop()
|
||||
return w, nil
|
||||
@@ -175,6 +176,20 @@ func (w *WorkerConnection) readLoop() {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if f.Type == "task.cancel" {
|
||||
var c WorkerCancellation
|
||||
if !w.registration.CancellationNotifications || f.ReplyTo != "" || json.Unmarshal(f.Body, &c) != nil || !validID(c.TaskID) || !validID(c.AttemptID) || c.Epoch == 0 || c.RequestedAt.IsZero() {
|
||||
w.fail(ErrWorkerProtocol)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case w.cancellations <- c:
|
||||
default:
|
||||
w.fail(ErrWorkerBackpressure)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
if f.Type != "ack" && f.Type != "error" {
|
||||
w.fail(ErrWorkerProtocol)
|
||||
return
|
||||
@@ -222,6 +237,28 @@ func (w *WorkerConnection) NextAssignment(ctx context.Context) (WorkerAssignment
|
||||
}
|
||||
}
|
||||
|
||||
// NextCancellation 等待特定尝试的停止提示;需注册 CancellationNotifications 并持续读取。
|
||||
// 提示允许迟到或重复;只取消匹配 TaskID/AttemptID/Epoch 的处理器,结果仍通过 Finish 确认。
|
||||
func (w *WorkerConnection) NextCancellation(ctx context.Context) (WorkerCancellation, error) {
|
||||
if !w.registration.CancellationNotifications {
|
||||
return WorkerCancellation{}, errors.New("cancellation notifications not enabled")
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return WorkerCancellation{}, err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return WorkerCancellation{}, ctx.Err()
|
||||
case <-w.ctx.Done():
|
||||
return WorkerCancellation{}, w.Err()
|
||||
case c := <-w.cancellations:
|
||||
if err := w.Err(); err != nil {
|
||||
return WorkerCancellation{}, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkerConnection) call(ctx context.Context, kind, id string, in, out any) (time.Time, error) {
|
||||
started := time.Now()
|
||||
if !validID(id) {
|
||||
@@ -335,7 +372,7 @@ func (w *WorkerConnection) Finish(ctx context.Context, id string, in WorkerFinis
|
||||
if in.Retryable && in.Outcome != WorkerFailed {
|
||||
return out, errors.New("retryable is only valid for known failure")
|
||||
}
|
||||
if (in.Outcome != WorkerSucceeded && in.Outcome != WorkerFailed && in.Outcome != WorkerUnknown) || len(in.Result) > 256<<10 || bytes.Equal(bytes.TrimSpace(in.Result), []byte("null")) || validateWorkerJSON(in.Result) != nil {
|
||||
if (in.Outcome != WorkerSucceeded && in.Outcome != WorkerFailed && in.Outcome != WorkerUnknown && in.Outcome != WorkerCancelled) || len(in.Result) > 256<<10 || bytes.Equal(bytes.TrimSpace(in.Result), []byte("null")) || validateWorkerJSON(in.Result) != nil {
|
||||
return out, errors.New("invalid Worker result outcome or JSON")
|
||||
}
|
||||
if in.Outcome == WorkerSucceeded && in.ErrorCode != "" || in.Outcome != WorkerSucceeded && !workerName(in.ErrorCode, 64) {
|
||||
|
||||
Reference in New Issue
Block a user