gitoriaLog in with ident

mpackdb

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit8788872587888725release 1.0.7caramboleyo87888725/tests/concurrent-delete-during-scan.test.js

2.6 KB

  1. import MPackDB from '../src/MPackDB.js';
  2. import { rm } from 'fs/promises';
  3. const TEST_DB_PATH = 'db/concurrent-delete-during-scan';
  4. console.log('--- Concurrent Delete During Scan Test ---');
  5. console.log('Regression test for the refresh() clobber bug (BUG_REPORT.md):');
  6. console.log('an unlocked find() must not roll back un-persisted meta mutations');
  7. console.log('of a concurrent delete()/insert() in the same process.\n');
  8. await rm(TEST_DB_PATH, { recursive: true, force: true });
  9. // UTXO-like store: string primary key + secondary indexes, like the blockchain uses
  10. const db = new MPackDB(`${TEST_DB_PATH}/utxos`, {
  11. primaryKey: 'id',
  12. indexes: ['symbol', '*blockNumber'],
  13. });
  14. const ROUNDS = 40;
  15. let resurrections = 0;
  16. let lostInserts = 0;
  17. for (let round = 0; round < ROUNDS; round++) {
  18. const spentId = `spent-${round}`;
  19. const keptId = `kept-${round}`;
  20. await db.insert({ id: spentId, symbol: 'ADR', blockNumber: round, v: 1 });
  21. // Hammer scans while deleting, so a scan's refresh() lands inside delete()'s await window
  22. const hammer = (async () => {
  23. for (let i = 0; i < 25; i++) {
  24. for await (const _ of db.find(() => true)) { /* full scan */ }
  25. }
  26. })();
  27. await Promise.all([
  28. db.delete(spentId),
  29. db.insert({ id: keptId, symbol: 'ADR', blockNumber: round, v: 2 }),
  30. hammer,
  31. ]);
  32. const spent = await db.find(spentId);
  33. if (spent.length !== 0) {
  34. resurrections++;
  35. console.log(` round ${round}: ✗ deleted record '${spentId}' RESURRECTED (${spent.length} copies)`);
  36. }
  37. const kept = await db.find(keptId);
  38. if (kept.length !== 1) {
  39. lostInserts++;
  40. console.log(` round ${round}: ✗ inserted record '${keptId}' wrong count: ${kept.length} (expected 1)`);
  41. }
  42. }
  43. // Also verify against a full unindexed scan (bypasses index paths)
  44. const all = await db.find(() => true);
  45. const spentAlive = all.filter(r => r.id.startsWith('spent-'));
  46. const keptAlive = all.filter(r => r.id.startsWith('kept-'));
  47. console.log(`\nRounds: ${ROUNDS}`);
  48. console.log(`Resurrected deletes (PK lookup): ${resurrections} (expected: 0)`);
  49. console.log(`Lost inserts (PK lookup): ${lostInserts} (expected: 0)`);
  50. console.log(`Deleted records in full scan: ${spentAlive.length} (expected: 0)`);
  51. console.log(`Kept records in full scan: ${keptAlive.length} (expected: ${ROUNDS})`);
  52. await db.close();
  53. const pass = resurrections === 0 && lostInserts === 0 && spentAlive.length === 0 && keptAlive.length === ROUNDS;
  54. console.log(`\n${pass ? '✓ All concurrent delete-during-scan tests passed!' : '✗ FAILED — concurrent scans corrupted meta'}`);
  55. if (!pass) {
  56. throw new Error('concurrent-delete-during-scan FAILED: deletes resurrected or inserts lost during concurrent scans');
  57. }

Branches

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