外观
错误与结果核对
先区分“请求被明确拒绝”“仍在执行”“结果无法确认”。对游戏写入,超时和 unknown 都不能推出“没有执行”,也不能据此换键重试。
操作回执
await ctx.operations.get(operation_id) 和 wait(...) 返回普通字典:
text
OperationReceipt = {
request_id: str,
operation_id: str,
state: "accepted" | "dispatching" | "running" |
"succeeded" | "failed" | "cancelled" | "unknown",
expires_at: str,
result: ActionResult | None,
can_retry: false
}| 状态 | 处理方式 |
|---|---|
accepted、dispatching、running | 保存原 ID,继续查询;不要重复提交新动作。 |
succeeded | 读取证据和具体结果,再判断是否满足业务目标。 |
failed | 读取原因与可能的部分效果;修正问题不等于可以直接重放。 |
cancelled | 查看部分效果和取消边界,不假设世界已经回滚。 |
unknown | 保留原意图、ID、幂等键和期限,停止自动重试,核对实际状态。 |
终态 result 至少含相同的 state,可选字段有 evidence、reason、result、partial_effects、recovery_required。字段缺失时不要自行填出成功证据。can_retry 为 false,不是通用重试建议。
python
receipt = await ctx.operations.get(operation_id)
if receipt["state"] == "unknown":
ctx.log.warning("动作结果未确认,需核对原收据",
extra={"operation_id": operation_id})
elif receipt["state"] == "succeeded":
evidence = (receipt.get("result") or {}).get("evidence")
ctx.log.info("动作已完成,证据=%s", evidence,
extra={"operation_id": operation_id})此片段只报告状态,不在错误分支重新发起动作。evidence 的含义取决于操作;服务器命令回应不能证明玩家看见画面或业务全部达成。
SDK 异常
| 异常及导入模块 | 应保留的信息与处理 |
|---|---|
IPCRejected,neomega_runtime.managed | code、request_id、retryable;明确的 Host 拒绝,按具体原因修正。 |
OperationSubmissionTimeout / OperationSubmissionError,neomega_runtime.operations | .intent;提交是否被接受不确定,保留原请求,不生成新键。 |
OperationWaitTimeout,neomega_runtime.operations | .operation_id;只是等待到期,原动作未因此取消,查询同一 ID。 |
OperationCancellationUncertain,neomega_runtime.operations | .operation_id;取消回应不确定,查询原操作。 |
CommitUncertain,neomega_runtime.storage | .intent;数据库提交回应丢失或无效,核对原 commit_id。 |
ServiceUncertain / ServiceWaitTimeout,neomega_runtime.services | 原 .intent / .call_id;在线提供方可能仍执行,不重新调用。 |
ServiceFailed / ServiceRejected,neomega_runtime.services | .receipt、.call_id,后者还有 .code;区分调用失败与预期业务拒绝。 |
BusinessConflict,neomega_runtime.business | 同业务 ID 对应不同请求;核对原业务记录,不换 ID 绕过。 |
ProtocolError,neomega_runtime.http | 协议结构或字段无效;是否可能已有副作用取决于发生在准备、提交还是查询阶段。 |
IPCError,neomega_runtime.managed | 底层连接或运行时错误;不能仅凭断线认定动作未执行。 |
IPCRejected.retryable 是 Host 的拒绝信息,不是允许重放有副作用调用的通行证。服务回执受 generation 与内存保留边界约束,详见在线服务。
本地参数校验还可能抛出 ValueError、TypeError,配置使用 ConfigError,密钥使用 SecretError,玩家匹配使用 PlayerNotFound 或 AmbiguousPlayer。这些都需要按发生位置处理,不能统一转换成“发奖失败,请再试一次”。
事务回应丢失
tx.prepare() 得到 CommitIntent,其中含固定的 commit_id、状态修改和动作意图。save 成功时业务状态已提交,动作仍有各自终态;不确定时可以先查询原提交:
python
from neomega_runtime.storage import CommitUncertain
try:
committed = await tx.save()
except CommitUncertain as error:
original_intent = error.intent
commit_id = original_intent.payload()["commit_id"]
# 保留 intent,且不要把可能含敏感业务数据的完整正文写入日志。
committed = await ctx.storage.receipt(commit_id)
if committed is None:
raise # 当前没有确认回执;不重新执行交易。receipt 返回 None 只表示这次查询未找到回执,不证明并发中的旧提交永远不会完成。连接已经断开时,立即查询也可能失败;应由恢复流程保留原请求,待连接可用后核对。内存变量不会跨进程重启保存,交易插件要提前设计持久业务标识和未决状态。
ctx.storage.submit(original_intent) 是显式重交原请求的接口,不是自动恢复策略。只有核对结果及对应业务规则允许时才使用,不能重新计算奖励、生成新 ID 或更换期限来绕过冲突。取消事务等待时异常也可能携带 .intent,同样要保留。
明确的版本冲突
业务事务遇到 IPCRejected.code == "revision_conflict",说明该次提交的状态版本条件不符。重新读取状态并重新审查业务前提;不要把所有 IPCRejected 都归为可重试冲突。SDK 的简单存储便捷方法已对这一种冲突提供有限重试,见存储说明。
配置和权限分别有自己的 revision。配置冲突需要合并新配置;权限冲突需要重新审阅当前制品的申请。不能只替换版本号继续提交之前的变更。
排查入口
| 现象 | 下一步 |
|---|---|
| 插件无法启动 | 执行 doctor NAME,核对配置、权限确认、事件来源及 Host 版本。 |
| 能启动但没有聊天事件 | 核对 manifest 订阅、实际事件来源和目标实例;用玩家聊天测试。 |
| 重载后仍是旧行为 | reload 只重启已安装制品;新源码需 update-plan/update,再核对实际制品摘要与运行日志。 |
| 配置已保存但没生效 | 检查 saved、applied、active_revision 及重载日志。 |
| 操作没看到效果 | 用原 operation_id 查看终态、证据、部分效果;再核对实际游戏状态。 |
反馈问题时提供框架与 SDK 版本、插件 ID/版本、最小代码、复现步骤、脱敏错误、发生时间及原 operation_id / commit_id。不要提供管理令牌、密钥、完整私有配置或未经脱敏的玩家数据。
Bus 错误
provider_busy、unavailable、contract_changed 等调用错误不能自动改用另一个提供者重试 service。循环、过深和无效因果分别拒绝为 cycle_rejected、depth_exceeded、invalid_cause。持久动作接纳不明先保留原请求及已知 ID,查询原回执;unknown 不换 key、session 或 cause 重放。见Bus。
Scope 与远程会话恢复
1.1.0 的 Scope把业务结果与清理结果分别保留。ScopeError 带有已知句柄、原 request_id、business_error 和 cleanup_error;run 的 ScopeResult 同样保留两种错误。只看到回调返回,不能判断 Scope 已经完成关闭。
关闭返回后检查 handle.state;只有 closed 表示关闭完成。仍未关闭时保留 Scope ID、operation_ids 与 pending_operation_ids,通过 scopes.read 和原动作回执核对。申请回应丢失时使用原 request_id 查询,不能创建新申请后再次执行业务。
EP 重连会建立新会话,不接管旧 Scope。原会话的动作可能仍在执行;新连接和过期租约都不能证明资源已可安全复用。Host 继续协调已接纳动作,插件按远程 EP 规则核对原回执,unknown 仍不自动重放。