@harperfast/rocksdb-js 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -27
- package/dist/index.cjs +322 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -39
- package/dist/index.d.mts +178 -39
- package/dist/index.mjs +320 -67
- package/dist/index.mjs.map +1 -1
- package/docs/backups.md +27 -0
- package/docs/performance/mutex-contention.md +4 -3
- package/docs/stats.md +11 -4
- package/docs/steady-clock-design.md +1 -1
- package/docs/transaction-log.md +27 -7
- package/package.json +19 -18
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
|
|
2
|
+
// \0rolldown/runtime.js
|
|
3
3
|
var __create = Object.create;
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -25,7 +25,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
enumerable: true
|
|
26
26
|
}) : target, mod));
|
|
27
27
|
|
|
28
|
-
//#endregion
|
|
29
28
|
let node_child_process = require("node:child_process");
|
|
30
29
|
let node_fs = require("node:fs");
|
|
31
30
|
let node_module = require("node:module");
|
|
@@ -37,7 +36,7 @@ ordered_binary = __toESM(ordered_binary, 1);
|
|
|
37
36
|
let _harperfast_extended_iterable = require("@harperfast/extended-iterable");
|
|
38
37
|
let msgpackr = require("msgpackr");
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
// src/load-binding.ts
|
|
41
40
|
const nativeExtRE = /\.node$/;
|
|
42
41
|
const req = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
43
42
|
/**
|
|
@@ -172,7 +171,7 @@ const currentThreadId = binding.currentThreadId;
|
|
|
172
171
|
* Reads the process-wide steady clock: `std::chrono::steady_clock` as
|
|
173
172
|
* fractional milliseconds from an unspecified origin that is fixed for the life
|
|
174
173
|
* of the process. Every sample in the process — main thread and every
|
|
175
|
-
* `worker_threads` worker, whenever started or restarted, on Node
|
|
174
|
+
* `worker_threads` worker, whenever started or restarted, on Node and Bun
|
|
176
175
|
* — is in one domain, and the difference between two samples is real elapsed
|
|
177
176
|
* time unaffected by wall-clock steps. Non-decreasing but not unique (two
|
|
178
177
|
* samples may be equal; treat `==` as "not later"). Windows QPC samples from
|
|
@@ -210,12 +209,30 @@ const transactionLogMapCount = binding.transactionLogMapCount;
|
|
|
210
209
|
*/
|
|
211
210
|
const lockWakeCallbackCount = binding.lockWakeCallbackCount;
|
|
212
211
|
/**
|
|
212
|
+
* The calling thread's counters for the per-transaction iterator pool: pooled
|
|
213
|
+
* iterators created, and idle ones reused by a later range. Internal — used by
|
|
214
|
+
* tests to prove a range was served by a reused iterator.
|
|
215
|
+
*/
|
|
216
|
+
const iteratorPoolStats = binding.iteratorPoolStats;
|
|
217
|
+
/**
|
|
218
|
+
* Whether transaction `getRange()` reuses pooled native iterators (off with
|
|
219
|
+
* `ROCKSDB_JS_ITERATOR_POOL=0`). Internal.
|
|
220
|
+
*/
|
|
221
|
+
const iteratorPoolEnabled = binding.iteratorPoolEnabled;
|
|
222
|
+
/**
|
|
213
223
|
* Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
|
|
214
224
|
* no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
|
|
215
225
|
* disarm. Used by the ERR_TRY_AGAIN retry regression test.
|
|
216
226
|
*/
|
|
217
227
|
const forceTryAgainForTesting = binding.forceTryAgainForTesting;
|
|
218
228
|
/**
|
|
229
|
+
* Test-only: turn the RocksDB conflict check before the transaction-log write off (`true`) or back
|
|
230
|
+
* on, so a test can make a logged batch conflict at commit from a stale snapshot.
|
|
231
|
+
*/
|
|
232
|
+
const disablePrelogConflictCheckForTesting = binding.disablePrelogConflictCheckForTesting;
|
|
233
|
+
/** Test-only: run the pre-log conflict check on retries alone (`true`, the default) or on every attempt. */
|
|
234
|
+
const setPrelogConflictCheckRetriesOnlyForTesting = binding.setPrelogConflictCheckRetriesOnlyForTesting;
|
|
235
|
+
/**
|
|
219
236
|
* Test-only seam: makes the physical column-family drop report failure.
|
|
220
237
|
* `0` = inert, `1` = fail without dropping (the retry performs the drop),
|
|
221
238
|
* `2` = drop for real and then report failure (the retry resolves as
|
|
@@ -258,8 +275,7 @@ const nativeValidateTransactionLog = binding.validateTransactionLog;
|
|
|
258
275
|
const stats = binding.stats;
|
|
259
276
|
const version = binding.version;
|
|
260
277
|
|
|
261
|
-
|
|
262
|
-
//#region src/validate-transaction-log.ts
|
|
278
|
+
// src/validate-transaction-log.ts
|
|
263
279
|
/**
|
|
264
280
|
* Validates a transaction log store directory: every `<sequence>.txnlog` file
|
|
265
281
|
* is checked for a valid header (token, version) and intact v1 entry framing
|
|
@@ -299,8 +315,7 @@ function validateTransactionLogStore(path, options) {
|
|
|
299
315
|
return new Promise((resolve, reject) => nativeValidateTransactionLog(resolve, reject, path, options?.strict ?? false));
|
|
300
316
|
}
|
|
301
317
|
|
|
302
|
-
|
|
303
|
-
//#region src/backup.ts
|
|
318
|
+
// src/backup.ts
|
|
304
319
|
/** Subdirectory (under a backup directory) holding per-backup transaction log snapshots. */
|
|
305
320
|
const TRANSACTION_LOGS_DIRNAME = "transaction_logs";
|
|
306
321
|
/** Non-blocking existence check (`fs.existsSync` would block the event loop). */
|
|
@@ -550,6 +565,14 @@ const backups = {
|
|
|
550
565
|
* Verifies a backup's file sizes, and optionally their checksums (which
|
|
551
566
|
* requires reading all backed-up data).
|
|
552
567
|
*
|
|
568
|
+
* Set `verifyTables: true` to also prove this build can read the backup
|
|
569
|
+
* before restoring it: every table file is opened, one at a time, and its
|
|
570
|
+
* footer (including `format_version`) and properties must be readable, and
|
|
571
|
+
* every compression algorithm it records must be supported by this build.
|
|
572
|
+
* A checksum-valid table written by a newer or differently built RocksDB
|
|
573
|
+
* otherwise passes and fails on the first read after a restore. Blob files
|
|
574
|
+
* are not checked.
|
|
575
|
+
*
|
|
553
576
|
* When the backup was created with `transactionLogs: true`, the backup's
|
|
554
577
|
* transaction log snapshot (`<backupDir>/transaction_logs/<backupId>/`) is
|
|
555
578
|
* also validated — every log file's header and entry framing must be intact
|
|
@@ -557,7 +580,9 @@ const backups = {
|
|
|
557
580
|
* is a verification failure). Set `verifyTransactionLogs: false` to skip.
|
|
558
581
|
*/
|
|
559
582
|
async verify(backupDir, backupId, options) {
|
|
560
|
-
|
|
583
|
+
const verifyTables = options?.verifyTables;
|
|
584
|
+
if (verifyTables !== void 0 && typeof verifyTables !== "boolean") throw new TypeError(`verifyTables must be a boolean, received ${verifyTables === null ? "null" : typeof verifyTables}`);
|
|
585
|
+
await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false, verifyTables === true));
|
|
561
586
|
if (options?.verifyTransactionLogs === false) return;
|
|
562
587
|
const logsDir = (0, node_path.join)(backupDir, TRANSACTION_LOGS_DIRNAME, String(backupId));
|
|
563
588
|
if (!await exists(logsDir)) return;
|
|
@@ -574,8 +599,7 @@ const backups = {
|
|
|
574
599
|
}
|
|
575
600
|
};
|
|
576
601
|
|
|
577
|
-
|
|
578
|
-
//#region src/count-estimator.ts
|
|
602
|
+
// src/count-estimator.ts
|
|
579
603
|
/**
|
|
580
604
|
* Below this many traversed entries the observed density is too noisy to
|
|
581
605
|
* calibrate with, so `estimate()` returns the raw statistical estimate.
|
|
@@ -718,8 +742,7 @@ var CountEstimator = class {
|
|
|
718
742
|
}
|
|
719
743
|
};
|
|
720
744
|
|
|
721
|
-
|
|
722
|
-
//#region src/util.ts
|
|
745
|
+
// src/util.ts
|
|
723
746
|
/**
|
|
724
747
|
* Parses a duration string into milliseconds.
|
|
725
748
|
*
|
|
@@ -790,8 +813,7 @@ function when(subject, callback, errback) {
|
|
|
790
813
|
}
|
|
791
814
|
}
|
|
792
815
|
|
|
793
|
-
|
|
794
|
-
//#region src/dbi.ts
|
|
816
|
+
// src/dbi.ts
|
|
795
817
|
/**
|
|
796
818
|
* The base class for all database operations. This base class is shared by
|
|
797
819
|
* `RocksDatabase` and `Transaction`.
|
|
@@ -1076,8 +1098,7 @@ var DBI = class DBI {
|
|
|
1076
1098
|
}
|
|
1077
1099
|
};
|
|
1078
1100
|
|
|
1079
|
-
|
|
1080
|
-
//#region src/tar.ts
|
|
1101
|
+
// src/tar.ts
|
|
1081
1102
|
/**
|
|
1082
1103
|
* A minimal, dependency-free streaming USTAR (POSIX tar) encoder.
|
|
1083
1104
|
*
|
|
@@ -1283,8 +1304,7 @@ var TarEncoder = class {
|
|
|
1283
1304
|
}
|
|
1284
1305
|
};
|
|
1285
1306
|
|
|
1286
|
-
|
|
1287
|
-
//#region src/backup-stream.ts
|
|
1307
|
+
// src/backup-stream.ts
|
|
1288
1308
|
/** Native event discriminator: a new file header (vs. a payload chunk). */
|
|
1289
1309
|
const EVENT_FILE = 0;
|
|
1290
1310
|
/**
|
|
@@ -1328,9 +1348,22 @@ async function backupToStream(db, stream, options) {
|
|
|
1328
1348
|
}
|
|
1329
1349
|
}
|
|
1330
1350
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
const
|
|
1351
|
+
// src/dbi-iterator.ts
|
|
1352
|
+
const { ITERATOR_RESULT_DONE, ITERATOR_RESULT_FAST, ITERATOR_RESULT_RELEASED } = constants;
|
|
1353
|
+
const transactionsToTrim = /* @__PURE__ */ new Set();
|
|
1354
|
+
function trimIdleIterators() {
|
|
1355
|
+
try {
|
|
1356
|
+
for (const transaction of transactionsToTrim) transaction.trimIterators();
|
|
1357
|
+
} finally {
|
|
1358
|
+
transactionsToTrim.clear();
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function scheduleTrim(transaction) {
|
|
1362
|
+
if (transaction !== void 0) {
|
|
1363
|
+
if (transactionsToTrim.size === 0) setImmediate(trimIdleIterators);
|
|
1364
|
+
transactionsToTrim.add(transaction);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1334
1367
|
const DONE_RESULT = Object.freeze({
|
|
1335
1368
|
done: true,
|
|
1336
1369
|
value: void 0
|
|
@@ -1350,10 +1383,12 @@ var DBIterator = class {
|
|
|
1350
1383
|
#includeValues;
|
|
1351
1384
|
#limit;
|
|
1352
1385
|
#yielded = 0;
|
|
1353
|
-
|
|
1386
|
+
#transaction;
|
|
1387
|
+
constructor(iterator, store, includeValues, limit, transaction) {
|
|
1354
1388
|
this.iterator = iterator;
|
|
1355
1389
|
this.store = store;
|
|
1356
1390
|
this.#includeValues = includeValues;
|
|
1391
|
+
this.#transaction = transaction;
|
|
1357
1392
|
this.#limit = limit ?? void 0;
|
|
1358
1393
|
}
|
|
1359
1394
|
[Symbol.iterator]() {
|
|
@@ -1361,7 +1396,7 @@ var DBIterator = class {
|
|
|
1361
1396
|
}
|
|
1362
1397
|
next() {
|
|
1363
1398
|
if (this.#limit !== void 0 && this.#yielded >= this.#limit) {
|
|
1364
|
-
this.iterator.return
|
|
1399
|
+
this.#release(this.iterator.return());
|
|
1365
1400
|
return DONE_RESULT;
|
|
1366
1401
|
}
|
|
1367
1402
|
let result;
|
|
@@ -1369,11 +1404,15 @@ var DBIterator = class {
|
|
|
1369
1404
|
result = this.iterator.next();
|
|
1370
1405
|
} catch (err) {
|
|
1371
1406
|
try {
|
|
1372
|
-
this.iterator.return
|
|
1407
|
+
this.#release(this.iterator.return());
|
|
1373
1408
|
} catch {}
|
|
1374
1409
|
throw err;
|
|
1375
1410
|
}
|
|
1376
1411
|
if (result === ITERATOR_RESULT_DONE) return DONE_RESULT;
|
|
1412
|
+
if (result === ITERATOR_RESULT_RELEASED) {
|
|
1413
|
+
scheduleTrim(this.#transaction);
|
|
1414
|
+
return DONE_RESULT;
|
|
1415
|
+
}
|
|
1377
1416
|
this.#yielded++;
|
|
1378
1417
|
const includeValues = this.#includeValues;
|
|
1379
1418
|
const value = {};
|
|
@@ -1394,20 +1433,23 @@ var DBIterator = class {
|
|
|
1394
1433
|
};
|
|
1395
1434
|
}
|
|
1396
1435
|
return(value) {
|
|
1397
|
-
this.iterator.return
|
|
1436
|
+
this.#release(this.iterator.return());
|
|
1398
1437
|
return {
|
|
1399
1438
|
done: true,
|
|
1400
1439
|
value
|
|
1401
1440
|
};
|
|
1402
1441
|
}
|
|
1403
1442
|
throw(err) {
|
|
1404
|
-
this.iterator.throw
|
|
1443
|
+
this.#release(this.iterator.throw(err));
|
|
1405
1444
|
throw err;
|
|
1406
1445
|
}
|
|
1446
|
+
#release(pooled) {
|
|
1447
|
+
if (pooled) scheduleTrim(this.#transaction);
|
|
1448
|
+
this.#transaction = void 0;
|
|
1449
|
+
}
|
|
1407
1450
|
};
|
|
1408
1451
|
|
|
1409
|
-
|
|
1410
|
-
//#region src/encoding.ts
|
|
1452
|
+
// src/encoding.ts
|
|
1411
1453
|
/**
|
|
1412
1454
|
* Initializes the key encoder functions.
|
|
1413
1455
|
*
|
|
@@ -1474,8 +1516,7 @@ function createFixedBuffer(size) {
|
|
|
1474
1516
|
return buffer;
|
|
1475
1517
|
}
|
|
1476
1518
|
|
|
1477
|
-
|
|
1478
|
-
//#region src/store.ts
|
|
1519
|
+
// src/store.ts
|
|
1479
1520
|
const { ONLY_IF_IN_MEMORY_CACHE_FLAG, NOT_IN_MEMORY_CACHE_FLAG, ALWAYS_CREATE_NEW_BUFFER_FLAG, FRESH_VERSION_FLAG, POPULATE_VERSION_FLAG, ITERATOR_REVERSE_FLAG, ITERATOR_INCLUSIVE_END_FLAG, ITERATOR_EXCLUSIVE_START_FLAG, ITERATOR_INCLUDE_VALUES_FLAG, ITERATOR_NEEDS_STABLE_VALUE_BUFFER_FLAG, ITERATOR_CONTEXT_IS_TRANSACTION_FLAG, ITERATOR_HAS_TRANSACTION_ID_FLAG } = constants;
|
|
1480
1521
|
const KEY_BUFFER_SIZE = 4096;
|
|
1481
1522
|
const KEY_BUFFER = createFixedBuffer(KEY_BUFFER_SIZE);
|
|
@@ -1776,6 +1817,10 @@ var Store = class {
|
|
|
1776
1817
|
*/
|
|
1777
1818
|
writeBufferSize;
|
|
1778
1819
|
/**
|
|
1820
|
+
* The memtable arena block size in bytes; `0` or unset is automatic.
|
|
1821
|
+
*/
|
|
1822
|
+
arenaBlockSize;
|
|
1823
|
+
/**
|
|
1779
1824
|
* The function used to encode keys using the shared `keyBuffer`.
|
|
1780
1825
|
*/
|
|
1781
1826
|
writeKey;
|
|
@@ -1828,6 +1873,7 @@ var Store = class {
|
|
|
1828
1873
|
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1829
1874
|
this.verificationTable = options?.verificationTable;
|
|
1830
1875
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1876
|
+
this.arenaBlockSize = options?.arenaBlockSize;
|
|
1831
1877
|
this.writeKey = writeKey;
|
|
1832
1878
|
this.#columnFamilyOptions = options ? { ...options } : void 0;
|
|
1833
1879
|
}
|
|
@@ -2078,7 +2124,9 @@ var Store = class {
|
|
|
2078
2124
|
if (context !== this.db) flags |= ITERATOR_CONTEXT_IS_TRANSACTION_FLAG;
|
|
2079
2125
|
if (txnId !== void 0) flags |= ITERATOR_HAS_TRANSACTION_ID_FLAG;
|
|
2080
2126
|
const advancedOptions = options !== void 0 && (options.adaptiveReadahead !== void 0 || options.asyncIO !== void 0 || options.autoReadaheadSize !== void 0 || options.backgroundPurgeOnIteratorCleanup !== void 0 || options.fillCache !== void 0 || options.readaheadSize !== void 0 || options.tailing !== void 0) ? options : void 0;
|
|
2081
|
-
|
|
2127
|
+
const nativeIterator = new NativeIterator(context, flags, startKeyEnd, endKeyStart, endKeyEnd, advancedOptions, txnId);
|
|
2128
|
+
const transaction = context !== this.db ? context : options?.transaction?._context;
|
|
2129
|
+
return new _harperfast_extended_iterable.ExtendedIterable(new DBIterator(nativeIterator, this, includeValues, options?.limit, transaction));
|
|
2082
2130
|
}
|
|
2083
2131
|
getSync(context, key, alwaysCreateNewBuffer = false, options) {
|
|
2084
2132
|
const keyParam = getKeyParam(this.encodeKey(key));
|
|
@@ -2192,7 +2240,8 @@ var Store = class {
|
|
|
2192
2240
|
transactionLogsPath: this.transactionLogsPath,
|
|
2193
2241
|
timestampFloorLog: this.timestampFloorLog,
|
|
2194
2242
|
verificationTable: this.verificationTable,
|
|
2195
|
-
writeBufferSize: this.writeBufferSize
|
|
2243
|
+
writeBufferSize: this.writeBufferSize,
|
|
2244
|
+
arenaBlockSize: this.arenaBlockSize
|
|
2196
2245
|
});
|
|
2197
2246
|
this.identityPath = this.db.identityPath;
|
|
2198
2247
|
return false;
|
|
@@ -2298,8 +2347,7 @@ function getKeyParam(keyBuffer) {
|
|
|
2298
2347
|
return keyBuffer.length;
|
|
2299
2348
|
}
|
|
2300
2349
|
|
|
2301
|
-
|
|
2302
|
-
//#region src/transaction.ts
|
|
2350
|
+
// src/transaction.ts
|
|
2303
2351
|
/**
|
|
2304
2352
|
* Sentinel value returned by `commit()` when `coordinatedRetry: true` and the
|
|
2305
2353
|
* transaction encountered an IsBusy conflict. The native layer parks on VT
|
|
@@ -2323,10 +2371,18 @@ var TransactionAlreadyAbortedError = class extends Error {
|
|
|
2323
2371
|
*/
|
|
2324
2372
|
var TransactionRetryableError = class extends Error {
|
|
2325
2373
|
hasLog;
|
|
2374
|
+
/**
|
|
2375
|
+
* The attempt was refused before writing its transaction-log batch (`transactionLogAbortMarkers`):
|
|
2376
|
+
* an older transaction held a write intent on one of its keys (`transactionLogWriteIntents`), or, on a retry, RocksDB's conflict
|
|
2377
|
+
* check found a write that already committed (see `lastConflictKind()`). Nothing of it is in the
|
|
2378
|
+
* log, so a retry may stage its batch afresh.
|
|
2379
|
+
*/
|
|
2380
|
+
refusedBeforeLog;
|
|
2326
2381
|
txn;
|
|
2327
2382
|
constructor(error, txn) {
|
|
2328
2383
|
super(error.message);
|
|
2329
2384
|
this.hasLog = error.hasLog ?? false;
|
|
2385
|
+
this.refusedBeforeLog = error.refusedBeforeLog ?? false;
|
|
2330
2386
|
this.txn = txn;
|
|
2331
2387
|
}
|
|
2332
2388
|
};
|
|
@@ -2356,6 +2412,38 @@ var TransactionAbandonedError = class extends Error {
|
|
|
2356
2412
|
}
|
|
2357
2413
|
};
|
|
2358
2414
|
/**
|
|
2415
|
+
* `abort()` of a transaction whose transaction-log batch could not be marked aborted
|
|
2416
|
+
* (`transactionLogAbortMarkers`). The batch stays unresolved, so committed readers do not pass it and
|
|
2417
|
+
* the log store refuses appends, until a retry on the next append, database flush or store close
|
|
2418
|
+
* marks it.
|
|
2419
|
+
*/
|
|
2420
|
+
var TransactionAbortUnresolvedError = class extends Error {
|
|
2421
|
+
code = "ERR_TRANSACTION_ABORT_UNRESOLVED";
|
|
2422
|
+
txn;
|
|
2423
|
+
constructor(error, txn) {
|
|
2424
|
+
super(error.message);
|
|
2425
|
+
this.txn = txn;
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
/**
|
|
2429
|
+
* Why a transaction's most recent attempt was refused or retried, from {@link Transaction.lastConflictKind}.
|
|
2430
|
+
*/
|
|
2431
|
+
const ConflictKind = {
|
|
2432
|
+
none: 0,
|
|
2433
|
+
/** Refused on an older transaction's write intent before writing its log batch. */
|
|
2434
|
+
intentBeforeLog: 1,
|
|
2435
|
+
/** Refused on an older transaction's write intent after its log batch was written. */
|
|
2436
|
+
intentAfterLog: 2,
|
|
2437
|
+
/** RocksDB found a conflict at commit. */
|
|
2438
|
+
atCommit: 3,
|
|
2439
|
+
/**
|
|
2440
|
+
* RocksDB's conflict check, run before the log write, found a write that already committed, or,
|
|
2441
|
+
* as at commit, could not validate because the memtable history it needs was flushed (needs a
|
|
2442
|
+
* RocksDB build that exports the check).
|
|
2443
|
+
*/
|
|
2444
|
+
checkBeforeLog: 4
|
|
2445
|
+
};
|
|
2446
|
+
/**
|
|
2359
2447
|
* Provides transaction level operations to a transaction callback.
|
|
2360
2448
|
*/
|
|
2361
2449
|
var Transaction = class extends DBI {
|
|
@@ -2370,9 +2458,10 @@ var Transaction = class extends DBI {
|
|
|
2370
2458
|
if (store.readOnly) {
|
|
2371
2459
|
super(store);
|
|
2372
2460
|
this.#txn = { id: 0 };
|
|
2373
|
-
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = () => {};
|
|
2461
|
+
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = this.setParkDeadline = () => {};
|
|
2374
2462
|
this.commit = async () => {};
|
|
2375
|
-
this.getTimestamp = () => 0;
|
|
2463
|
+
this.getTimestamp = this.lastConflictKind = () => 0;
|
|
2464
|
+
this.logWritten = () => false;
|
|
2376
2465
|
} else {
|
|
2377
2466
|
const txn = new NativeTransaction(store.db, options);
|
|
2378
2467
|
super(store, txn);
|
|
@@ -2398,6 +2487,7 @@ var Transaction = class extends DBI {
|
|
|
2398
2487
|
this.#txn.abort();
|
|
2399
2488
|
} catch (err) {
|
|
2400
2489
|
if (err instanceof Error && "code" in err && err.code === "ERR_TRANSACTION_ABANDONED") throw new TransactionAbandonedError(err, this);
|
|
2490
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_TRANSACTION_ABORT_UNRESOLVED") throw new TransactionAbortUnresolvedError(err, this);
|
|
2401
2491
|
throw err;
|
|
2402
2492
|
}
|
|
2403
2493
|
}
|
|
@@ -2466,6 +2556,28 @@ var Transaction = class extends DBI {
|
|
|
2466
2556
|
return this.#txn.getTimestamp();
|
|
2467
2557
|
}
|
|
2468
2558
|
/**
|
|
2559
|
+
* Whether this transaction's batch is in the transaction log. A commit conflict after the batch
|
|
2560
|
+
* was written keeps it for the retry, which must not stage it again; an attempt refused before
|
|
2561
|
+
* writing it (`refusedBeforeLog`) leaves nothing.
|
|
2562
|
+
*/
|
|
2563
|
+
logWritten() {
|
|
2564
|
+
return this.#txn.logWritten();
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* Why the most recent attempt was refused or retried; one of {@link ConflictKind}.
|
|
2568
|
+
*/
|
|
2569
|
+
lastConflictKind() {
|
|
2570
|
+
return this.#txn.lastConflictKind();
|
|
2571
|
+
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Bounds coordinated-retry parks: past `deadline` (epoch milliseconds) a parked commit resolves
|
|
2574
|
+
* `RETRY_NOW` at once, so a caller enforcing its own deadline can abort instead of waiting out
|
|
2575
|
+
* the default park bound. 0 clears it.
|
|
2576
|
+
*/
|
|
2577
|
+
setParkDeadline(deadline) {
|
|
2578
|
+
this.#txn.setParkDeadline(deadline);
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2469
2581
|
* Get the transaction id.
|
|
2470
2582
|
*/
|
|
2471
2583
|
get id() {
|
|
@@ -2486,8 +2598,8 @@ var Transaction = class extends DBI {
|
|
|
2486
2598
|
}
|
|
2487
2599
|
};
|
|
2488
2600
|
|
|
2489
|
-
|
|
2490
|
-
|
|
2601
|
+
// src/database.ts
|
|
2602
|
+
const backoffCell = new Int32Array(new SharedArrayBuffer(4));
|
|
2491
2603
|
const BACKGROUND_ERROR_SEVERITY_NAMES = [
|
|
2492
2604
|
"none",
|
|
2493
2605
|
"soft",
|
|
@@ -2897,6 +3009,19 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2897
3009
|
return this.store.db.flushSync(options);
|
|
2898
3010
|
}
|
|
2899
3011
|
/**
|
|
3012
|
+
* Syncs the write-ahead log to stable storage (`DB::SyncWAL`), so every WAL-backed write
|
|
3013
|
+
* committed before the call survives a power loss afterwards. Unlike {@link flush} it writes
|
|
3014
|
+
* no SST file and never stalls writers. Not covered: writes that skipped the WAL
|
|
3015
|
+
* (`disableWAL`), and transaction-log files, which keep their own durability. Runs on the
|
|
3016
|
+
* libuv threadpool; RocksDB serializes concurrent syncs of one WAL, so keep one in flight
|
|
3017
|
+
* per database rather than parking pool workers on a queue of them.
|
|
3018
|
+
*
|
|
3019
|
+
* On a read-only database the returned promise resolves without syncing.
|
|
3020
|
+
*/
|
|
3021
|
+
syncWal() {
|
|
3022
|
+
return new Promise((resolve, reject) => this.store.db.syncWal(resolve, reject));
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
2900
3025
|
* Returns the most recent {@link BackgroundError} observed on this database, or
|
|
2901
3026
|
* `null` when none has occurred. The `'error'` event is the push counterpart;
|
|
2902
3027
|
* this is the pull equivalent for on-demand checks (e.g. a health probe) and
|
|
@@ -3365,7 +3490,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3365
3490
|
async transaction(callback, options) {
|
|
3366
3491
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3367
3492
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3368
|
-
const txn = new Transaction(this.store, options
|
|
3493
|
+
const txn = new Transaction(this.store, options?.retryOnBusy === false && options.coordinatedRetry ? {
|
|
3494
|
+
...options,
|
|
3495
|
+
coordinatedRetry: false
|
|
3496
|
+
} : options);
|
|
3369
3497
|
let result;
|
|
3370
3498
|
this.notify("begin-transaction");
|
|
3371
3499
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -3379,7 +3507,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3379
3507
|
commitResult = await txn.commit();
|
|
3380
3508
|
} catch (commitErr) {
|
|
3381
3509
|
if (commitErr instanceof TransactionAlreadyAbortedError) return;
|
|
3382
|
-
if (commitErr instanceof TransactionRetryableError && (options?.retryOnBusy ?? commitErr.hasLog) && attempt < maxRetries) continue;
|
|
3510
|
+
if (commitErr instanceof TransactionRetryableError && (options?.retryOnBusy ?? (commitErr.hasLog || commitErr.refusedBeforeLog)) && attempt < maxRetries) continue;
|
|
3383
3511
|
this.#abandonTransaction(txn, commitErr);
|
|
3384
3512
|
return;
|
|
3385
3513
|
}
|
|
@@ -3411,10 +3539,13 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3411
3539
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3412
3540
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3413
3541
|
const isRetryable = (err, attempt) => {
|
|
3414
|
-
|
|
3542
|
+
if (!(err instanceof TransactionRetryableError && (options?.retryOnBusy ?? (err.hasLog || err.refusedBeforeLog)) && attempt <= maxRetries)) return false;
|
|
3543
|
+
const kind = txn.lastConflictKind();
|
|
3544
|
+
if (kind === ConflictKind.intentBeforeLog || kind === ConflictKind.intentAfterLog) Atomics.wait(backoffCell, 0, 0, Math.min(2 ** attempt, 50));
|
|
3545
|
+
return true;
|
|
3415
3546
|
};
|
|
3547
|
+
const txn = new Transaction(this.store, options);
|
|
3416
3548
|
const runAttempt = (attempt) => {
|
|
3417
|
-
const txn = new Transaction(this.store, options);
|
|
3418
3549
|
let result;
|
|
3419
3550
|
try {
|
|
3420
3551
|
result = callback(txn, attempt);
|
|
@@ -3430,7 +3561,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3430
3561
|
if (isRetryable(commitErr, attempt)) return runAttempt(attempt + 1);
|
|
3431
3562
|
this.#abandonTransaction(txn, commitErr);
|
|
3432
3563
|
}
|
|
3433
|
-
});
|
|
3564
|
+
}, (callbackErr) => this.#abortTransaction(txn, callbackErr));
|
|
3434
3565
|
try {
|
|
3435
3566
|
txn.commitSync();
|
|
3436
3567
|
return result;
|
|
@@ -3455,7 +3586,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3455
3586
|
try {
|
|
3456
3587
|
txn.abort();
|
|
3457
3588
|
} catch (abortErr) {
|
|
3458
|
-
if (abortErr instanceof TransactionAbandonedError) throw abortErr;
|
|
3589
|
+
if (abortErr instanceof TransactionAbandonedError || abortErr instanceof TransactionAbortUnresolvedError) throw abortErr;
|
|
3459
3590
|
}
|
|
3460
3591
|
throw commitErr;
|
|
3461
3592
|
}
|
|
@@ -3570,10 +3701,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3570
3701
|
}
|
|
3571
3702
|
};
|
|
3572
3703
|
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
const { TRANSACTION_LOG_TOKEN, TRANSACTION_LOG_ENTRY_HEADER_SIZE: TRANSACTION_LOG_ENTRY_HEADER_SIZE$1 } = constants;
|
|
3704
|
+
// src/parse-transaction-log.ts
|
|
3705
|
+
const { TRANSACTION_LOG_TOKEN, TRANSACTION_LOG_ENTRY_HEADER_SIZE: TRANSACTION_LOG_ENTRY_HEADER_SIZE$1, TRANSACTION_LOG_ENTRY_LAST_FLAG: TRANSACTION_LOG_ENTRY_LAST_FLAG$1, TRANSACTION_LOG_ENTRY_ABORTED_FLAG: TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1 } = constants;
|
|
3576
3706
|
const MIN_VALID_TIMESTAMP = Date.UTC(2026, 0, 27);
|
|
3707
|
+
const VALID_FLAGS_MASK = TRANSACTION_LOG_ENTRY_LAST_FLAG$1 | TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1;
|
|
3577
3708
|
/**
|
|
3578
3709
|
* Loads an entire transaction log file into memory.
|
|
3579
3710
|
* @param path - The path to the transaction log file.
|
|
@@ -3611,6 +3742,8 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3611
3742
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) anomalies.push(`Header timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3612
3743
|
const entries = [];
|
|
3613
3744
|
let entryAnomalyCount = 0;
|
|
3745
|
+
let previousEndedTransaction = true;
|
|
3746
|
+
let previousTimestamp = NaN;
|
|
3614
3747
|
while (fileOffset < size) {
|
|
3615
3748
|
const timestamp = read(8).readDoubleBE(0);
|
|
3616
3749
|
if (timestamp === 0) {
|
|
@@ -3627,7 +3760,11 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3627
3760
|
const data = read(length);
|
|
3628
3761
|
const entryAnomalies = [];
|
|
3629
3762
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) entryAnomalies.push(`timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3630
|
-
if ((flags &
|
|
3763
|
+
if ((flags & ~VALID_FLAGS_MASK) !== 0) entryAnomalies.push(`flags 0x${flags.toString(16).padStart(2, "0")} contains undefined bits (expected 0x00-0x03)`);
|
|
3764
|
+
const startsTransaction = previousEndedTransaction || timestamp !== previousTimestamp;
|
|
3765
|
+
if ((flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1) !== 0 && !startsTransaction) entryAnomalies.push("abort flag on an entry that does not start a transaction");
|
|
3766
|
+
previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG$1) !== 0;
|
|
3767
|
+
previousTimestamp = timestamp;
|
|
3631
3768
|
const entry = {
|
|
3632
3769
|
timestamp,
|
|
3633
3770
|
length,
|
|
@@ -3656,11 +3793,11 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3656
3793
|
}
|
|
3657
3794
|
}
|
|
3658
3795
|
|
|
3659
|
-
|
|
3660
|
-
//#region src/transaction-log-reader.ts
|
|
3796
|
+
// src/transaction-log-reader.ts
|
|
3661
3797
|
const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
|
|
3662
3798
|
const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
|
|
3663
|
-
const
|
|
3799
|
+
const EXACT_START_SPAN = /* @__PURE__ */ new Float64Array(3);
|
|
3800
|
+
const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE, TRANSACTION_LOG_ENTRY_LAST_FLAG, TRANSACTION_LOG_ENTRY_ABORTED_FLAG } = constants;
|
|
3664
3801
|
/**
|
|
3665
3802
|
* Number of consecutive well-formed frames that, on their own, signal that real log data has
|
|
3666
3803
|
* resumed after a framing break. Kept in step with `RESYNC_MIN_FRAMES` in
|
|
@@ -3696,6 +3833,32 @@ var CorruptFrameError = class extends RangeError {
|
|
|
3696
3833
|
}
|
|
3697
3834
|
};
|
|
3698
3835
|
/**
|
|
3836
|
+
* Thrown when an iterator advances past segments it never read and that can no longer be read —
|
|
3837
|
+
* typically ones retention purged while the reader lagged. The entries they held cannot be
|
|
3838
|
+
* recovered from this log, so a consumer replicating it must not treat the stream as contiguous.
|
|
3839
|
+
* Missing sequence numbers prove only that continuity is unknown, not that each held entries.
|
|
3840
|
+
*
|
|
3841
|
+
* The iterator is left positioned at the start of `resumeLogId`, so a caller that accepts the
|
|
3842
|
+
* discontinuity calls `next()` again to continue from there.
|
|
3843
|
+
*
|
|
3844
|
+
* Not a `RangeError`: this is not broken framing, and consumers classify those by class.
|
|
3845
|
+
*/
|
|
3846
|
+
var TransactionLogGapError = class extends Error {
|
|
3847
|
+
/** Sequence number of the last segment this iterator read up to. */
|
|
3848
|
+
logId;
|
|
3849
|
+
/** Sequence number of the segment iteration resumes in. */
|
|
3850
|
+
resumeLogId;
|
|
3851
|
+
/** Segments between `logId` and `resumeLogId` that could not be read. Empty segments are not counted. */
|
|
3852
|
+
unavailableLogCount;
|
|
3853
|
+
constructor(logId, resumeLogId, unavailableLogCount) {
|
|
3854
|
+
super(`Transaction log iterator skipped ${unavailableLogCount} unavailable segment(s) between log ${logId} and log ${resumeLogId}; entries in them were not read`);
|
|
3855
|
+
this.name = "TransactionLogGapError";
|
|
3856
|
+
this.logId = logId;
|
|
3857
|
+
this.resumeLogId = resumeLogId;
|
|
3858
|
+
this.unavailableLogCount = unavailableLogCount;
|
|
3859
|
+
}
|
|
3860
|
+
};
|
|
3861
|
+
/**
|
|
3699
3862
|
* Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
|
|
3700
3863
|
* the written extent: a single entry can legitimately exceed the rotation threshold (the first
|
|
3701
3864
|
* entry written to a fresh file is always written in full). A zero timestamp is the
|
|
@@ -3771,6 +3934,14 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3771
3934
|
let dataView;
|
|
3772
3935
|
let logBuffer = this._currentLogBuffer?.deref();
|
|
3773
3936
|
let foundExactStart = false;
|
|
3937
|
+
let stopLogId = 0;
|
|
3938
|
+
let stopPosition = 0;
|
|
3939
|
+
let tailLogId = 0;
|
|
3940
|
+
let tailPosition = 0;
|
|
3941
|
+
let segmentRemovals = 0;
|
|
3942
|
+
let previousEndedTransaction = true;
|
|
3943
|
+
let previousTimestamp = NaN;
|
|
3944
|
+
let skippingAborted = false;
|
|
3774
3945
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3775
3946
|
position = size;
|
|
3776
3947
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
@@ -3780,11 +3951,32 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3780
3951
|
FLOAT_TO_UINT32[0] = this._getLastFlushed();
|
|
3781
3952
|
if (FLOAT_TO_UINT32[0] === 0) FLOAT_TO_UINT32[0] = this._findPosition(0);
|
|
3782
3953
|
start ??= 0;
|
|
3954
|
+
} else if (exactStart) {
|
|
3955
|
+
const startPosition = this._findExactStart(start, EXACT_START_SPAN);
|
|
3956
|
+
FLOAT_TO_UINT32[0] = EXACT_START_SPAN[0];
|
|
3957
|
+
stopLogId = UINT32_FROM_FLOAT[1];
|
|
3958
|
+
stopPosition = UINT32_FROM_FLOAT[0];
|
|
3959
|
+
FLOAT_TO_UINT32[0] = EXACT_START_SPAN[1];
|
|
3960
|
+
tailLogId = UINT32_FROM_FLOAT[1];
|
|
3961
|
+
tailPosition = UINT32_FROM_FLOAT[0];
|
|
3962
|
+
segmentRemovals = EXACT_START_SPAN[2];
|
|
3963
|
+
FLOAT_TO_UINT32[0] = startPosition;
|
|
3783
3964
|
} else FLOAT_TO_UINT32[0] = this._findPosition(start);
|
|
3784
3965
|
logId = UINT32_FROM_FLOAT[1];
|
|
3785
3966
|
position = UINT32_FROM_FLOAT[0];
|
|
3786
3967
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3787
3968
|
}
|
|
3969
|
+
const positionedAt = position;
|
|
3970
|
+
if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition)) {
|
|
3971
|
+
stopLogId = 0;
|
|
3972
|
+
if (skipToTail(this, logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
3973
|
+
logId = tailLogId;
|
|
3974
|
+
position = tailPosition;
|
|
3975
|
+
logBuffer = tailLogBuffer ?? logBuffer;
|
|
3976
|
+
}
|
|
3977
|
+
tailLogBuffer = void 0;
|
|
3978
|
+
}
|
|
3979
|
+
let gapFloor = logId;
|
|
3788
3980
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3789
3981
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3790
3982
|
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
@@ -3794,6 +3986,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3794
3986
|
logBuffer.size = 0;
|
|
3795
3987
|
logBuffer.dataView = new DataView(logBuffer.buffer);
|
|
3796
3988
|
size = 0;
|
|
3989
|
+
if (positionedAt <= TRANSACTION_LOG_FILE_HEADER_SIZE || logId > 0 && this.getLogFileSize(logId) > 0) gapFloor = logId - 1;
|
|
3797
3990
|
}
|
|
3798
3991
|
}
|
|
3799
3992
|
dataView = logBuffer.dataView;
|
|
@@ -3803,7 +3996,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3803
3996
|
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3804
3997
|
else size = cachedSize;
|
|
3805
3998
|
}
|
|
3806
|
-
|
|
3999
|
+
const iterator = {
|
|
3807
4000
|
[Symbol.iterator]() {
|
|
3808
4001
|
return this;
|
|
3809
4002
|
},
|
|
@@ -3815,13 +4008,17 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3815
4008
|
if (latestLogId > logBuffer.logId) {
|
|
3816
4009
|
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3817
4010
|
if (position >= size) {
|
|
3818
|
-
const
|
|
4011
|
+
const fromLogId = logBuffer.logId;
|
|
4012
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
3819
4013
|
if (nextLogBuffer) {
|
|
4014
|
+
const unavailable = unavailableLogCount;
|
|
3820
4015
|
dataView = nextLogBuffer.dataView;
|
|
3821
4016
|
logBuffer = nextLogBuffer;
|
|
3822
4017
|
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3823
4018
|
else size = latestSize;
|
|
3824
4019
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4020
|
+
previousEndedTransaction = true;
|
|
4021
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
3825
4022
|
}
|
|
3826
4023
|
}
|
|
3827
4024
|
}
|
|
@@ -3841,30 +4038,55 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3841
4038
|
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
|
|
3842
4039
|
const broken = corruptFrame(`Corrupt transaction log: truncated entry header at position ${position.toString(16)} of log ${logBuffer.logId} (available=${limit - position})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
|
|
3843
4040
|
position = broken.nextPosition;
|
|
4041
|
+
previousEndedTransaction = true;
|
|
3844
4042
|
throw broken.error;
|
|
3845
4043
|
}
|
|
3846
4044
|
const length = dataView.getUint32(position + 8);
|
|
3847
4045
|
if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
|
|
3848
4046
|
const broken = corruptFrame(length === 0 ? `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: zero-length entry` : `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: declared length ${length} overruns the log (limit=${limit})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
|
|
3849
4047
|
position = broken.nextPosition;
|
|
4048
|
+
previousEndedTransaction = true;
|
|
3850
4049
|
throw broken.error;
|
|
3851
4050
|
}
|
|
3852
4051
|
position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
4052
|
+
const flags = logBuffer[position - 1];
|
|
4053
|
+
const aborted = (flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG) !== 0;
|
|
4054
|
+
if (previousEndedTransaction || timestamp !== previousTimestamp || aborted) skippingAborted = aborted;
|
|
4055
|
+
previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG) !== 0;
|
|
4056
|
+
previousTimestamp = timestamp;
|
|
3853
4057
|
let matchesRange;
|
|
3854
|
-
if (
|
|
4058
|
+
if (skippingAborted) matchesRange = false;
|
|
4059
|
+
else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
3855
4060
|
else if (exactStart) {
|
|
4061
|
+
matchesRange = false;
|
|
3856
4062
|
if (timestamp === start) {
|
|
3857
4063
|
matchesRange = !exclusiveStart;
|
|
3858
4064
|
foundExactStart = true;
|
|
3859
|
-
|
|
4065
|
+
stopLogId = 0;
|
|
4066
|
+
}
|
|
3860
4067
|
} else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
|
|
4068
|
+
if (!matchesRange && stopLogId !== 0 && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
|
|
4069
|
+
stopLogId = 0;
|
|
4070
|
+
position += length;
|
|
4071
|
+
if (skipToTail(transactionLog, logBuffer.logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
4072
|
+
if (tailLogBuffer) {
|
|
4073
|
+
logBuffer = tailLogBuffer;
|
|
4074
|
+
dataView = tailLogBuffer.dataView;
|
|
4075
|
+
}
|
|
4076
|
+
position = tailPosition;
|
|
4077
|
+
previousEndedTransaction = true;
|
|
4078
|
+
size = 0;
|
|
4079
|
+
}
|
|
4080
|
+
tailLogBuffer = void 0;
|
|
4081
|
+
return iterator.next();
|
|
4082
|
+
}
|
|
3861
4083
|
const entryStart = position;
|
|
3862
4084
|
position += length;
|
|
3863
4085
|
if (matchesRange) return {
|
|
3864
4086
|
done: false,
|
|
3865
4087
|
value: {
|
|
3866
4088
|
timestamp,
|
|
3867
|
-
endTxn:
|
|
4089
|
+
endTxn: previousEndedTransaction,
|
|
3868
4090
|
data: logBuffer.subarray(entryStart, position)
|
|
3869
4091
|
}
|
|
3870
4092
|
};
|
|
@@ -3875,11 +4097,13 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3875
4097
|
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
3876
4098
|
if (!readUncommitted) logBuffer.size = size;
|
|
3877
4099
|
if (position < size) continue;
|
|
3878
|
-
const
|
|
4100
|
+
const fromLogId = logBuffer.logId;
|
|
4101
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
3879
4102
|
if (!nextLogBuffer) return {
|
|
3880
4103
|
done: true,
|
|
3881
4104
|
value: void 0
|
|
3882
4105
|
};
|
|
4106
|
+
const unavailable = unavailableLogCount;
|
|
3883
4107
|
logBuffer = nextLogBuffer;
|
|
3884
4108
|
dataView = logBuffer.dataView;
|
|
3885
4109
|
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
@@ -3891,6 +4115,8 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3891
4115
|
} else size = cachedSize;
|
|
3892
4116
|
}
|
|
3893
4117
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4118
|
+
previousEndedTransaction = true;
|
|
4119
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
3894
4120
|
}
|
|
3895
4121
|
}
|
|
3896
4122
|
}
|
|
@@ -3900,7 +4126,29 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3900
4126
|
};
|
|
3901
4127
|
}
|
|
3902
4128
|
};
|
|
4129
|
+
return iterator;
|
|
3903
4130
|
} });
|
|
4131
|
+
function pastStop(logId, position, stopLogId, stopPosition) {
|
|
4132
|
+
return logId > stopLogId || logId === stopLogId && position >= stopPosition;
|
|
4133
|
+
}
|
|
4134
|
+
let tailLogBuffer;
|
|
4135
|
+
/**
|
|
4136
|
+
* Whether an exact-start lookup at `position` of `fromLogId` may take its one-shot jump to the tail.
|
|
4137
|
+
* next() calls it on a frame boundary, so a mapping error leaves a resumable cursor; from query()
|
|
4138
|
+
* the error escapes before any iterator exists. The removal
|
|
4139
|
+
* check follows the mapping and holds the store's lock, so a purge either declines the jump (the
|
|
4140
|
+
* scan then reports the gap) or follows it like a purge of segments already read.
|
|
4141
|
+
*/
|
|
4142
|
+
function skipToTail(transactionLog, fromLogId, position, tailLogId, tailPosition, segmentRemovals) {
|
|
4143
|
+
tailLogBuffer = void 0;
|
|
4144
|
+
if (tailLogId === fromLogId) return tailPosition > position;
|
|
4145
|
+
if (tailLogId < fromLogId) return false;
|
|
4146
|
+
const mapped = getLogMemoryMap(transactionLog, tailLogId);
|
|
4147
|
+
if (mapped === void 0 || transactionLog._segmentRemovals() !== segmentRemovals) return false;
|
|
4148
|
+
tailLogBuffer = mapped;
|
|
4149
|
+
return true;
|
|
4150
|
+
}
|
|
4151
|
+
let unavailableLogCount = 0;
|
|
3904
4152
|
/**
|
|
3905
4153
|
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3906
4154
|
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
@@ -3909,15 +4157,22 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3909
4157
|
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3910
4158
|
* and so lands *past* a survivor sitting between two holes.
|
|
3911
4159
|
*/
|
|
3912
|
-
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
4160
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor) {
|
|
4161
|
+
unavailableLogCount = 0;
|
|
3913
4162
|
let candidateLogId = fromLogId + 1;
|
|
3914
4163
|
while (candidateLogId <= latestLogId) {
|
|
3915
4164
|
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3916
4165
|
if (logBuffer) {
|
|
3917
4166
|
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3918
|
-
} else
|
|
3919
|
-
|
|
3920
|
-
|
|
4167
|
+
} else {
|
|
4168
|
+
const fileSize = transactionLog.getLogFileSize(candidateLogId);
|
|
4169
|
+
if (fileSize > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
|
|
4170
|
+
if (fileSize === 0 && candidateLogId > gapFloor) unavailableLogCount++;
|
|
4171
|
+
}
|
|
4172
|
+
const nextLogId = transactionLog._nextLogId(candidateLogId);
|
|
4173
|
+
if (nextLogId === 0) return;
|
|
4174
|
+
unavailableLogCount += Math.max(0, nextLogId - 1 - Math.max(candidateLogId, gapFloor));
|
|
4175
|
+
candidateLogId = nextLogId;
|
|
3921
4176
|
}
|
|
3922
4177
|
}
|
|
3923
4178
|
/**
|
|
@@ -3971,15 +4226,14 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3971
4226
|
};
|
|
3972
4227
|
}
|
|
3973
4228
|
|
|
3974
|
-
|
|
3975
|
-
//#region src/index.ts
|
|
4229
|
+
// src/index.ts
|
|
3976
4230
|
const versions = {
|
|
3977
4231
|
rocksdb: version,
|
|
3978
|
-
"rocksdb-js": "2.
|
|
4232
|
+
"rocksdb-js": "2.13.0"
|
|
3979
4233
|
};
|
|
3980
4234
|
|
|
3981
|
-
//#endregion
|
|
3982
4235
|
exports.BackgroundError = BackgroundError;
|
|
4236
|
+
exports.ConflictKind = ConflictKind;
|
|
3983
4237
|
exports.CorruptFrameError = CorruptFrameError;
|
|
3984
4238
|
exports.CountEstimator = CountEstimator;
|
|
3985
4239
|
exports.DBI = DBI;
|
|
@@ -3987,7 +4241,9 @@ exports.DBIterator = DBIterator;
|
|
|
3987
4241
|
exports.RocksDatabase = RocksDatabase;
|
|
3988
4242
|
exports.Store = Store;
|
|
3989
4243
|
exports.Transaction = Transaction;
|
|
4244
|
+
exports.TransactionAbortUnresolvedError = TransactionAbortUnresolvedError;
|
|
3990
4245
|
exports.TransactionLog = TransactionLog;
|
|
4246
|
+
exports.TransactionLogGapError = TransactionLogGapError;
|
|
3991
4247
|
exports.backups = backups;
|
|
3992
4248
|
exports.constants = constants;
|
|
3993
4249
|
exports.coolTransactionLogs = coolTransactionLogs;
|