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