import type { Finalizable, KeyValueStorage, NotificationChannelStorage, WebSocketMap } from '@solid/community-server'; import { Initializer } from '@solid/community-server'; /** 默认清扫间隔(分钟)。同时是“新建但尚未连上 socket”的通道的宽限期。 */ export declare const NOTIFICATION_CHANNEL_SWEEP_INTERVAL_MINUTES = 5; /** * 回收没有活动 socket 的通知通道。 * * 背景:CSS 的 `WebSocket2023Storer` 只在 socket 关闭时把它从内存 map 中移除,通道记录留在 * `SubscriptionStorage` 里直到 `endAt` 过期(默认 2 周);客户端 DELETE 失败、页面崩溃、 * 进程被强杀等情况都会留下再也无人使用、也无人回收的通道。`ReclaimingWebSocket2023Storer` * 覆盖了正常关闭的路径,本组件负责其余部分: * * 1. 启动时清扫一次:进程刚起来,内存 map 必然为空,任何持久化的通道都不可能还有活动 socket; * 2. 之后每 {@link NOTIFICATION_CHANNEL_SWEEP_INTERVAL_MINUTES} 分钟清扫一次:无 socket 的通道 * 需要连续两次被判定为孤儿才删除,避免误删“刚 POST 出来、socket 还在握手”的通道。 * * 只回收本实例 `baseUrl` 下的通道:通道 id 由本实例的订阅路由生成,Cloud 集群中共享 * `internal_kv` 的其他节点(若存在)的通道由各自实例负责,这里不做跨实例删除。 * 与 socket 归属相关的限制(同一个 baseUrl 被多个 CSS 进程共享时无法判断通道是否在别的进程里存活) * 见 `docs/COMPONENTS.md`。 */ export declare class NotificationChannelSweeper extends Initializer implements Finalizable { protected readonly logger: import("global-logger-factory").Logger; private readonly channelStorage; private readonly channelIndex; private readonly socketMap; private readonly baseUrl; private readonly intervalMs; /** 见过一次、但当时没有活动 socket 的通道 id;下一次仍是孤儿才回收。 */ private readonly awaitingSecondLook; private timer?; private sweeping; constructor(storage: NotificationChannelStorage, channelIndex: KeyValueStorage, socketMap: WebSocketMap, baseUrl: string, intervalMinutes?: number); /** * CSS 启动序列的一部分(在 HTTP server 开始监听之前执行):先做一次启动清扫,再挂上有界的周期任务。 * * 实测同一次 CSS 启动里本方法会被调用两次(配置图里只出现一次)。这里做成幂等:重复调用既不重复清扫, * 也不重复注册定时器——两个定时器会让“连续两次判定”退化成相隔几百毫秒的两次判定,等于没有宽限期。 */ handle(): Promise; finalize(): Promise; /** * 执行一次清扫,返回被回收的通道数量。 * * @param initial - 启动清扫:内存中还没有任何 socket,因此无 socket 的通道一律视为孤儿。 */ sweep(initial?: boolean): Promise; /** 枚举本实例 baseUrl 下的通道记录。topic 索引行(值是 id 数组)不是通道,跳过。 */ private listOwnedChannels; }