故障排查
大多数问题会先体现在 fork 的状态上,之后才在别处显现。先找到 fork、读出它的状态,再按该状态、看到的日志消息或症状查找对应的行。
找到 fork
Section titled “找到 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 却没有动静。 |
Pending
Section titled “Pending”missing 列出 fork 所在领域和环境都没有提供的必需服务的键字符串。按顺序检查:
- 发布方加载了吗? 处于
Failed、Pending或已停用的发布方不会发布任何东西。在 fork 树中找到它。 - 在同一个领域吗? 如果发布方位于隔离了该键的 fork 内部,它只在那个领域中发布。见领域。
- 键匹配吗? 服务按键字符串(例如
app/Database)精确匹配。graft.yml中配置项的inject字段会让它额外等待更多的键。 - 两个插件在互相等待吗? 如果双方各自注入对方发布的服务,两者都会停在
Pending。把共享部分移到第三个插件中,让两者都注入它。
Active 却没有动静
Section titled “Active 却没有动静”| 原因 | 修复 |
|---|---|
工作用 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>" … |
新代码已导入,但插件启动失败或超时。旧插件重新运行。启动失败的日志带有 cause,超时没有 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 上的代码不受监听。见配置变更与热重载。 |
停止迟迟不结束
Section titled “停止迟迟不结束”发布方要等它所发布服务的消费者全部停止后才会停止,顺序见服务撤回流程。如果这一过程超过 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 |