Skip to content

fix(channels): nack() 缺少幂等性保护,重复调用会多次触发 onNack 回调 #104903

Description

@evan-YM

问题描述

src/channels/message/receive.tscreateMessageReceiveContextnack() 方法缺少幂等性保护。

对比

ack() 方法有明确的幂等性守卫(第 82-84 行):

ack: async () => {
  // Ack callbacks must be idempotent because receive pipelines may revisit completed stages.
  if (ctx.ackState === "acked") {
    return;
  }
  await params.onAck?.();
  ctx.ackState = "acked";
  ctx.ackedAt = Date.now();
  delete ctx.nackErrorMessage;
},

nack() 方法没有对应的守卫(第 90-94 行):

nack: async (error) => {
  await params.onNack?.(error);      // 多次调用会重复触发回调
  ctx.ackState = "nacked";
  ctx.nackErrorMessage = normalizeAckErrorMessage(error);
},

影响

多次调用 nack() 会重复触发 onNack 回调。在 receive pipeline 重访已完成阶段的场景下(与 ack() 注释中描述的场景相同),可能导致重复的错误处理、重复的清理操作或重复的日志记录。

复现

const ctx = createMessageReceiveContext({
  id: "test",
  channel: "test",
  message: {},
  onNack: () => console.log("nack called"),  // 会被调用两次
});
await ctx.nack(new Error("first"));
await ctx.nack(new Error("second"));  // 第二次调用,onNack 再次触发

预期行为

nack() 应该在 ackState 不是 "pending" 时直接返回,与 ack() 的行为对称。

修复方案

nack() 开头加入幂等性检查:

nack: async (error) => {
  if (ctx.ackState !== "pending") {
    return;
  }
  await params.onNack?.(error);
  ctx.ackState = "nacked";
  ctx.nackErrorMessage = normalizeAckErrorMessage(error);
},

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.no-staleExclude from stale automation

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions