Skip to content

Commit 5b86a9a

Browse files
committed
test: add auto-capture lock contention test (Issue #665)
1 parent 7f1475d commit 5b86a9a

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// test/auto-capture-lock-contention.test.mjs
2+
/**
3+
* Auto-Capture Lock Contention Test
4+
*
5+
* 測試目標:
6+
* 模擬 auto-capture 產生多個記憶時,每次寫入都會取得 lock
7+
* 對比:單獨寫入 vs 批次寫入的 lock 次數
8+
*
9+
* 這個測試用於驗證 Issue #665 的問題存在
10+
*/
11+
12+
import { describe, it } from "node:test";
13+
import assert from "node:assert/strict";
14+
import { mkdtempSync, rmSync } from "node:fs";
15+
import { tmpdir } from "node:os";
16+
import { join } from "node:path";
17+
import jitiFactory from "jiti";
18+
19+
const jiti = jitiFactory(import.meta.url, { interopDefault: true });
20+
21+
describe("Auto-Capture Lock Contention", () => {
22+
// 追蹤 lock 取得次數
23+
let lockAcquisitionCount = 0;
24+
25+
// 測試 1:模擬 auto-capture 單獨寫入(N 次 lock)
26+
it("should acquire lock N times for N entries (current behavior)", async () => {
27+
const { MemoryStore } = jiti("../src/store.ts");
28+
const dir = mkdtempSync(join(tmpdir(), "memory-acquire-lock-"));
29+
30+
const store = new MemoryStore({
31+
dbPath: dir,
32+
vectorDim: 128,
33+
});
34+
35+
// 模擬 auto-capture 產生 10 個記憶
36+
const entries = Array(10).fill(null).map((_, i) => ({
37+
text: `Auto-capture memory ${i}`,
38+
vector: new Array(128).fill(Math.random()),
39+
category: "fact",
40+
scope: "test",
41+
importance: 0.5,
42+
metadata: "{}",
43+
}));
44+
45+
// 測量:每次單獨寫入
46+
const start = Date.now();
47+
for (const entry of entries) {
48+
await store.store(entry);
49+
}
50+
const duration = Date.now() - start;
51+
52+
console.log(`[Individual] 10 entries: ${duration}ms`);
53+
54+
await store.destroy();
55+
rmSync(dir, { recursive: true, force: true });
56+
});
57+
58+
// 測試 2:模擬高並發 auto-capture(實際壓測)
59+
it("should demonstrate lock contention with 20 concurrent auto-captures", async () => {
60+
const { MemoryStore } = jiti("../src/store.ts");
61+
const dir = mkdtempSync(join(tmpdir(), "memory-ac-concurrent-"));
62+
63+
const store = new MemoryStore({
64+
dbPath: dir,
65+
vectorDim: 64,
66+
});
67+
68+
// 模擬 20 個並發的 auto-capture 操作
69+
// 每個操作都會取得 lock
70+
const results = await Promise.allSettled(
71+
Array(20).fill(null).map((_, i) =>
72+
store.store({
73+
text: `Concurrent auto-capture ${i}`,
74+
vector: new Array(64).fill(0.1),
75+
category: "fact",
76+
scope: "agent:test",
77+
importance: 0.5,
78+
metadata: "{}",
79+
})
80+
)
81+
);
82+
83+
const successCount = results.filter(r => r.status === "fulfilled").length;
84+
const failCount = results.filter(r => r.status === "rejected").length;
85+
86+
console.log(`[Concurrent] 20 auto-captures: ${successCount} success, ${failCount} failed`);
87+
88+
// 使用 Redis lock 後,成功率應該很高
89+
// 但在 Redis 不可用時會 fallback 到 file lock
90+
assert.ok(successCount >= 15, `Expected at least 15 successful, got ${successCount}`);
91+
92+
await store.destroy();
93+
rmSync(dir, { recursive: true, force: true });
94+
}, 60000);
95+
96+
// 測試 3:批次寫入的理論模擬(展示改善方向)
97+
it("batch write would reduce lock acquisitions", async () => {
98+
const dir = mkdtempSync(join(tmpdir(), "memory-batch-sim-"));
99+
const { MemoryStore } = jiti("../src/store.ts");
100+
const store = new MemoryStore({
101+
dbPath: dir,
102+
vectorDim: 32,
103+
});
104+
105+
// 理論計算:
106+
// - 單獨寫入:10 entries = 10 次 lock
107+
// - 批次寫入:10 entries = 1 次 lock
108+
//
109+
// 這個測試只是展示概念,實際批次寫入需要實作 bulkStore
110+
111+
const entries = Array(10).fill(null).map((_, i) => ({
112+
text: `Batch memory ${i}`,
113+
vector: new Array(32).fill(0.1),
114+
category: "fact",
115+
scope: "test",
116+
importance: 0.5,
117+
metadata: "{}",
118+
}));
119+
120+
// 單獨寫入(現有行為)
121+
const individualStart = Date.now();
122+
for (const entry of entries) {
123+
await store.store(entry);
124+
}
125+
const individualTime = Date.now() - individualStart;
126+
127+
console.log(`[Theoretical] Individual: 10 lock acquisitions in ${individualTime}ms`);
128+
console.log(`[Theoretical] Batch (not implemented): would be ~1 lock acquisition`);
129+
console.log(`[Theoretical] Improvement: ~${((individualTime - 100) / individualTime * 100).toFixed(0)}% faster`);
130+
131+
await store.destroy();
132+
rmSync(dir, { recursive: true, force: true });
133+
});
134+
135+
// 測試 4:真實的 lock 開銷測量
136+
it("measure actual lock overhead", async () => {
137+
const dir = mkdtempSync(join(tmpdir(), "memory-lock-overhead-"));
138+
const { MemoryStore } = jiti("../src/store.ts");
139+
const store = new MemoryStore({
140+
dbPath: dir,
141+
vectorDim: 16,
142+
});
143+
144+
// 測量 50 次寫入的總時間
145+
const times = [];
146+
for (let i = 0; i < 50; i++) {
147+
const start = Date.now();
148+
await store.store({
149+
text: `Measure ${i}`,
150+
vector: new Array(16).fill(0.1),
151+
category: "fact",
152+
scope: "test",
153+
importance: 0.5,
154+
metadata: "{}",
155+
});
156+
times.push(Date.now() - start);
157+
}
158+
159+
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
160+
const maxTime = Math.max(...times);
161+
const minTime = Math.min(...times);
162+
163+
console.log(`[Lock Overhead] 50 writes:`);
164+
console.log(` - Average: ${avgTime.toFixed(2)}ms`);
165+
console.log(` - Min: ${minTime}ms`);
166+
console.log(` - Max: ${maxTime}ms`);
167+
168+
// 如果 maxTime 很高,表示有 lock 競爭
169+
if (maxTime > 5000) {
170+
console.log(`⚠️ High max time (${maxTime}ms) indicates lock contention!`);
171+
}
172+
173+
await store.destroy();
174+
rmSync(dir, { recursive: true, force: true });
175+
}, 120000);
176+
});
177+
178+
console.log("=== Auto-Capture Lock Contention Tests ===");
179+
console.log("This test demonstrates Issue #665: auto-capture creates multiple lock acquisitions");

0 commit comments

Comments
 (0)