发现于 #5160 实现期间(读 send() 时看到),与该单无关,未在该 PR 中修 。
位置 packages/plugins/plugin-email/src/email-service.ts,send()(#5160 后叫 sendInternal)的 try/finally:
this.managedRowIds.add(id);
try {
const res = await this.options.persistence.insert(baseRow);
persistedId = typeof res === 'string' ? res : res?.id ?? id;
if (persistedId !== id) this.managedRowIds.add(persistedId); // 加了
...
} finally {
this.managedRowIds.delete(id); // 只删了 id
}
persistedId !== id 时,persistedId 被塞进 managedRowIds 这个 Set< string > 却永远不会被删 。每一封这样的邮件都在进程生命周期内往集合里留一个字符串。
后果 内存 :长跑进程里 managedRowIds 单调增长,一封一个 id;语义 :isServiceManaged(persistedId) 从此恒为 true。id 唯一,所以不会误伤别的行 —— 但 outbox drain 钩子(email-plugin.ts afterInsert)与 plugin-email: sys_email 的 queued 行崩溃后永久滞留 —— 无任何轮询者,且 drain 钩子把失败 warn 掉 #5161 将要加的清扫器都以这个集合判断"该行归 send() 管",一个永久 true 的条目是一条永远不会被复核的断言。今天为什么打不到 仓内唯一的 persistence 实现(email-plugin.ts 里的 insert)是
return created?.id ? { id: String(created.id) } : { id: String(row.id) };
而 send() 传入的 baseRow.id 就是它自己生成的 newId(),ObjectQL insert 会原样回传,所以 persistedId === id,分支不进。EmailPersistence 是导出的公开接口 ,任何自建实现(数据库自增主键、外部投递系统回执 id)都会踩到。
按"观察级"归档:今天没有用户能撞到,但它是一条已经写下的、只等一个不同 persistence 实现来点燃的引线。严重程度请 PM triage 判定 —— 我在 #5160 里没顺手改,因为它与该单的 diff 无关。
建议修法 } finally {
this.managedRowIds.delete(id);
if (persistedId && persistedId !== id) this.managedRowIds.delete(persistedId);
}
(需要把 persistedId 提到 try 外面声明。)配一条用例:persistence 返回与入参不同的 id,断言 send() 返回后 isServiceManaged(persistedId) 为 false。
发现于 #5160 实现期间(读
send()时看到),与该单无关,未在该 PR 中修。位置
packages/plugins/plugin-email/src/email-service.ts,send()(#5160 后叫sendInternal)的 try/finally:persistedId !== id时,persistedId被塞进managedRowIds这个Set< string >却永远不会被删。每一封这样的邮件都在进程生命周期内往集合里留一个字符串。后果
managedRowIds单调增长,一封一个 id;isServiceManaged(persistedId)从此恒为 true。id 唯一,所以不会误伤别的行 —— 但 outbox drain 钩子(email-plugin.tsafterInsert)与 plugin-email: sys_email 的 queued 行崩溃后永久滞留 —— 无任何轮询者,且 drain 钩子把失败 warn 掉 #5161 将要加的清扫器都以这个集合判断"该行归 send() 管",一个永久 true 的条目是一条永远不会被复核的断言。今天为什么打不到
仓内唯一的 persistence 实现(
email-plugin.ts里的insert)是而
send()传入的baseRow.id就是它自己生成的newId(),ObjectQL insert 会原样回传,所以persistedId === id,分支不进。EmailPersistence是导出的公开接口,任何自建实现(数据库自增主键、外部投递系统回执 id)都会踩到。按"观察级"归档:今天没有用户能撞到,但它是一条已经写下的、只等一个不同 persistence 实现来点燃的引线。严重程度请 PM triage 判定 —— 我在 #5160 里没顺手改,因为它与该单的 diff 无关。
建议修法
(需要把
persistedId提到 try 外面声明。)配一条用例:persistence 返回与入参不同的 id,断言send()返回后isServiceManaged(persistedId)为 false。