Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion web/src/useEventReconnect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,26 @@ describe("KB stream reconnect", () => {
source.emit("graph");
await vi.advanceTimersByTimeAsync(100);
}
await vi.advanceTimersByTimeAsync(OVERVIEW_SETTLE_MS - 1_000 - 1);
await vi.advanceTimersByTimeAsync(OVERVIEW_SETTLE_MS - 1_000);
expect(overview.fetch).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(SETTLE_MS);
expect(overview.fetch).toHaveBeenCalledTimes(2);
});

it("the overview deadline rides the open fast window instead of flushing it early", async () => {
const client = createClient();
const overview = await watch(client, ["graph", "kb-a", "overview", 200]);
const neighborhood = await watch(client, ["graph", "kb-a", "neighborhood", "e1"]);
const { source } = mount(() => useKbEvents("kb-a"));
source.emit("open");
source.emit("graph");
await vi.advanceTimersByTimeAsync(OVERVIEW_SETTLE_MS - 100);
source.emit("graph");
await vi.advanceTimersByTimeAsync(SETTLE_MS - 1);
expect(neighborhood.fetch).toHaveBeenCalledTimes(2);
expect(overview.fetch).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(1);
expect(neighborhood.fetch).toHaveBeenCalledTimes(3);
expect(overview.fetch).toHaveBeenCalledTimes(2);
});

Expand Down
17 changes: 8 additions & 9 deletions web/src/useKbEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
//
// **失效是合并着做的。** 一篇文档抽取时每落一条事实就发一个 graph 事件,
// 从前每个事件各失效一次,图谱页在那几秒里把 overview 重取了十几遍——
// 每次都是同一张图,最后一次才算数。所以事件只把 key 记下来,停顿一小会
// 再一次性失效:一阵事件只换来一次重取,最后那次一定包含之前所有的变化。
// 每次都是同一张图,最后一次才算数。所以事件只把 key 记下来,等一个窗口
// 再一次性失效:窗口里的事件只换来一次重取,最后那次一定包含之前所有的变化。
// 窗口从第一个事件算起、中途不重置:持续推送时定期刷,不必等推送停下。
// 幂等性没变,只是把「每条都刷」变成「刷最后一条」。
//
// 失效不看 staleTime:这里失效的键在 queryDefaults.ts 里放到了 30 秒,靠的就是
Expand All @@ -15,14 +16,14 @@ import { partialMatchKey, useQueryClient, type QueryKey } from "@tanstack/react-
import { subscribeEvents } from "./eventStream";
import { STREAM_KEYS } from "./queryDefaults";

/** 一阵事件之间的静默期。抽取落事实的间隔远小于它,人眼看不出这点延迟 */
/** 合并窗口。抽取落事实的间隔远小于它,人眼看不出这点延迟 */
export const SETTLE_MS = 300;

/** 全图 overview 要数遍整个库:推送期间几秒重数一次就够了(#1296) */
export const OVERVIEW_SETTLE_MS = 3_000;

/**
* 把一阵失效合并成一次:`push` 只记键,静默 `settleMs` 后一起交给 `invalidate`,
* 把一个窗口里的失效合并成一次:`push` 只记键,第一次 `push` 的 `settleMs` 后一起交给 `invalidate`,
* 同一个键及其前缀只失效一次;`flush` 给卸载与重连用,攒着的刷掉而不是丢掉。
* 抽成纯函数是为了能测;订阅那层只是把事件名映射到键。
*/
Expand Down Expand Up @@ -62,11 +63,9 @@ export function useKbEvents(kbId: string | undefined) {
(key) => queryClient.invalidateQueries({ queryKey: key }),
SETTLE_MS,
);
// overview 到点也经快的那层刷:重连补上的 ["graph", kbId] 已覆盖它,不再多取一遍
const overview = createInvalidationCoalescer((key) => {
push(key);
flush();
}, OVERVIEW_SETTLE_MS);
// overview 到点也交给快的那层:搭上正在走的窗口,不把别的键提前刷掉;
// 重连补上的 ["graph", kbId] 已覆盖它,不再多取一遍
const overview = createInvalidationCoalescer((key) => push(key), OVERVIEW_SETTLE_MS);
const pushGraph = () => {
push(["graph", kbId, "neighborhood"]);
overview.push(["graph", kbId, "overview"]);
Expand Down
Loading