gitoriaLog in with ident

mpackdb

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Branchmaster87888725release 1.0.7caramboleyomaster/tests/causal-lock.test.js

4.0 KB

  1. import MPackDB from '../src/MPackDB.js';
  2. import { rm } from 'fs/promises';
  3. const TEST_DB_PATH = 'db/causal-lock';
  4. console.log('--- Causal Lock Test ---');
  5. console.log('An operation STARTED while another operation holds the lock must wait.');
  6. console.log('(The old depth-counter lock let any op that started during a critical');
  7. console.log('section walk right in — no in-process serialization between lanes.)\n');
  8. const sleep = ms => new Promise(r => setTimeout(r, ms));
  9. // TEST 1: a withLock section STARTED while another withLock section is in
  10. // flight must wait for it. The old depth-counter lock let it re-enter and
  11. // interleave (find-then-insert produced duplicates).
  12. await rm(TEST_DB_PATH, { recursive: true, force: true });
  13. const db = new MPackDB(`${TEST_DB_PATH}/users`, { primaryKey: '*id', indexes: ['email'] });
  14. await db.init();
  15. const events = [];
  16. const findIfAbsentInsert = origin => db.withLock(async () => {
  17. events.push(`${origin}:start`);
  18. const existing = await db.find(u => u.email === '[email protected]');
  19. await sleep(100); // hold the lock with the check result in hand
  20. if (existing.length === 0) {
  21. await db.insert({ email: '[email protected]', origin });
  22. }
  23. events.push(`${origin}:end`);
  24. });
  25. const first = findIfAbsentInsert('first');
  26. // Give the first section time to actually acquire the lock, then intrude
  27. await sleep(30);
  28. const second = findIfAbsentInsert('second');
  29. await Promise.all([first, second]);
  30. const records = await db.find(u => u.email === '[email protected]');
  31. console.log(`TEST 1: records for [email protected]: ${records.length} (expected: 1)`);
  32. console.log(` event order: ${events.join(' → ')}`);
  33. const test1Pass = records.length === 1
  34. && events.indexOf('first:end') < events.indexOf('second:start');
  35. console.log(` ${test1Pass ? '✓ PASS' : '✗ FAIL — second withLock entered the critical section mid-flight'}\n`);
  36. // TEST 2: nested operations inside withLock still re-enter (no deadlock)
  37. console.log('TEST 2: nested find/insert/delete inside withLock (re-entrancy)');
  38. const result = await Promise.race([
  39. db.withLock(async () => {
  40. const inserted = await db.insert({ email: '[email protected]' });
  41. const found = await db.find(u => u.email === '[email protected]');
  42. await db.delete(u => u.email === '[email protected]');
  43. const gone = await db.find(u => u.email === '[email protected]');
  44. return { inserted, found: found.length, gone: gone.length };
  45. }),
  46. sleep(5000).then(() => 'TIMEOUT'),
  47. ]);
  48. const test2Pass = result !== 'TIMEOUT' && result.found === 1 && result.gone === 0;
  49. console.log(` result: ${JSON.stringify(result)}`);
  50. console.log(` ${test2Pass ? '✓ PASS' : '✗ FAIL — nested ops deadlocked or misbehaved'}\n`);
  51. // TEST 3: update (delete → insert via callback) works under the causal lock
  52. console.log('TEST 3: update() nests delete+insert under one lock');
  53. await db.insert({ email: '[email protected]', v: 1 });
  54. const updated = await Promise.race([
  55. db.update(u => u.email === '[email protected]', r => { r.v = 2; return r; }),
  56. sleep(5000).then(() => 'TIMEOUT'),
  57. ]);
  58. const after = await db.find(u => u.email === '[email protected]');
  59. const test3Pass = updated !== 'TIMEOUT' && after.length === 1 && after[0].v === 2;
  60. console.log(` after update: ${JSON.stringify(after)}`);
  61. console.log(` ${test3Pass ? '✓ PASS' : '✗ FAIL'}\n`);
  62. // TEST 4: 20 concurrent inserts started in staggered waves stay serialized & unique
  63. console.log('TEST 4: staggered concurrent inserts (unique numeric PKs)');
  64. const waves = [];
  65. for (let i = 0; i < 20; i++) {
  66. waves.push((async () => {
  67. await sleep(i * 7);
  68. return db.insert({ email: `wave-${i}@example.com` });
  69. })());
  70. }
  71. const ids = await Promise.all(waves);
  72. const unique = new Set(ids);
  73. const test4Pass = unique.size === 20;
  74. console.log(` ${ids.length} inserts, ${unique.size} unique ids (expected: 20)`);
  75. console.log(` ${test4Pass ? '✓ PASS' : '✗ FAIL — primary key collision'}\n`);
  76. await db.close();
  77. const allPass = test1Pass && test2Pass && test3Pass && test4Pass;
  78. console.log(allPass ? '✓ All causal lock tests passed!' : '✗ Some causal lock tests FAILED');
  79. 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