90 lines
3.2 KiB
JavaScript
90 lines
3.2 KiB
JavaScript
// src/app.js onFilesChanged —— handler 并发闸(audit P1-4)
|
||
//
|
||
// 背景:原代码
|
||
// const myToken = ++fsHandlerToken; // 自增但还没 SET
|
||
// if (inFlightToken !== 0) { return; } // 检查还是 0
|
||
// inFlightToken = myToken; // 才 SET
|
||
// 两行间是 microtask 窗口:burst 50 events 时多个 callback 排在同一 microtask
|
||
// queue 里,每个都看到 inFlightToken === 0,全部过闸 → handler 并发跑
|
||
// (applyEntries 双跑、scanDir 双发、UI 闪烁)。
|
||
//
|
||
// 修复:抽 makeLock() 工厂 → { tryClaim, release, current }。tryClaim 是同步
|
||
// 「check-and-set」原子操作:if 锁空闲则 SET 并返新 token,否则返 null。
|
||
// 在 callback 入口 tryClaim → null 直接 return → 不再丢事件,统一存到
|
||
// pendingRescanRel(既有逻辑保留)。
|
||
//
|
||
// 测试策略:直接 import 工厂函数,模拟 burst 场景验证「N 次 tryClaim 只有
|
||
// 第一次成功」。
|
||
|
||
import { describe, it, expect } from 'vitest';
|
||
import { makeLock } from '../../src/fs-handler-lock.js';
|
||
|
||
describe('fs-handler lock(audit P1-4)', () => {
|
||
it('初始空闲 → tryClaim 返非零 token,current 反映该 token', () => {
|
||
const lock = makeLock();
|
||
const t = lock.tryClaim();
|
||
expect(t).toBeGreaterThan(0);
|
||
expect(lock.current()).toBe(t);
|
||
});
|
||
|
||
it('已占用时 tryClaim 返 null,current 不变', () => {
|
||
const lock = makeLock();
|
||
const t1 = lock.tryClaim();
|
||
const t2 = lock.tryClaim();
|
||
expect(t1).toBeGreaterThan(0);
|
||
expect(t2).toBe(null);
|
||
expect(lock.current()).toBe(t1);
|
||
});
|
||
|
||
it('release(token) → 锁重新空闲,tryClaim 能拿到新 token', () => {
|
||
const lock = makeLock();
|
||
const t1 = lock.tryClaim();
|
||
expect(lock.release(t1)).toBe(true);
|
||
expect(lock.current()).toBe(0);
|
||
const t2 = lock.tryClaim();
|
||
expect(t2).toBeGreaterThan(0);
|
||
expect(t2).not.toBe(t1);
|
||
});
|
||
|
||
it('release(非当前 token) → 返 false,锁不被错误释放', () => {
|
||
const lock = makeLock();
|
||
const t1 = lock.tryClaim();
|
||
expect(lock.release(99999)).toBe(false);
|
||
expect(lock.current()).toBe(t1);
|
||
});
|
||
|
||
it('burst 50 次 tryClaim → 只有 1 次成功', () => {
|
||
// 关键测试:模拟 microtask 批量场景
|
||
const lock = makeLock();
|
||
const results = Array.from({ length: 50 }, () => lock.tryClaim());
|
||
const successes = results.filter((r) => r !== null);
|
||
expect(successes).toHaveLength(1);
|
||
// 其余 49 次全部 null
|
||
expect(results.filter((r) => r === null)).toHaveLength(49);
|
||
});
|
||
|
||
it('release → tryClaim → 再 burst:交替可串行化多批事件', () => {
|
||
const lock = makeLock();
|
||
for (let batch = 0; batch < 5; batch++) {
|
||
const t = lock.tryClaim();
|
||
expect(t).not.toBe(null);
|
||
// 本批 10 次并发全应被拒
|
||
for (let i = 0; i < 10; i++) {
|
||
expect(lock.tryClaim()).toBe(null);
|
||
}
|
||
// 释放
|
||
expect(lock.release(t)).toBe(true);
|
||
}
|
||
});
|
||
|
||
it('token 自增:每次成功 tryClaim 返的 token 都不同', () => {
|
||
const lock = makeLock();
|
||
const t1 = lock.tryClaim();
|
||
lock.release(t1);
|
||
const t2 = lock.tryClaim();
|
||
lock.release(t2);
|
||
const t3 = lock.tryClaim();
|
||
expect(new Set([t1, t2, t3]).size).toBe(3);
|
||
});
|
||
});
|