You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
47 lines
2.4 KiB
TypeScript
47 lines
2.4 KiB
TypeScript
// apps/hq/test/concurrency.test.ts — D24 (red-team): SQLite transaction serialization.
|
|
import { describe, it, expect } from 'vitest'
|
|
import { openDb } from '../src/db'
|
|
|
|
describe('SqliteDb.transaction serialization (D24)', () => {
|
|
it('overlapping transactions do not interleave — a concurrent counter stays consistent', async () => {
|
|
const db = openDb(':memory:')
|
|
await db.exec(`CREATE TABLE ctr (id INTEGER PRIMARY KEY, n INTEGER NOT NULL)`)
|
|
await db.run(`INSERT INTO ctr (id, n) VALUES (1, 0)`)
|
|
// 50 read-modify-write transactions fired concurrently. Without serialization the
|
|
// read-then-write would race and lose increments; the mutex makes the final count exact.
|
|
const bump = () => db.transaction(async () => {
|
|
const row = (await db.get<{ n: number }>(`SELECT n FROM ctr WHERE id=1`))!
|
|
// Yield the event loop mid-transaction — the moment a naive impl would interleave.
|
|
await Promise.resolve()
|
|
await db.run(`UPDATE ctr SET n=? WHERE id=1`, row.n + 1)
|
|
})
|
|
await Promise.all(Array.from({ length: 50 }, bump))
|
|
expect((await db.get<{ n: number }>(`SELECT n FROM ctr WHERE id=1`))!.n).toBe(50)
|
|
await db.close()
|
|
})
|
|
|
|
it('a nested transaction still commits with its parent (savepoint), and rolls back alone on inner failure', async () => {
|
|
const db = openDb(':memory:')
|
|
await db.exec(`CREATE TABLE t (k TEXT PRIMARY KEY)`)
|
|
// Outer commits; a caught inner failure rolls back only the savepoint.
|
|
await db.transaction(async () => {
|
|
await db.run(`INSERT INTO t (k) VALUES ('outer')`)
|
|
await db.transaction(async () => {
|
|
await db.run(`INSERT INTO t (k) VALUES ('inner')`)
|
|
throw new Error('inner boom')
|
|
}).catch(() => undefined)
|
|
})
|
|
expect(await db.get(`SELECT k FROM t WHERE k='outer'`)).toBeDefined()
|
|
expect(await db.get(`SELECT k FROM t WHERE k='inner'`)).toBeUndefined()
|
|
// An outer failure rolls back everything including the nested savepoint work.
|
|
await db.transaction(async () => {
|
|
await db.run(`INSERT INTO t (k) VALUES ('a')`)
|
|
await db.transaction(async () => { await db.run(`INSERT INTO t (k) VALUES ('b')`) })
|
|
throw new Error('outer boom')
|
|
}).catch(() => undefined)
|
|
expect(await db.get(`SELECT k FROM t WHERE k='a'`)).toBeUndefined()
|
|
expect(await db.get(`SELECT k FROM t WHERE k='b'`)).toBeUndefined()
|
|
await db.close()
|
|
})
|
|
})
|