mpackdb
All repositories: gitoria
4.0 KB
import MPackDB from '../src/MPackDB.js';import { rm } from 'fs/promises';const TEST_DB_PATH = 'db/causal-lock';console.log('--- Causal Lock Test ---');console.log('An operation STARTED while another operation holds the lock must wait.');console.log('(The old depth-counter lock let any op that started during a critical');console.log('section walk right in — no in-process serialization between lanes.)\n');const sleep = ms => new Promise(r => setTimeout(r, ms));// TEST 1: a withLock section STARTED while another withLock section is in// flight must wait for it. The old depth-counter lock let it re-enter and// interleave (find-then-insert produced duplicates).await rm(TEST_DB_PATH, { recursive: true, force: true });const db = new MPackDB(`${TEST_DB_PATH}/users`, { primaryKey: '*id', indexes: ['email'] });await db.init();const events = [];const findIfAbsentInsert = origin => db.withLock(async () => {events.push(`${origin}:start`);const existing = await db.find(u => u.email === '[email protected]');await sleep(100); // hold the lock with the check result in handif (existing.length === 0) {await db.insert({ email: '[email protected]', origin });}events.push(`${origin}:end`);});const first = findIfAbsentInsert('first');// Give the first section time to actually acquire the lock, then intrudeawait sleep(30);const second = findIfAbsentInsert('second');await Promise.all([first, second]);const records = await db.find(u => u.email === '[email protected]');console.log(`TEST 1: records for [email protected]: ${records.length} (expected: 1)`);console.log(` event order: ${events.join(' → ')}`);const test1Pass = records.length === 1&& events.indexOf('first:end') < events.indexOf('second:start');console.log(` ${test1Pass ? '✓ PASS' : '✗ FAIL — second withLock entered the critical section mid-flight'}\n`);// TEST 2: nested operations inside withLock still re-enter (no deadlock)console.log('TEST 2: nested find/insert/delete inside withLock (re-entrancy)');const result = await Promise.race([db.withLock(async () => {const inserted = await db.insert({ email: '[email protected]' });const found = await db.find(u => u.email === '[email protected]');await db.delete(u => u.email === '[email protected]');const gone = await db.find(u => u.email === '[email protected]');return { inserted, found: found.length, gone: gone.length };}),sleep(5000).then(() => 'TIMEOUT'),]);const test2Pass = result !== 'TIMEOUT' && result.found === 1 && result.gone === 0;console.log(` result: ${JSON.stringify(result)}`);console.log(` ${test2Pass ? '✓ PASS' : '✗ FAIL — nested ops deadlocked or misbehaved'}\n`);// TEST 3: update (delete → insert via callback) works under the causal lockconsole.log('TEST 3: update() nests delete+insert under one lock');await db.insert({ email: '[email protected]', v: 1 });const updated = await Promise.race([db.update(u => u.email === '[email protected]', r => { r.v = 2; return r; }),sleep(5000).then(() => 'TIMEOUT'),]);const after = await db.find(u => u.email === '[email protected]');const test3Pass = updated !== 'TIMEOUT' && after.length === 1 && after[0].v === 2;console.log(` after update: ${JSON.stringify(after)}`);console.log(` ${test3Pass ? '✓ PASS' : '✗ FAIL'}\n`);// TEST 4: 20 concurrent inserts started in staggered waves stay serialized & uniqueconsole.log('TEST 4: staggered concurrent inserts (unique numeric PKs)');const waves = [];for (let i = 0; i < 20; i++) {waves.push((async () => {await sleep(i * 7);return db.insert({ email: `wave-${i}@example.com` });})());}const ids = await Promise.all(waves);const unique = new Set(ids);const test4Pass = unique.size === 20;console.log(` ${ids.length} inserts, ${unique.size} unique ids (expected: 20)`);console.log(` ${test4Pass ? '✓ PASS' : '✗ FAIL — primary key collision'}\n`);await db.close();const allPass = test1Pass && test2Pass && test3Pass && test4Pass;console.log(allPass ? '✓ All causal lock tests passed!' : '✗ Some causal lock tests FAILED');if (!allPass) throw new Error('causal-lock tests FAILED');
Branches
- mastermain branch
Latest commits
- 87888725release 1.0.7caramboleyo
- c4cdb9b6node: import prefixes (Deno compat) + pre-existing index-state WIPcaramboleyo
- 0afb8f4bupdate now must be a callbackcaramboleyo
- cde73eb4release 1.0.6caramboleyo
- d01dda02add index hints, intersection, boundingBox; remove findByIndexcaramboleyo
- b8ffc1a0release 1.0.5caramboleyo
- d47876a1reimplemented lost features like indexed find and more testscaramboleyo
- 7f08da9afixed insert ignoring model definitioncaramboleyo
- 705774a9added flush before findcaramboleyo
- b4db6391initial commitcaramboleyo