Node Error 留住内存
旧 Node 的 Error 可经 Immediate 引用链保留其他请求存储。核对 24.20.0 清理,并区分跨请求保留与 Error 自己的调用帧。
请求已经结束,setImmediate 回调里创建并保存的少量 Error,却留住了其他请求的 AsyncLocalStorage 数据。查看堆快照时,保留路径可能经过 Error 的回调接收者、Immediate,再连接到旧队列和异步上下文。
Node 24.19.0 和 24.14.0 的环境曾出现这一跨请求保留。已核对的正式版 24.20.0 会在 Immediate 运行结束后清理相关存储和回调引用。这里要把跨请求连接与 Error 自己保留的调用帧分开,不能把它写成当前版本所有 Error 都会泄漏。
保留者可能不是请求缓存
应用可能为了错误报告、取消原因或后续诊断,把 Error 保存在一个较长寿命的对象里。Error 的延迟堆栈信息可以保留回调里的接收者,而 Immediate 又可能仍带有 AsyncLocalStorage 的存储、回调或参数。
如果这些旧对象还通过队列关系连接到其他任务,一个少量保存的错误就可能留住比本请求大得多的数据。仅检查业务缓存有没有清空,可能漏掉这条运行时路径。
应从实际堆快照中的保留者链核对:留下的是某个 Error 自己的上下文,还是经已经执行的 Immediate 继续连接到其他请求。RSS 或 external 数值上涨只能提供线索,不能单独证明这条根因。
应用自己保留整份请求、闭包或大 Buffer 的情况,也要另外排查。运行时清理修复不会替应用删除仍然有业务引用的数据。
24.20.0 清理已运行的 Immediate
正式版 24.20.0 的定时任务实现,在回调正常结束后清理 Immediate 的异步存储、_onImmediate 和参数引用。回调抛错时,也会清掉回调和参数,并安排对应存储清理。
这让已经完成的任务不再继续通过这些引用留住请求上下文。项目可以沿 24.x 的兼容升级路径选择含有相应处理的版本;其他发行线应检查实际实现,不能只按主版本大小推断它们包含同样的修复。
若应用运行在 Electron、其他嵌入宿主或不同 worker 环境中,要核对实际运行时。终端升级了 Node,宿主仍带着旧版本,不会改变它内部的 Immediate 清理逻辑。
Error 自己的调用帧仍可能存在
跨请求部分清理后,保存的 Error 仍可能保留自己的调用帧,直到堆栈信息被读取。这与“已经完成的 Immediate 还携带其他请求存储”不是同一个结论。
因此,升级后某些 Error 仍出现在堆快照里,不等于跨请求清理没有生效。继续检查它究竟引用了什么,以及应用为什么需要长时间保存完整 Error,而不是只看对象是否存在。
也不要把全局改写 AbortController、统一丢弃错误或清空所有 stack 当成修复。它们会改变取消原因与诊断行为,却未必切断真正的保留者关系。需要缩短业务错误保留时间时,应按原有日志、错误追踪和隐私要求设计。
用请求结束后的引用关系验收
在受控环境中保留应用原来的错误保存条件,让 Immediate 回调完成,再观察旧存储是否仍经运行时资源连接到其他请求。正常请求上下文和错误报告仍应能按业务要求使用。
验收目标不是“所有 Error 消失”或“内存马上归零”。需要确认跨请求链已被切断,同时把应用仍主动保留的错误、请求数据和调用帧作为各自独立的内存使用继续判断。


