/** * [ref] —— 收编弧跑动期间的**进程内静默闸**([ref] I1「引擎先停」在本进程里的执法面)。 * * ── 病 ────────────────────────────────────────────────────────────────────────────────────────── * 183 I1 写的是「发起收编之前必须先把引擎停掉」。那是一条**运维前提**,而本进程此前对它**零执法**: * `POST /v1/adoption` 收下请求、弧开始逐表 UPDATE 的同时,本副本的维护 tick 照常每几秒跑一轮, * 而那一轮里好几条腿写的正是被迁的那些表。最锋利的一条是**审批收敛器**(`approval-reconciler.ts`): * 它按 `owner` 读 PARKING/STREAM_PENDING 行并 CAS 结算。收编把 `approval_ask.owner` 从 A 改成 B 的 * 那一瞬,收敛器手里可能正握着按**旧** owner 读出来的一批行 —— 结算下去就是把一只本该 PARKED 的 ask * 写成 **DENIED**,而 DENIED 是终局:收编回滚不了它,重跑也追认不回来。 * * ── 修 ────────────────────────────────────────────────────────────────────────────────────────── * 弧跑动期间把维护 tick 整轮让开。判据不是「挑几条腿避让」而是**整轮**: * ① 维护 tick 是 best-effort 的周期动作(每条腿都幂等、都能等下一轮),让开几轮零代价;而逐腿判 * 「这条腿碰不碰被迁的表」会立刻变成第二份需要与 `REBIND_LEGS` 同步的表 —— 那正是本仓反复吃过亏的 * 「两份判据必然漂」形(漏判一条新腿是**静默**的:它照写不误)。 * ② 弧本身是毫秒级的几条 UPDATE,库里常态在飞行数为 0 —— 让开的窗极短。 * * ── 射程边界(如实登记,不假称已解)────────────────────────────────────────────────────────────── * 两条边界,都写在明处: * ① **进程内**。这道闸只挡得住**运行这条弧的那个副本**;多副本部署里别的副本的 tick 照跑,那部分仍然 * 只能靠 183 I1 的运维前提(以及弧内那些 rev-CAS:载荷改写撞上并发写会响亮抛、整体回滚)。 * 更强的形(跨副本静默)需要一枚库上的 quiesce 租约 + 每条腿的取租,那是一次独立设计,不在本条射程。 * ② **排空有界**。等不到就带着读数继续(见下),所以「本进程零腿在写被迁的表」是**通常**成立而不是 * 恒成立 —— 超时那一次由 `onDrainTimeout` 响亮说出来,不许静默当成已排空。 * 本闸因此是**纵深防御**,不是把 I1 变成机器保证。 * * ── 形态 ──────────────────────────────────────────────────────────────────────────────────────── * 模块级计数器而不是布尔:并发同参 POST 是本车明确支持的形(UNIQUE 仲裁),两条弧可以叠在一起, * 布尔会被先结束的那条**提前**放开闸。计数器 + `try/finally` ⇒ 抛错也一定归零。 */ /** 本进程此刻是否有收编弧在跑(维护 tick 的让路判据)。 */ export declare function isAdoptionQuiescing(): boolean; /** * 登记一条**刚起飞**的维护腿。调用方仍然 `void` 它自己的链(错误处置不变);本函数只借用它的落地时刻。 * * `catch` 是必须的:登记的这一份引用绝不能变成第二个未处理拒绝源(腿自己的链已经有 guard 接住了)。 */ export declare function trackMaintenanceLeg(leg: Promise): void; /** 取证口(测试用):此刻有几只等待方挂着。生产不读 —— 它存在是为了让「重复超时不泄漏」可被断言。 */ export declare function drainWaiterCountForTest(): number; /** 排空结果:`drained` = 全部落地;否则 `pending` = 超时时仍在飞的条数。 */ export interface DrainReading { drained: boolean; pending: number; } /** 有界排空(见 {@link airborneLegs} 的注)。`timeoutMs <= 0` ⇒ 不等,直接如实回报在飞数。 */ export declare function drainMaintenanceLegs(timeoutMs: number): Promise; /** 排空的默认上界。取值理由:维护腿的常态是毫秒级的几条 SQL;5s 足够让它们全部落地,又不会把一次 * 收编吊死在一条卡住的腿上(卡住的那一条由它自己的重入守卫与 throttled-warn 负责被看见)。 */ export declare const ADOPTION_DRAIN_TIMEOUT_MS = 5000; export interface QuiesceOpts { /** 排空上界(测试可调小);缺省 {@link ADOPTION_DRAIN_TIMEOUT_MS}。 */ drainTimeoutMs?: number; /** 没排空时的**响亮**出口(生产接 logger.warn)。缺席 ⇒ 不复述(读数仍由返回值带出)。 */ onDrainTimeout?: (reading: DrainReading) => void; } /** * 在闸内跑一条收编弧:**先关闸,再排空,最后才动数据**。 * * 三步的次序是承重的 —— 先关闸(此后不会再有新 tick 起飞新腿),再等已经在飞的那些落地,然后才把 * 控制权交给弧。反过来(先排空再关闸)之间那道窗里正好可以起飞新的一轮。 * * `finally` 归零是硬的:抛出去的异常照常上抛(调用方的错误语义一个字不改),但闸绝不能因为一次失败 * 就永久关着 —— 那会把一次收编失败升级成「这台副本从此不再做任何后台维护」。 */ export declare function withAdoptionQuiesce(fn: () => Promise, opts?: QuiesceOpts): Promise; /** 测试用的复位口(用例之间零串味)。生产路径**不调** —— 它会把别的在飞弧的计数一起抹掉。 */ export declare function resetAdoptionQuiesceForTest(): void; //# sourceMappingURL=quiesce.d.ts.map