@harperfast/rocksdb-js 2.12.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 +133 -11
- package/dist/index.cjs +311 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -39
- package/dist/index.d.mts +168 -39
- package/dist/index.mjs +309 -66
- package/dist/index.mjs.map +1 -1
- package/docs/performance/mutex-contention.md +4 -3
- package/docs/stats.md +4 -0
- package/docs/steady-clock-design.md +1 -1
- package/docs/transaction-log.md +5 -4
- package/package.json +16 -16
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). */
|
|
@@ -584,8 +599,7 @@ const backups = {
|
|
|
584
599
|
}
|
|
585
600
|
};
|
|
586
601
|
|
|
587
|
-
|
|
588
|
-
//#region src/count-estimator.ts
|
|
602
|
+
// src/count-estimator.ts
|
|
589
603
|
/**
|
|
590
604
|
* Below this many traversed entries the observed density is too noisy to
|
|
591
605
|
* calibrate with, so `estimate()` returns the raw statistical estimate.
|
|
@@ -728,8 +742,7 @@ var CountEstimator = class {
|
|
|
728
742
|
}
|
|
729
743
|
};
|
|
730
744
|
|
|
731
|
-
|
|
732
|
-
//#region src/util.ts
|
|
745
|
+
// src/util.ts
|
|
733
746
|
/**
|
|
734
747
|
* Parses a duration string into milliseconds.
|
|
735
748
|
*
|
|
@@ -800,8 +813,7 @@ function when(subject, callback, errback) {
|
|
|
800
813
|
}
|
|
801
814
|
}
|
|
802
815
|
|
|
803
|
-
|
|
804
|
-
//#region src/dbi.ts
|
|
816
|
+
// src/dbi.ts
|
|
805
817
|
/**
|
|
806
818
|
* The base class for all database operations. This base class is shared by
|
|
807
819
|
* `RocksDatabase` and `Transaction`.
|
|
@@ -1086,8 +1098,7 @@ var DBI = class DBI {
|
|
|
1086
1098
|
}
|
|
1087
1099
|
};
|
|
1088
1100
|
|
|
1089
|
-
|
|
1090
|
-
//#region src/tar.ts
|
|
1101
|
+
// src/tar.ts
|
|
1091
1102
|
/**
|
|
1092
1103
|
* A minimal, dependency-free streaming USTAR (POSIX tar) encoder.
|
|
1093
1104
|
*
|
|
@@ -1293,8 +1304,7 @@ var TarEncoder = class {
|
|
|
1293
1304
|
}
|
|
1294
1305
|
};
|
|
1295
1306
|
|
|
1296
|
-
|
|
1297
|
-
//#region src/backup-stream.ts
|
|
1307
|
+
// src/backup-stream.ts
|
|
1298
1308
|
/** Native event discriminator: a new file header (vs. a payload chunk). */
|
|
1299
1309
|
const EVENT_FILE = 0;
|
|
1300
1310
|
/**
|
|
@@ -1338,9 +1348,22 @@ async function backupToStream(db, stream, options) {
|
|
|
1338
1348
|
}
|
|
1339
1349
|
}
|
|
1340
1350
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
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
|
+
}
|
|
1344
1367
|
const DONE_RESULT = Object.freeze({
|
|
1345
1368
|
done: true,
|
|
1346
1369
|
value: void 0
|
|
@@ -1360,10 +1383,12 @@ var DBIterator = class {
|
|
|
1360
1383
|
#includeValues;
|
|
1361
1384
|
#limit;
|
|
1362
1385
|
#yielded = 0;
|
|
1363
|
-
|
|
1386
|
+
#transaction;
|
|
1387
|
+
constructor(iterator, store, includeValues, limit, transaction) {
|
|
1364
1388
|
this.iterator = iterator;
|
|
1365
1389
|
this.store = store;
|
|
1366
1390
|
this.#includeValues = includeValues;
|
|
1391
|
+
this.#transaction = transaction;
|
|
1367
1392
|
this.#limit = limit ?? void 0;
|
|
1368
1393
|
}
|
|
1369
1394
|
[Symbol.iterator]() {
|
|
@@ -1371,7 +1396,7 @@ var DBIterator = class {
|
|
|
1371
1396
|
}
|
|
1372
1397
|
next() {
|
|
1373
1398
|
if (this.#limit !== void 0 && this.#yielded >= this.#limit) {
|
|
1374
|
-
this.iterator.return
|
|
1399
|
+
this.#release(this.iterator.return());
|
|
1375
1400
|
return DONE_RESULT;
|
|
1376
1401
|
}
|
|
1377
1402
|
let result;
|
|
@@ -1379,11 +1404,15 @@ var DBIterator = class {
|
|
|
1379
1404
|
result = this.iterator.next();
|
|
1380
1405
|
} catch (err) {
|
|
1381
1406
|
try {
|
|
1382
|
-
this.iterator.return
|
|
1407
|
+
this.#release(this.iterator.return());
|
|
1383
1408
|
} catch {}
|
|
1384
1409
|
throw err;
|
|
1385
1410
|
}
|
|
1386
1411
|
if (result === ITERATOR_RESULT_DONE) return DONE_RESULT;
|
|
1412
|
+
if (result === ITERATOR_RESULT_RELEASED) {
|
|
1413
|
+
scheduleTrim(this.#transaction);
|
|
1414
|
+
return DONE_RESULT;
|
|
1415
|
+
}
|
|
1387
1416
|
this.#yielded++;
|
|
1388
1417
|
const includeValues = this.#includeValues;
|
|
1389
1418
|
const value = {};
|
|
@@ -1404,20 +1433,23 @@ var DBIterator = class {
|
|
|
1404
1433
|
};
|
|
1405
1434
|
}
|
|
1406
1435
|
return(value) {
|
|
1407
|
-
this.iterator.return
|
|
1436
|
+
this.#release(this.iterator.return());
|
|
1408
1437
|
return {
|
|
1409
1438
|
done: true,
|
|
1410
1439
|
value
|
|
1411
1440
|
};
|
|
1412
1441
|
}
|
|
1413
1442
|
throw(err) {
|
|
1414
|
-
this.iterator.throw
|
|
1443
|
+
this.#release(this.iterator.throw(err));
|
|
1415
1444
|
throw err;
|
|
1416
1445
|
}
|
|
1446
|
+
#release(pooled) {
|
|
1447
|
+
if (pooled) scheduleTrim(this.#transaction);
|
|
1448
|
+
this.#transaction = void 0;
|
|
1449
|
+
}
|
|
1417
1450
|
};
|
|
1418
1451
|
|
|
1419
|
-
|
|
1420
|
-
//#region src/encoding.ts
|
|
1452
|
+
// src/encoding.ts
|
|
1421
1453
|
/**
|
|
1422
1454
|
* Initializes the key encoder functions.
|
|
1423
1455
|
*
|
|
@@ -1484,8 +1516,7 @@ function createFixedBuffer(size) {
|
|
|
1484
1516
|
return buffer;
|
|
1485
1517
|
}
|
|
1486
1518
|
|
|
1487
|
-
|
|
1488
|
-
//#region src/store.ts
|
|
1519
|
+
// src/store.ts
|
|
1489
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;
|
|
1490
1521
|
const KEY_BUFFER_SIZE = 4096;
|
|
1491
1522
|
const KEY_BUFFER = createFixedBuffer(KEY_BUFFER_SIZE);
|
|
@@ -1786,6 +1817,10 @@ var Store = class {
|
|
|
1786
1817
|
*/
|
|
1787
1818
|
writeBufferSize;
|
|
1788
1819
|
/**
|
|
1820
|
+
* The memtable arena block size in bytes; `0` or unset is automatic.
|
|
1821
|
+
*/
|
|
1822
|
+
arenaBlockSize;
|
|
1823
|
+
/**
|
|
1789
1824
|
* The function used to encode keys using the shared `keyBuffer`.
|
|
1790
1825
|
*/
|
|
1791
1826
|
writeKey;
|
|
@@ -1838,6 +1873,7 @@ var Store = class {
|
|
|
1838
1873
|
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1839
1874
|
this.verificationTable = options?.verificationTable;
|
|
1840
1875
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1876
|
+
this.arenaBlockSize = options?.arenaBlockSize;
|
|
1841
1877
|
this.writeKey = writeKey;
|
|
1842
1878
|
this.#columnFamilyOptions = options ? { ...options } : void 0;
|
|
1843
1879
|
}
|
|
@@ -2088,7 +2124,9 @@ var Store = class {
|
|
|
2088
2124
|
if (context !== this.db) flags |= ITERATOR_CONTEXT_IS_TRANSACTION_FLAG;
|
|
2089
2125
|
if (txnId !== void 0) flags |= ITERATOR_HAS_TRANSACTION_ID_FLAG;
|
|
2090
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;
|
|
2091
|
-
|
|
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));
|
|
2092
2130
|
}
|
|
2093
2131
|
getSync(context, key, alwaysCreateNewBuffer = false, options) {
|
|
2094
2132
|
const keyParam = getKeyParam(this.encodeKey(key));
|
|
@@ -2202,7 +2240,8 @@ var Store = class {
|
|
|
2202
2240
|
transactionLogsPath: this.transactionLogsPath,
|
|
2203
2241
|
timestampFloorLog: this.timestampFloorLog,
|
|
2204
2242
|
verificationTable: this.verificationTable,
|
|
2205
|
-
writeBufferSize: this.writeBufferSize
|
|
2243
|
+
writeBufferSize: this.writeBufferSize,
|
|
2244
|
+
arenaBlockSize: this.arenaBlockSize
|
|
2206
2245
|
});
|
|
2207
2246
|
this.identityPath = this.db.identityPath;
|
|
2208
2247
|
return false;
|
|
@@ -2308,8 +2347,7 @@ function getKeyParam(keyBuffer) {
|
|
|
2308
2347
|
return keyBuffer.length;
|
|
2309
2348
|
}
|
|
2310
2349
|
|
|
2311
|
-
|
|
2312
|
-
//#region src/transaction.ts
|
|
2350
|
+
// src/transaction.ts
|
|
2313
2351
|
/**
|
|
2314
2352
|
* Sentinel value returned by `commit()` when `coordinatedRetry: true` and the
|
|
2315
2353
|
* transaction encountered an IsBusy conflict. The native layer parks on VT
|
|
@@ -2333,10 +2371,18 @@ var TransactionAlreadyAbortedError = class extends Error {
|
|
|
2333
2371
|
*/
|
|
2334
2372
|
var TransactionRetryableError = class extends Error {
|
|
2335
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;
|
|
2336
2381
|
txn;
|
|
2337
2382
|
constructor(error, txn) {
|
|
2338
2383
|
super(error.message);
|
|
2339
2384
|
this.hasLog = error.hasLog ?? false;
|
|
2385
|
+
this.refusedBeforeLog = error.refusedBeforeLog ?? false;
|
|
2340
2386
|
this.txn = txn;
|
|
2341
2387
|
}
|
|
2342
2388
|
};
|
|
@@ -2366,6 +2412,38 @@ var TransactionAbandonedError = class extends Error {
|
|
|
2366
2412
|
}
|
|
2367
2413
|
};
|
|
2368
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
|
+
/**
|
|
2369
2447
|
* Provides transaction level operations to a transaction callback.
|
|
2370
2448
|
*/
|
|
2371
2449
|
var Transaction = class extends DBI {
|
|
@@ -2380,9 +2458,10 @@ var Transaction = class extends DBI {
|
|
|
2380
2458
|
if (store.readOnly) {
|
|
2381
2459
|
super(store);
|
|
2382
2460
|
this.#txn = { id: 0 };
|
|
2383
|
-
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = () => {};
|
|
2461
|
+
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = this.setParkDeadline = () => {};
|
|
2384
2462
|
this.commit = async () => {};
|
|
2385
|
-
this.getTimestamp = () => 0;
|
|
2463
|
+
this.getTimestamp = this.lastConflictKind = () => 0;
|
|
2464
|
+
this.logWritten = () => false;
|
|
2386
2465
|
} else {
|
|
2387
2466
|
const txn = new NativeTransaction(store.db, options);
|
|
2388
2467
|
super(store, txn);
|
|
@@ -2408,6 +2487,7 @@ var Transaction = class extends DBI {
|
|
|
2408
2487
|
this.#txn.abort();
|
|
2409
2488
|
} catch (err) {
|
|
2410
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);
|
|
2411
2491
|
throw err;
|
|
2412
2492
|
}
|
|
2413
2493
|
}
|
|
@@ -2476,6 +2556,28 @@ var Transaction = class extends DBI {
|
|
|
2476
2556
|
return this.#txn.getTimestamp();
|
|
2477
2557
|
}
|
|
2478
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
|
+
/**
|
|
2479
2581
|
* Get the transaction id.
|
|
2480
2582
|
*/
|
|
2481
2583
|
get id() {
|
|
@@ -2496,8 +2598,8 @@ var Transaction = class extends DBI {
|
|
|
2496
2598
|
}
|
|
2497
2599
|
};
|
|
2498
2600
|
|
|
2499
|
-
|
|
2500
|
-
|
|
2601
|
+
// src/database.ts
|
|
2602
|
+
const backoffCell = new Int32Array(new SharedArrayBuffer(4));
|
|
2501
2603
|
const BACKGROUND_ERROR_SEVERITY_NAMES = [
|
|
2502
2604
|
"none",
|
|
2503
2605
|
"soft",
|
|
@@ -2907,6 +3009,19 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2907
3009
|
return this.store.db.flushSync(options);
|
|
2908
3010
|
}
|
|
2909
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
|
+
/**
|
|
2910
3025
|
* Returns the most recent {@link BackgroundError} observed on this database, or
|
|
2911
3026
|
* `null` when none has occurred. The `'error'` event is the push counterpart;
|
|
2912
3027
|
* this is the pull equivalent for on-demand checks (e.g. a health probe) and
|
|
@@ -3375,7 +3490,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3375
3490
|
async transaction(callback, options) {
|
|
3376
3491
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3377
3492
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3378
|
-
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);
|
|
3379
3497
|
let result;
|
|
3380
3498
|
this.notify("begin-transaction");
|
|
3381
3499
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -3389,7 +3507,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3389
3507
|
commitResult = await txn.commit();
|
|
3390
3508
|
} catch (commitErr) {
|
|
3391
3509
|
if (commitErr instanceof TransactionAlreadyAbortedError) return;
|
|
3392
|
-
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;
|
|
3393
3511
|
this.#abandonTransaction(txn, commitErr);
|
|
3394
3512
|
return;
|
|
3395
3513
|
}
|
|
@@ -3421,10 +3539,13 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3421
3539
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3422
3540
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3423
3541
|
const isRetryable = (err, attempt) => {
|
|
3424
|
-
|
|
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;
|
|
3425
3546
|
};
|
|
3547
|
+
const txn = new Transaction(this.store, options);
|
|
3426
3548
|
const runAttempt = (attempt) => {
|
|
3427
|
-
const txn = new Transaction(this.store, options);
|
|
3428
3549
|
let result;
|
|
3429
3550
|
try {
|
|
3430
3551
|
result = callback(txn, attempt);
|
|
@@ -3440,7 +3561,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3440
3561
|
if (isRetryable(commitErr, attempt)) return runAttempt(attempt + 1);
|
|
3441
3562
|
this.#abandonTransaction(txn, commitErr);
|
|
3442
3563
|
}
|
|
3443
|
-
});
|
|
3564
|
+
}, (callbackErr) => this.#abortTransaction(txn, callbackErr));
|
|
3444
3565
|
try {
|
|
3445
3566
|
txn.commitSync();
|
|
3446
3567
|
return result;
|
|
@@ -3465,7 +3586,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3465
3586
|
try {
|
|
3466
3587
|
txn.abort();
|
|
3467
3588
|
} catch (abortErr) {
|
|
3468
|
-
if (abortErr instanceof TransactionAbandonedError) throw abortErr;
|
|
3589
|
+
if (abortErr instanceof TransactionAbandonedError || abortErr instanceof TransactionAbortUnresolvedError) throw abortErr;
|
|
3469
3590
|
}
|
|
3470
3591
|
throw commitErr;
|
|
3471
3592
|
}
|
|
@@ -3580,10 +3701,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3580
3701
|
}
|
|
3581
3702
|
};
|
|
3582
3703
|
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
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;
|
|
3586
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;
|
|
3587
3708
|
/**
|
|
3588
3709
|
* Loads an entire transaction log file into memory.
|
|
3589
3710
|
* @param path - The path to the transaction log file.
|
|
@@ -3621,6 +3742,8 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3621
3742
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) anomalies.push(`Header timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3622
3743
|
const entries = [];
|
|
3623
3744
|
let entryAnomalyCount = 0;
|
|
3745
|
+
let previousEndedTransaction = true;
|
|
3746
|
+
let previousTimestamp = NaN;
|
|
3624
3747
|
while (fileOffset < size) {
|
|
3625
3748
|
const timestamp = read(8).readDoubleBE(0);
|
|
3626
3749
|
if (timestamp === 0) {
|
|
@@ -3637,7 +3760,11 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3637
3760
|
const data = read(length);
|
|
3638
3761
|
const entryAnomalies = [];
|
|
3639
3762
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) entryAnomalies.push(`timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3640
|
-
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;
|
|
3641
3768
|
const entry = {
|
|
3642
3769
|
timestamp,
|
|
3643
3770
|
length,
|
|
@@ -3666,11 +3793,11 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3666
3793
|
}
|
|
3667
3794
|
}
|
|
3668
3795
|
|
|
3669
|
-
|
|
3670
|
-
//#region src/transaction-log-reader.ts
|
|
3796
|
+
// src/transaction-log-reader.ts
|
|
3671
3797
|
const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
|
|
3672
3798
|
const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
|
|
3673
|
-
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;
|
|
3674
3801
|
/**
|
|
3675
3802
|
* Number of consecutive well-formed frames that, on their own, signal that real log data has
|
|
3676
3803
|
* resumed after a framing break. Kept in step with `RESYNC_MIN_FRAMES` in
|
|
@@ -3706,6 +3833,32 @@ var CorruptFrameError = class extends RangeError {
|
|
|
3706
3833
|
}
|
|
3707
3834
|
};
|
|
3708
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
|
+
/**
|
|
3709
3862
|
* Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
|
|
3710
3863
|
* the written extent: a single entry can legitimately exceed the rotation threshold (the first
|
|
3711
3864
|
* entry written to a fresh file is always written in full). A zero timestamp is the
|
|
@@ -3781,6 +3934,14 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3781
3934
|
let dataView;
|
|
3782
3935
|
let logBuffer = this._currentLogBuffer?.deref();
|
|
3783
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;
|
|
3784
3945
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3785
3946
|
position = size;
|
|
3786
3947
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
@@ -3790,11 +3951,32 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3790
3951
|
FLOAT_TO_UINT32[0] = this._getLastFlushed();
|
|
3791
3952
|
if (FLOAT_TO_UINT32[0] === 0) FLOAT_TO_UINT32[0] = this._findPosition(0);
|
|
3792
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;
|
|
3793
3964
|
} else FLOAT_TO_UINT32[0] = this._findPosition(start);
|
|
3794
3965
|
logId = UINT32_FROM_FLOAT[1];
|
|
3795
3966
|
position = UINT32_FROM_FLOAT[0];
|
|
3796
3967
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3797
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;
|
|
3798
3980
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3799
3981
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3800
3982
|
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
@@ -3804,6 +3986,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3804
3986
|
logBuffer.size = 0;
|
|
3805
3987
|
logBuffer.dataView = new DataView(logBuffer.buffer);
|
|
3806
3988
|
size = 0;
|
|
3989
|
+
if (positionedAt <= TRANSACTION_LOG_FILE_HEADER_SIZE || logId > 0 && this.getLogFileSize(logId) > 0) gapFloor = logId - 1;
|
|
3807
3990
|
}
|
|
3808
3991
|
}
|
|
3809
3992
|
dataView = logBuffer.dataView;
|
|
@@ -3813,7 +3996,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3813
3996
|
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3814
3997
|
else size = cachedSize;
|
|
3815
3998
|
}
|
|
3816
|
-
|
|
3999
|
+
const iterator = {
|
|
3817
4000
|
[Symbol.iterator]() {
|
|
3818
4001
|
return this;
|
|
3819
4002
|
},
|
|
@@ -3825,13 +4008,17 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3825
4008
|
if (latestLogId > logBuffer.logId) {
|
|
3826
4009
|
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3827
4010
|
if (position >= size) {
|
|
3828
|
-
const
|
|
4011
|
+
const fromLogId = logBuffer.logId;
|
|
4012
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
3829
4013
|
if (nextLogBuffer) {
|
|
4014
|
+
const unavailable = unavailableLogCount;
|
|
3830
4015
|
dataView = nextLogBuffer.dataView;
|
|
3831
4016
|
logBuffer = nextLogBuffer;
|
|
3832
4017
|
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3833
4018
|
else size = latestSize;
|
|
3834
4019
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4020
|
+
previousEndedTransaction = true;
|
|
4021
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
3835
4022
|
}
|
|
3836
4023
|
}
|
|
3837
4024
|
}
|
|
@@ -3851,30 +4038,55 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3851
4038
|
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
|
|
3852
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);
|
|
3853
4040
|
position = broken.nextPosition;
|
|
4041
|
+
previousEndedTransaction = true;
|
|
3854
4042
|
throw broken.error;
|
|
3855
4043
|
}
|
|
3856
4044
|
const length = dataView.getUint32(position + 8);
|
|
3857
4045
|
if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
|
|
3858
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);
|
|
3859
4047
|
position = broken.nextPosition;
|
|
4048
|
+
previousEndedTransaction = true;
|
|
3860
4049
|
throw broken.error;
|
|
3861
4050
|
}
|
|
3862
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;
|
|
3863
4057
|
let matchesRange;
|
|
3864
|
-
if (
|
|
4058
|
+
if (skippingAborted) matchesRange = false;
|
|
4059
|
+
else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
3865
4060
|
else if (exactStart) {
|
|
4061
|
+
matchesRange = false;
|
|
3866
4062
|
if (timestamp === start) {
|
|
3867
4063
|
matchesRange = !exclusiveStart;
|
|
3868
4064
|
foundExactStart = true;
|
|
3869
|
-
|
|
4065
|
+
stopLogId = 0;
|
|
4066
|
+
}
|
|
3870
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
|
+
}
|
|
3871
4083
|
const entryStart = position;
|
|
3872
4084
|
position += length;
|
|
3873
4085
|
if (matchesRange) return {
|
|
3874
4086
|
done: false,
|
|
3875
4087
|
value: {
|
|
3876
4088
|
timestamp,
|
|
3877
|
-
endTxn:
|
|
4089
|
+
endTxn: previousEndedTransaction,
|
|
3878
4090
|
data: logBuffer.subarray(entryStart, position)
|
|
3879
4091
|
}
|
|
3880
4092
|
};
|
|
@@ -3885,11 +4097,13 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3885
4097
|
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
3886
4098
|
if (!readUncommitted) logBuffer.size = size;
|
|
3887
4099
|
if (position < size) continue;
|
|
3888
|
-
const
|
|
4100
|
+
const fromLogId = logBuffer.logId;
|
|
4101
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
3889
4102
|
if (!nextLogBuffer) return {
|
|
3890
4103
|
done: true,
|
|
3891
4104
|
value: void 0
|
|
3892
4105
|
};
|
|
4106
|
+
const unavailable = unavailableLogCount;
|
|
3893
4107
|
logBuffer = nextLogBuffer;
|
|
3894
4108
|
dataView = logBuffer.dataView;
|
|
3895
4109
|
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
@@ -3901,6 +4115,8 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3901
4115
|
} else size = cachedSize;
|
|
3902
4116
|
}
|
|
3903
4117
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4118
|
+
previousEndedTransaction = true;
|
|
4119
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
3904
4120
|
}
|
|
3905
4121
|
}
|
|
3906
4122
|
}
|
|
@@ -3910,7 +4126,29 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3910
4126
|
};
|
|
3911
4127
|
}
|
|
3912
4128
|
};
|
|
4129
|
+
return iterator;
|
|
3913
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;
|
|
3914
4152
|
/**
|
|
3915
4153
|
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3916
4154
|
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
@@ -3919,15 +4157,22 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3919
4157
|
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3920
4158
|
* and so lands *past* a survivor sitting between two holes.
|
|
3921
4159
|
*/
|
|
3922
|
-
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
4160
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor) {
|
|
4161
|
+
unavailableLogCount = 0;
|
|
3923
4162
|
let candidateLogId = fromLogId + 1;
|
|
3924
4163
|
while (candidateLogId <= latestLogId) {
|
|
3925
4164
|
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3926
4165
|
if (logBuffer) {
|
|
3927
4166
|
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3928
|
-
} else
|
|
3929
|
-
|
|
3930
|
-
|
|
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;
|
|
3931
4176
|
}
|
|
3932
4177
|
}
|
|
3933
4178
|
/**
|
|
@@ -3981,15 +4226,14 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3981
4226
|
};
|
|
3982
4227
|
}
|
|
3983
4228
|
|
|
3984
|
-
|
|
3985
|
-
//#region src/index.ts
|
|
4229
|
+
// src/index.ts
|
|
3986
4230
|
const versions = {
|
|
3987
4231
|
rocksdb: version,
|
|
3988
|
-
"rocksdb-js": "2.
|
|
4232
|
+
"rocksdb-js": "2.13.0"
|
|
3989
4233
|
};
|
|
3990
4234
|
|
|
3991
|
-
//#endregion
|
|
3992
4235
|
exports.BackgroundError = BackgroundError;
|
|
4236
|
+
exports.ConflictKind = ConflictKind;
|
|
3993
4237
|
exports.CorruptFrameError = CorruptFrameError;
|
|
3994
4238
|
exports.CountEstimator = CountEstimator;
|
|
3995
4239
|
exports.DBI = DBI;
|
|
@@ -3997,7 +4241,9 @@ exports.DBIterator = DBIterator;
|
|
|
3997
4241
|
exports.RocksDatabase = RocksDatabase;
|
|
3998
4242
|
exports.Store = Store;
|
|
3999
4243
|
exports.Transaction = Transaction;
|
|
4244
|
+
exports.TransactionAbortUnresolvedError = TransactionAbortUnresolvedError;
|
|
4000
4245
|
exports.TransactionLog = TransactionLog;
|
|
4246
|
+
exports.TransactionLogGapError = TransactionLogGapError;
|
|
4001
4247
|
exports.backups = backups;
|
|
4002
4248
|
exports.constants = constants;
|
|
4003
4249
|
exports.coolTransactionLogs = coolTransactionLogs;
|