跳转到内容

故障排查

大多数问题会先体现在 fork 的状态上,之后才在别处显现。先找到 fork、读出它的状态,再按该状态、看到的日志消息或症状查找对应的行。

Graft 记录插件的失败时会附上完整的 Cause,并带有 graft.plugin 和 graft.fork 注解。处于等待中的 fork 不写日志:Pending 和长时间的 Loading 只会出现在 fork 树中。

查看位置 能看到什么
graft start 的输出 带 Cause 的失败,以及下文列出的消息。插件写出的日志行会标明所属 fork。
Graft.forks 每个 fork 的 name、id 和 state,包括 Pending 时的 missing 和 Failed 时的 cause。
fork.state 你自己加载的某个 fork 的每一次状态变化,直到 Disposed。

要把一直停在 Pending 的 fork 记入日志,请添加用 fork 树诊断中的插件。http-gateway 示例通过 HTTP 服务器挂载 GET /_graft,显示实时插件树。

状态 检查什么
长时间 Loading apply 还没有返回。在插件整个生命周期内运行的工作,例如 Stream.runForEach、服务器循环或 Effect.never,应放进 Plugin.fork。模块导入一直不结束的配置项也会停在 Loading。
Pending 见 Pending。
Failed 读取 cause:在错误中找到它的错误标签,再修正输入、依赖或代码。fork 何时再次尝试,见失败与重试。
Active,但什么也没发生 见 Active 却没有动静。

missing 列出 fork 所在领域和环境都没有提供的必需服务的键字符串。按顺序检查:

  1. 发布方加载了吗? 处于 Failed、Pending 或已停用的发布方不会发布任何东西。在 fork 树中找到它。
  2. 在同一个领域吗? 如果发布方位于隔离了该键的 fork 内部,它只在那个领域中发布。见领域。
  3. 键匹配吗? 服务按键字符串(例如 app/Database)精确匹配。graft.yml 中配置项的 inject 字段会让它额外等待更多的键。
  4. 两个插件在互相等待吗? 如果双方各自注入对方发布的服务,两者都会停在 Pending。把共享部分移到第三个插件中,让两者都注入它。
原因 修复
工作用 Effect.forkChild 启动。apply 返回时它就被中断,而中断不会让插件失败。 改用 Plugin.fork 启动。
用 Effect.forkScoped、裸定时器或无人持有的 promise 启动的工作静默失败了。 Plugin.fork 会报告失败:插件失败,并可以重试。见插件中的 Effect。
处理函数或资源是在 Effect.scoped 内,或在某次事件处理函数调用内设置的。 它随那个 Scope 一起结束了。请在 apply 中注册。见处理函数的上下文。
启动时某个可选依赖不存在。 插件在缺少它的情况下运行,并在它出现时重启。如果插件离开它就无法工作,请把它改为必需依赖。
某个任务捕获所有错误后继续执行。 让你处理不了的失败使任务失败。见插件中的 Effect。

关于插件的消息列在 Graft 记录的日志中。下面这些来自 graft start 和 loader 的消息,指向代码或配置中的问题:

消息 含义
Restart required… 某个模块变化无法热重载,旧插件继续运行。见重载未生效的情形。
Reloading plugin modules failed 新代码导入失败或没有通过检查。旧插件继续运行;修复代码后再保存一次。
Kept the previous code: plugin "<name>" … 替换失败或超时;计入的恢复 fork 为活跃或 Pending。失败带有完整 cause,超时没有 cause。修复代码后再保存一次。
Replacement abandoned: plugin "<name>" … RecoveryFailed:放弃新代码的原因。错误带有完整 cause,超时会指出插件。
Previous code failed to restart: plugin "<name>" … RecoveryFailed:恢复的旧插件也失败了。错误带有完整 cause。修复启动失败后再保存一次。
Waiting for valid configuration in <file> 使用 --watch 时,首个 graft.yml 无效。见初始配置无效。
Dropped an edit to "<id>": the file changed it too 一次 API 编辑与文件中的编辑冲突,被放弃了。见与外部编辑的冲突。
症状 原因与修复
对服务 yield* 时出现类型错误 该键不在 inject 中,或者被列为 Plugin.optional:可选依赖要用 Effect.serviceOption 读取。见依赖。
类型错误中提到 Tasks 某个辅助函数的类型标注丢掉了 Plugin.Tasks。见类型中的任务错误。
defect Plugin.fork requires a plugin activation Plugin.fork 在插件之外运行,例如在宿主程序中。请在插件中调用它;在宿主中,用 Scope 持有 fiber。
fork.await 或 loader.ready 已返回,插件却没有运行 两者都把 Pending 视为已稳定,ready 还把 Failed 的 fork 视为已稳定。请在 fork.state 上等待 Active。见 await、stable 和 state。
另一个插件加载或重载时,某个消费者跟着重启 它的发布方重启了,而消费者总会随发布方重启;让发布方原地更新。另一种可能是消费者使用了可选依赖或环境兜底,而它的领域中出现了发布方:见查找顺序。
插件反复重启 每次 retry 尝试都以同样的方式失败。阅读日志中的 cause;调度修不好持续存在的错误。
消费者看到的是环境值,而不是发布的值 发布方在另一个领域中发布。见环境兜底与继承。
处理函数从未被调用 派发发生在另一个领域、过滤器排除了它、键字符串不一致,或者它的 Scope 已经关闭。此外,Event.emit 遇到第一个失败就停止,Event.bail 遇到第一个 Some 就停止。
处理函数看到错误的服务、设置或日志注解 处理函数使用所属插件的上下文,而不是派发方的上下文。把它需要的东西作为事件参数传入。见处理函数的上下文。
编辑 graft.yml 没有效果 graft start 没有使用 --watch;文件无效,Graft 保留上一个有效版本(无效文件);或者更新函数返回了 true,却没有应用变化的字段。
修改环境变量没有效果 !env 在配置项加载、变化或刷新时读取,而不是在进程环境变化时读取。使用 --watch 时,保存 --env-file 指定的文件会触发刷新。见何时重新读取。
编辑模块没有效果 graft start 没有使用 --watch;文件在配置目录之外或位于 node_modules 中;配置项加载的是构建后的 JavaScript,而它没有重新构建;或者重载失败了。见什么会触发重载。
重载后旧行为仍在继续 某个模块在导入时启动了定时器或连接,Graft 无法停止它。把它移到 apply 中。见插件模块的规则。
配置项以 ConfigError 失败 某个 !env 变量在进程环境、--env-file 文件和 --env-dir 目录中都没有设置。只有这个配置项失败。设置该变量后重启;在 --watch 下也可以保存 env 文件(loader.retry 会重新读取)。见环境变量。
配置项以 ModuleNotFound 失败 它的 name 没有解析成功,消息中列出了尝试过的名称。见插件名称。
配置项以 ModuleInvalid 失败 模块导入失败,或者既没有在默认导出、也没有在 plugin 导出中提供插件。
graft start 立即退出 未使用 --watch 时首个配置无效,或者 --env-file 指定的文件或 --env-dir 指定的目录不存在。见 graft start。
包管理器列出了两个版本的 effect 或 @graftland/graft 某个插件包直接依赖了它们,而它们必须只有一份。见插件包。
远程插件忽略代码修改 未使用 --watch 时,重启宿主。使用时,如果配置项指向构建后的 JavaScript,就重新构建;网络 runner 上的代码不受监听。见配置变更与热重载。

发布方要等它所发布服务的消费者全部停止后才会停止,顺序见服务撤回流程。如果这一过程超过 10 秒,Graft 会记录 Still waiting for consumers of <keys>; a teardown cycle may be holding them 并继续等待;关闭、重启和热重载时都是如此。

排查 修复
消费者的终结器在等待某件发布方停止之后才会做的事。 不要在终结器中等待发布方;释放资源后直接返回。
经由服务或事件形成的环:A 的清理等待 B,B 的清理又等待 A。 用第三个插件持有共享部分,打破这个环。
消费者任务在 Effect.uninterruptible 内等待一件永远不会发生的事。 让等待保持可中断,只把关键步骤设为不可中断。

没有任何警告就卡住的停止,可能是某个模块导入一直没有结束;见限制。

在 Cause 中找到错误标签,再到错误页面中列出该标签的小节查看处理方法。

标签 小节
SchemaError、Fork.CleanupError、任务失败 Fork 失败
ServiceConflict 服务
Graft.ReplacementConflict 替换
UnregisteredReference、InvalidIntercept Intercept
CyclicConfig、InvalidOperation、StoreError、LoaderClosed 加载器与配置文件
HmrError、Skipped 结果 热重载
RemoteError、Contract.UndeclaredFailure、RemoteWithdrawn、RemoteEntryError Remote