@harperfast/rocksdb-js 2.12.0 → 2.14.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 +375 -49
- package/dist/index.cjs +587 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +276 -58
- package/dist/index.d.mts +276 -58
- package/dist/index.mjs +583 -159
- package/dist/index.mjs.map +1 -1
- package/docs/performance/mutex-contention.md +4 -3
- package/docs/performance/transaction-state-2026-11-09.csv +28 -0
- package/docs/performance/transaction-state.md +67 -0
- package/docs/stats.md +4 -0
- package/docs/steady-clock-design.md +1 -1
- package/docs/transaction-log.md +39 -7
- package/package.json +19 -19
package/dist/index.mjs
CHANGED
|
@@ -3,16 +3,16 @@ import { execSync } from "node:child_process";
|
|
|
3
3
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { isMainThread } from "node:worker_threads";
|
|
6
7
|
import { access, cp, mkdir, readdir, rm } from "node:fs/promises";
|
|
7
8
|
import * as orderedBinary from "ordered-binary";
|
|
8
9
|
import { ExtendedIterable } from "@harperfast/extended-iterable";
|
|
9
10
|
import { Encoder } from "msgpackr";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
// \0rolldown/runtime.js
|
|
12
13
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
//#region src/load-binding.ts
|
|
15
|
+
// src/load-binding.ts
|
|
16
16
|
const nativeExtRE = /\.node$/;
|
|
17
17
|
const req = createRequire(import.meta.url);
|
|
18
18
|
/**
|
|
@@ -55,6 +55,18 @@ function locateBinding() {
|
|
|
55
55
|
throw new Error("Unable to locate rocksdb-js native binding");
|
|
56
56
|
}
|
|
57
57
|
const binding = req(locateBinding());
|
|
58
|
+
if (isMainThread) {
|
|
59
|
+
let exitShutdownRan = false;
|
|
60
|
+
const shutdownOnExit = () => {
|
|
61
|
+
if (exitShutdownRan) return;
|
|
62
|
+
exitShutdownRan = true;
|
|
63
|
+
if (!binding.shutdownOnExit() && !process.exitCode) process.exitCode = 1;
|
|
64
|
+
};
|
|
65
|
+
process.on("exit", shutdownOnExit);
|
|
66
|
+
process.on("uncaughtExceptionMonitor", () => {
|
|
67
|
+
if (process._exiting) shutdownOnExit();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
58
70
|
/**
|
|
59
71
|
* The native `BackgroundError` constructor (a real `Error` subclass). Exported
|
|
60
72
|
* as both a value — for `err instanceof BackgroundError` — and, via declaration
|
|
@@ -117,7 +129,7 @@ const getRegistryStatus = binding.registryStatus;
|
|
|
117
129
|
*/
|
|
118
130
|
const registryStatus = getRegistryStatus;
|
|
119
131
|
/**
|
|
120
|
-
* Flushes every open database and
|
|
132
|
+
* Flushes every open database and cancels its outstanding compactions, then
|
|
121
133
|
* releases the registry.
|
|
122
134
|
*
|
|
123
135
|
* **Throws** the first failure it encounters: a close whose native teardown
|
|
@@ -127,19 +139,10 @@ const registryStatus = getRegistryStatus;
|
|
|
127
139
|
* `database:closeFailed`; a timeout has no event, so the throw is its only
|
|
128
140
|
* signal.
|
|
129
141
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* ```typescript
|
|
135
|
-
* process.on('exit', () => {
|
|
136
|
-
* try {
|
|
137
|
-
* shutdown();
|
|
138
|
-
* } catch (error) {
|
|
139
|
-
* console.error('rocksdb-js shutdown failed', error);
|
|
140
|
-
* }
|
|
141
|
-
* });
|
|
142
|
-
* ```
|
|
142
|
+
* You do not need to call it at exit: when rocksdb-js loads on the main
|
|
143
|
+
* thread it registers a `process.on('exit')` listener that does the same and
|
|
144
|
+
* reports failures to stderr and the exit code instead of throwing (see the
|
|
145
|
+
* `shutdownOnExit` config option). Calling it yourself as well is safe.
|
|
143
146
|
*/
|
|
144
147
|
const shutdown = binding.shutdown;
|
|
145
148
|
const currentThreadId = binding.currentThreadId;
|
|
@@ -147,7 +150,7 @@ const currentThreadId = binding.currentThreadId;
|
|
|
147
150
|
* Reads the process-wide steady clock: `std::chrono::steady_clock` as
|
|
148
151
|
* fractional milliseconds from an unspecified origin that is fixed for the life
|
|
149
152
|
* of the process. Every sample in the process — main thread and every
|
|
150
|
-
* `worker_threads` worker, whenever started or restarted, on Node
|
|
153
|
+
* `worker_threads` worker, whenever started or restarted, on Node and Bun
|
|
151
154
|
* — is in one domain, and the difference between two samples is real elapsed
|
|
152
155
|
* time unaffected by wall-clock steps. Non-decreasing but not unique (two
|
|
153
156
|
* samples may be equal; treat `==` as "not later"). Windows QPC samples from
|
|
@@ -159,6 +162,30 @@ const currentThreadId = binding.currentThreadId;
|
|
|
159
162
|
*/
|
|
160
163
|
const steadyClockNow = binding.steadyClockNow;
|
|
161
164
|
/**
|
|
165
|
+
* Raises the process-wide monotonic timestamp clock — the one behind
|
|
166
|
+
* `db.getMonotonicTimestamp()`, every transaction's initial timestamp and so
|
|
167
|
+
* every transaction-log batch key — to at least `timestamp` (Unix epoch
|
|
168
|
+
* milliseconds). Every timestamp the process issues after the call is strictly
|
|
169
|
+
* greater than `timestamp`, across every database and `worker_threads` worker.
|
|
170
|
+
* Timestamps already issued and timestamps adopted with `txn.setTimestamp()`
|
|
171
|
+
* are unaffected. No open database is needed.
|
|
172
|
+
*
|
|
173
|
+
* Use it to carry a floor the caller persisted across a restart, which a
|
|
174
|
+
* backward wall-clock step would otherwise let the new process reissue. The
|
|
175
|
+
* floor is in memory only and is not a fence: restore it before the process
|
|
176
|
+
* starts any transaction it originates, and pass only floors this node
|
|
177
|
+
* persisted itself, since the clock stays that far ahead until wall time
|
|
178
|
+
* catches up.
|
|
179
|
+
*
|
|
180
|
+
* @returns `true` if the clock was raised: `timestamp` was ahead of both the
|
|
181
|
+
* wall clock and every timestamp already issued. `false` if the clock was
|
|
182
|
+
* already at or above it.
|
|
183
|
+
* @throws {TypeError} If `timestamp` is not a number.
|
|
184
|
+
* @throws {RangeError} If `timestamp` is not finite, is not greater than 0, is
|
|
185
|
+
* not less than 8.64e15, or is more than ten years ahead of the wall clock.
|
|
186
|
+
*/
|
|
187
|
+
const raiseTimestampFloor = binding.raiseTimestampFloor;
|
|
188
|
+
/**
|
|
162
189
|
* Advises the kernel that the file-backed pages of every mapped transaction log
|
|
163
190
|
* are cold (Linux MADV_COLD), so they are reclaimed first under memory pressure
|
|
164
191
|
* without being freed — useful during replication catch-up, where a full read of
|
|
@@ -185,12 +212,30 @@ const transactionLogMapCount = binding.transactionLogMapCount;
|
|
|
185
212
|
*/
|
|
186
213
|
const lockWakeCallbackCount = binding.lockWakeCallbackCount;
|
|
187
214
|
/**
|
|
215
|
+
* The calling thread's counters for the per-transaction iterator pool: pooled
|
|
216
|
+
* iterators created, and idle ones reused by a later range. Internal — used by
|
|
217
|
+
* tests to prove a range was served by a reused iterator.
|
|
218
|
+
*/
|
|
219
|
+
const iteratorPoolStats = binding.iteratorPoolStats;
|
|
220
|
+
/**
|
|
221
|
+
* Whether transaction `getRange()` reuses pooled native iterators (off with
|
|
222
|
+
* `ROCKSDB_JS_ITERATOR_POOL=0`). Internal.
|
|
223
|
+
*/
|
|
224
|
+
const iteratorPoolEnabled = binding.iteratorPoolEnabled;
|
|
225
|
+
/**
|
|
188
226
|
* Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
|
|
189
227
|
* no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
|
|
190
228
|
* disarm. Used by the ERR_TRY_AGAIN retry regression test.
|
|
191
229
|
*/
|
|
192
230
|
const forceTryAgainForTesting = binding.forceTryAgainForTesting;
|
|
193
231
|
/**
|
|
232
|
+
* Test-only: turn the RocksDB conflict check before the transaction-log write off (`true`) or back
|
|
233
|
+
* on, so a test can make a logged batch conflict at commit from a stale snapshot.
|
|
234
|
+
*/
|
|
235
|
+
const disablePrelogConflictCheckForTesting = binding.disablePrelogConflictCheckForTesting;
|
|
236
|
+
/** Test-only: run the pre-log conflict check on retries alone (`true`, the default) or on every attempt. */
|
|
237
|
+
const setPrelogConflictCheckRetriesOnlyForTesting = binding.setPrelogConflictCheckRetriesOnlyForTesting;
|
|
238
|
+
/**
|
|
194
239
|
* Test-only seam: makes the physical column-family drop report failure.
|
|
195
240
|
* `0` = inert, `1` = fail without dropping (the retry performs the drop),
|
|
196
241
|
* `2` = drop for real and then report failure (the retry resolves as
|
|
@@ -233,8 +278,7 @@ const nativeValidateTransactionLog = binding.validateTransactionLog;
|
|
|
233
278
|
const stats = binding.stats;
|
|
234
279
|
const version = binding.version;
|
|
235
280
|
|
|
236
|
-
|
|
237
|
-
//#region src/validate-transaction-log.ts
|
|
281
|
+
// src/validate-transaction-log.ts
|
|
238
282
|
/**
|
|
239
283
|
* Validates a transaction log store directory: every `<sequence>.txnlog` file
|
|
240
284
|
* is checked for a valid header (token, version) and intact v1 entry framing
|
|
@@ -274,8 +318,7 @@ function validateTransactionLogStore(path, options) {
|
|
|
274
318
|
return new Promise((resolve, reject) => nativeValidateTransactionLog(resolve, reject, path, options?.strict ?? false));
|
|
275
319
|
}
|
|
276
320
|
|
|
277
|
-
|
|
278
|
-
//#region src/backup.ts
|
|
321
|
+
// src/backup.ts
|
|
279
322
|
/** Subdirectory (under a backup directory) holding per-backup transaction log snapshots. */
|
|
280
323
|
const TRANSACTION_LOGS_DIRNAME = "transaction_logs";
|
|
281
324
|
/** Non-blocking existence check (`fs.existsSync` would block the event loop). */
|
|
@@ -559,8 +602,7 @@ const backups = {
|
|
|
559
602
|
}
|
|
560
603
|
};
|
|
561
604
|
|
|
562
|
-
|
|
563
|
-
//#region src/count-estimator.ts
|
|
605
|
+
// src/count-estimator.ts
|
|
564
606
|
/**
|
|
565
607
|
* Below this many traversed entries the observed density is too noisy to
|
|
566
608
|
* calibrate with, so `estimate()` returns the raw statistical estimate.
|
|
@@ -703,8 +745,7 @@ var CountEstimator = class {
|
|
|
703
745
|
}
|
|
704
746
|
};
|
|
705
747
|
|
|
706
|
-
|
|
707
|
-
//#region src/util.ts
|
|
748
|
+
// src/util.ts
|
|
708
749
|
/**
|
|
709
750
|
* Parses a duration string into milliseconds.
|
|
710
751
|
*
|
|
@@ -775,8 +816,7 @@ function when(subject, callback, errback) {
|
|
|
775
816
|
}
|
|
776
817
|
}
|
|
777
818
|
|
|
778
|
-
|
|
779
|
-
//#region src/dbi.ts
|
|
819
|
+
// src/dbi.ts
|
|
780
820
|
/**
|
|
781
821
|
* The base class for all database operations. This base class is shared by
|
|
782
822
|
* `RocksDatabase` and `Transaction`.
|
|
@@ -1061,8 +1101,7 @@ var DBI = class DBI {
|
|
|
1061
1101
|
}
|
|
1062
1102
|
};
|
|
1063
1103
|
|
|
1064
|
-
|
|
1065
|
-
//#region src/tar.ts
|
|
1104
|
+
// src/tar.ts
|
|
1066
1105
|
/**
|
|
1067
1106
|
* A minimal, dependency-free streaming USTAR (POSIX tar) encoder.
|
|
1068
1107
|
*
|
|
@@ -1268,8 +1307,7 @@ var TarEncoder = class {
|
|
|
1268
1307
|
}
|
|
1269
1308
|
};
|
|
1270
1309
|
|
|
1271
|
-
|
|
1272
|
-
//#region src/backup-stream.ts
|
|
1310
|
+
// src/backup-stream.ts
|
|
1273
1311
|
/** Native event discriminator: a new file header (vs. a payload chunk). */
|
|
1274
1312
|
const EVENT_FILE = 0;
|
|
1275
1313
|
/**
|
|
@@ -1313,9 +1351,22 @@ async function backupToStream(db, stream, options) {
|
|
|
1313
1351
|
}
|
|
1314
1352
|
}
|
|
1315
1353
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const
|
|
1354
|
+
// src/dbi-iterator.ts
|
|
1355
|
+
const { ITERATOR_RESULT_DONE, ITERATOR_RESULT_FAST, ITERATOR_RESULT_RELEASED } = constants;
|
|
1356
|
+
const transactionsToTrim = /* @__PURE__ */ new Set();
|
|
1357
|
+
function trimIdleIterators() {
|
|
1358
|
+
try {
|
|
1359
|
+
for (const transaction of transactionsToTrim) transaction.trimIterators();
|
|
1360
|
+
} finally {
|
|
1361
|
+
transactionsToTrim.clear();
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
function scheduleTrim(transaction) {
|
|
1365
|
+
if (transaction !== void 0) {
|
|
1366
|
+
if (transactionsToTrim.size === 0) setImmediate(trimIdleIterators);
|
|
1367
|
+
transactionsToTrim.add(transaction);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1319
1370
|
const DONE_RESULT = Object.freeze({
|
|
1320
1371
|
done: true,
|
|
1321
1372
|
value: void 0
|
|
@@ -1335,10 +1386,12 @@ var DBIterator = class {
|
|
|
1335
1386
|
#includeValues;
|
|
1336
1387
|
#limit;
|
|
1337
1388
|
#yielded = 0;
|
|
1338
|
-
|
|
1389
|
+
#transaction;
|
|
1390
|
+
constructor(iterator, store, includeValues, limit, transaction) {
|
|
1339
1391
|
this.iterator = iterator;
|
|
1340
1392
|
this.store = store;
|
|
1341
1393
|
this.#includeValues = includeValues;
|
|
1394
|
+
this.#transaction = transaction;
|
|
1342
1395
|
this.#limit = limit ?? void 0;
|
|
1343
1396
|
}
|
|
1344
1397
|
[Symbol.iterator]() {
|
|
@@ -1346,7 +1399,7 @@ var DBIterator = class {
|
|
|
1346
1399
|
}
|
|
1347
1400
|
next() {
|
|
1348
1401
|
if (this.#limit !== void 0 && this.#yielded >= this.#limit) {
|
|
1349
|
-
this.iterator.return
|
|
1402
|
+
this.#release(this.iterator.return());
|
|
1350
1403
|
return DONE_RESULT;
|
|
1351
1404
|
}
|
|
1352
1405
|
let result;
|
|
@@ -1354,11 +1407,15 @@ var DBIterator = class {
|
|
|
1354
1407
|
result = this.iterator.next();
|
|
1355
1408
|
} catch (err) {
|
|
1356
1409
|
try {
|
|
1357
|
-
this.iterator.return
|
|
1410
|
+
this.#release(this.iterator.return());
|
|
1358
1411
|
} catch {}
|
|
1359
1412
|
throw err;
|
|
1360
1413
|
}
|
|
1361
1414
|
if (result === ITERATOR_RESULT_DONE) return DONE_RESULT;
|
|
1415
|
+
if (result === ITERATOR_RESULT_RELEASED) {
|
|
1416
|
+
scheduleTrim(this.#transaction);
|
|
1417
|
+
return DONE_RESULT;
|
|
1418
|
+
}
|
|
1362
1419
|
this.#yielded++;
|
|
1363
1420
|
const includeValues = this.#includeValues;
|
|
1364
1421
|
const value = {};
|
|
@@ -1379,20 +1436,23 @@ var DBIterator = class {
|
|
|
1379
1436
|
};
|
|
1380
1437
|
}
|
|
1381
1438
|
return(value) {
|
|
1382
|
-
this.iterator.return
|
|
1439
|
+
this.#release(this.iterator.return());
|
|
1383
1440
|
return {
|
|
1384
1441
|
done: true,
|
|
1385
1442
|
value
|
|
1386
1443
|
};
|
|
1387
1444
|
}
|
|
1388
1445
|
throw(err) {
|
|
1389
|
-
this.iterator.throw
|
|
1446
|
+
this.#release(this.iterator.throw(err));
|
|
1390
1447
|
throw err;
|
|
1391
1448
|
}
|
|
1449
|
+
#release(pooled) {
|
|
1450
|
+
if (pooled) scheduleTrim(this.#transaction);
|
|
1451
|
+
this.#transaction = void 0;
|
|
1452
|
+
}
|
|
1392
1453
|
};
|
|
1393
1454
|
|
|
1394
|
-
|
|
1395
|
-
//#region src/encoding.ts
|
|
1455
|
+
// src/encoding.ts
|
|
1396
1456
|
/**
|
|
1397
1457
|
* Initializes the key encoder functions.
|
|
1398
1458
|
*
|
|
@@ -1459,8 +1519,7 @@ function createFixedBuffer(size) {
|
|
|
1459
1519
|
return buffer;
|
|
1460
1520
|
}
|
|
1461
1521
|
|
|
1462
|
-
|
|
1463
|
-
//#region src/store.ts
|
|
1522
|
+
// src/store.ts
|
|
1464
1523
|
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;
|
|
1465
1524
|
const KEY_BUFFER_SIZE = 4096;
|
|
1466
1525
|
const KEY_BUFFER = createFixedBuffer(KEY_BUFFER_SIZE);
|
|
@@ -1761,6 +1820,14 @@ var Store = class {
|
|
|
1761
1820
|
*/
|
|
1762
1821
|
writeBufferSize;
|
|
1763
1822
|
/**
|
|
1823
|
+
* The memtable arena block size in bytes; `0` or unset is automatic.
|
|
1824
|
+
*/
|
|
1825
|
+
arenaBlockSize;
|
|
1826
|
+
/**
|
|
1827
|
+
* Bits per key of the whole-key filter in each SST; `0` disables filters.
|
|
1828
|
+
*/
|
|
1829
|
+
bloomFilterBitsPerKey;
|
|
1830
|
+
/**
|
|
1764
1831
|
* The function used to encode keys using the shared `keyBuffer`.
|
|
1765
1832
|
*/
|
|
1766
1833
|
writeKey;
|
|
@@ -1813,6 +1880,8 @@ var Store = class {
|
|
|
1813
1880
|
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1814
1881
|
this.verificationTable = options?.verificationTable;
|
|
1815
1882
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1883
|
+
this.arenaBlockSize = options?.arenaBlockSize;
|
|
1884
|
+
this.bloomFilterBitsPerKey = options?.bloomFilterBitsPerKey;
|
|
1816
1885
|
this.writeKey = writeKey;
|
|
1817
1886
|
this.#columnFamilyOptions = options ? { ...options } : void 0;
|
|
1818
1887
|
}
|
|
@@ -1972,7 +2041,7 @@ var Store = class {
|
|
|
1972
2041
|
const result = context.getSync(keyParam, flags | ONLY_IF_IN_MEMORY_CACHE_FLAG, txnId, expectedVersion);
|
|
1973
2042
|
if (typeof result === "number") {
|
|
1974
2043
|
if (result === NOT_IN_MEMORY_CACHE_FLAG) return new Promise((resolve, reject) => {
|
|
1975
|
-
context.get(keyParam, resolve, reject, txnId, expectedVersion);
|
|
2044
|
+
context.get(keyParam, resolve, reject, txnId, expectedVersion, flags);
|
|
1976
2045
|
});
|
|
1977
2046
|
if (result === FRESH_VERSION_FLAG) return result;
|
|
1978
2047
|
VALUE_BUFFER.end = result;
|
|
@@ -2063,7 +2132,9 @@ var Store = class {
|
|
|
2063
2132
|
if (context !== this.db) flags |= ITERATOR_CONTEXT_IS_TRANSACTION_FLAG;
|
|
2064
2133
|
if (txnId !== void 0) flags |= ITERATOR_HAS_TRANSACTION_ID_FLAG;
|
|
2065
2134
|
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;
|
|
2066
|
-
|
|
2135
|
+
const nativeIterator = new NativeIterator(context, flags, startKeyEnd, endKeyStart, endKeyEnd, advancedOptions, txnId);
|
|
2136
|
+
const transaction = context !== this.db ? context : options?.transaction?._context;
|
|
2137
|
+
return new ExtendedIterable(new DBIterator(nativeIterator, this, includeValues, options?.limit, transaction));
|
|
2067
2138
|
}
|
|
2068
2139
|
getSync(context, key, alwaysCreateNewBuffer = false, options) {
|
|
2069
2140
|
const keyParam = getKeyParam(this.encodeKey(key));
|
|
@@ -2177,7 +2248,9 @@ var Store = class {
|
|
|
2177
2248
|
transactionLogsPath: this.transactionLogsPath,
|
|
2178
2249
|
timestampFloorLog: this.timestampFloorLog,
|
|
2179
2250
|
verificationTable: this.verificationTable,
|
|
2180
|
-
writeBufferSize: this.writeBufferSize
|
|
2251
|
+
writeBufferSize: this.writeBufferSize,
|
|
2252
|
+
arenaBlockSize: this.arenaBlockSize,
|
|
2253
|
+
bloomFilterBitsPerKey: this.bloomFilterBitsPerKey
|
|
2181
2254
|
});
|
|
2182
2255
|
this.identityPath = this.db.identityPath;
|
|
2183
2256
|
return false;
|
|
@@ -2283,8 +2356,7 @@ function getKeyParam(keyBuffer) {
|
|
|
2283
2356
|
return keyBuffer.length;
|
|
2284
2357
|
}
|
|
2285
2358
|
|
|
2286
|
-
|
|
2287
|
-
//#region src/transaction.ts
|
|
2359
|
+
// src/transaction.ts
|
|
2288
2360
|
/**
|
|
2289
2361
|
* Sentinel value returned by `commit()` when `coordinatedRetry: true` and the
|
|
2290
2362
|
* transaction encountered an IsBusy conflict. The native layer parks on VT
|
|
@@ -2308,10 +2380,18 @@ var TransactionAlreadyAbortedError = class extends Error {
|
|
|
2308
2380
|
*/
|
|
2309
2381
|
var TransactionRetryableError = class extends Error {
|
|
2310
2382
|
hasLog;
|
|
2383
|
+
/**
|
|
2384
|
+
* The attempt was refused before writing its transaction-log batch (`transactionLogAbortMarkers`):
|
|
2385
|
+
* an older transaction held a write intent on one of its keys (`transactionLogWriteIntents`), or, on a retry, RocksDB's conflict
|
|
2386
|
+
* check found a write that already committed (see `lastConflictKind()`). Nothing of it is in the
|
|
2387
|
+
* log, so a retry may stage its batch afresh.
|
|
2388
|
+
*/
|
|
2389
|
+
refusedBeforeLog;
|
|
2311
2390
|
txn;
|
|
2312
2391
|
constructor(error, txn) {
|
|
2313
2392
|
super(error.message);
|
|
2314
2393
|
this.hasLog = error.hasLog ?? false;
|
|
2394
|
+
this.refusedBeforeLog = error.refusedBeforeLog ?? false;
|
|
2315
2395
|
this.txn = txn;
|
|
2316
2396
|
}
|
|
2317
2397
|
};
|
|
@@ -2341,6 +2421,38 @@ var TransactionAbandonedError = class extends Error {
|
|
|
2341
2421
|
}
|
|
2342
2422
|
};
|
|
2343
2423
|
/**
|
|
2424
|
+
* `abort()` of a transaction whose transaction-log batch could not be marked aborted
|
|
2425
|
+
* (`transactionLogAbortMarkers`). The batch stays unresolved, so committed readers do not pass it and
|
|
2426
|
+
* the log store refuses appends, until a retry on the next append, database flush or store close
|
|
2427
|
+
* marks it.
|
|
2428
|
+
*/
|
|
2429
|
+
var TransactionAbortUnresolvedError = class extends Error {
|
|
2430
|
+
code = "ERR_TRANSACTION_ABORT_UNRESOLVED";
|
|
2431
|
+
txn;
|
|
2432
|
+
constructor(error, txn) {
|
|
2433
|
+
super(error.message);
|
|
2434
|
+
this.txn = txn;
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
/**
|
|
2438
|
+
* Why a transaction's most recent attempt was refused or retried, from {@link Transaction.lastConflictKind}.
|
|
2439
|
+
*/
|
|
2440
|
+
const ConflictKind = {
|
|
2441
|
+
none: 0,
|
|
2442
|
+
/** Refused on an older transaction's write intent before writing its log batch. */
|
|
2443
|
+
intentBeforeLog: 1,
|
|
2444
|
+
/** Refused on an older transaction's write intent after its log batch was written. */
|
|
2445
|
+
intentAfterLog: 2,
|
|
2446
|
+
/** RocksDB found a conflict at commit. */
|
|
2447
|
+
atCommit: 3,
|
|
2448
|
+
/**
|
|
2449
|
+
* RocksDB's conflict check, run before the log write, found a write that already committed, or,
|
|
2450
|
+
* as at commit, could not validate because the memtable history it needs was flushed (needs a
|
|
2451
|
+
* RocksDB build that exports the check).
|
|
2452
|
+
*/
|
|
2453
|
+
checkBeforeLog: 4
|
|
2454
|
+
};
|
|
2455
|
+
/**
|
|
2344
2456
|
* Provides transaction level operations to a transaction callback.
|
|
2345
2457
|
*/
|
|
2346
2458
|
var Transaction = class extends DBI {
|
|
@@ -2355,9 +2467,10 @@ var Transaction = class extends DBI {
|
|
|
2355
2467
|
if (store.readOnly) {
|
|
2356
2468
|
super(store);
|
|
2357
2469
|
this.#txn = { id: 0 };
|
|
2358
|
-
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = () => {};
|
|
2470
|
+
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = this.setParkDeadline = () => {};
|
|
2359
2471
|
this.commit = async () => {};
|
|
2360
|
-
this.getTimestamp = () => 0;
|
|
2472
|
+
this.getTimestamp = this.lastConflictKind = () => 0;
|
|
2473
|
+
this.logWritten = () => false;
|
|
2361
2474
|
} else {
|
|
2362
2475
|
const txn = new NativeTransaction(store.db, options);
|
|
2363
2476
|
super(store, txn);
|
|
@@ -2383,6 +2496,7 @@ var Transaction = class extends DBI {
|
|
|
2383
2496
|
this.#txn.abort();
|
|
2384
2497
|
} catch (err) {
|
|
2385
2498
|
if (err instanceof Error && "code" in err && err.code === "ERR_TRANSACTION_ABANDONED") throw new TransactionAbandonedError(err, this);
|
|
2499
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_TRANSACTION_ABORT_UNRESOLVED") throw new TransactionAbortUnresolvedError(err, this);
|
|
2386
2500
|
throw err;
|
|
2387
2501
|
}
|
|
2388
2502
|
}
|
|
@@ -2451,6 +2565,28 @@ var Transaction = class extends DBI {
|
|
|
2451
2565
|
return this.#txn.getTimestamp();
|
|
2452
2566
|
}
|
|
2453
2567
|
/**
|
|
2568
|
+
* Whether this transaction's batch is in the transaction log. A commit conflict after the batch
|
|
2569
|
+
* was written keeps it for the retry, which must not stage it again; an attempt refused before
|
|
2570
|
+
* writing it (`refusedBeforeLog`) leaves nothing.
|
|
2571
|
+
*/
|
|
2572
|
+
logWritten() {
|
|
2573
|
+
return this.#txn.logWritten();
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Why the most recent attempt was refused or retried; one of {@link ConflictKind}.
|
|
2577
|
+
*/
|
|
2578
|
+
lastConflictKind() {
|
|
2579
|
+
return this.#txn.lastConflictKind();
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Bounds coordinated-retry parks: past `deadline` (epoch milliseconds) a parked commit resolves
|
|
2583
|
+
* `RETRY_NOW` at once, so a caller enforcing its own deadline can abort instead of waiting out
|
|
2584
|
+
* the default park bound. 0 clears it.
|
|
2585
|
+
*/
|
|
2586
|
+
setParkDeadline(deadline) {
|
|
2587
|
+
this.#txn.setParkDeadline(deadline);
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2454
2590
|
* Get the transaction id.
|
|
2455
2591
|
*/
|
|
2456
2592
|
get id() {
|
|
@@ -2471,8 +2607,8 @@ var Transaction = class extends DBI {
|
|
|
2471
2607
|
}
|
|
2472
2608
|
};
|
|
2473
2609
|
|
|
2474
|
-
|
|
2475
|
-
|
|
2610
|
+
// src/database.ts
|
|
2611
|
+
const backoffCell = new Int32Array(new SharedArrayBuffer(4));
|
|
2476
2612
|
const BACKGROUND_ERROR_SEVERITY_NAMES = [
|
|
2477
2613
|
"none",
|
|
2478
2614
|
"soft",
|
|
@@ -2882,6 +3018,19 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2882
3018
|
return this.store.db.flushSync(options);
|
|
2883
3019
|
}
|
|
2884
3020
|
/**
|
|
3021
|
+
* Syncs the write-ahead log to stable storage (`DB::SyncWAL`), so every WAL-backed write
|
|
3022
|
+
* committed before the call survives a power loss afterwards. Unlike {@link flush} it writes
|
|
3023
|
+
* no SST file and never stalls writers. Not covered: writes that skipped the WAL
|
|
3024
|
+
* (`disableWAL`), and transaction-log files, which keep their own durability. Runs on the
|
|
3025
|
+
* libuv threadpool; RocksDB serializes concurrent syncs of one WAL, so keep one in flight
|
|
3026
|
+
* per database rather than parking pool workers on a queue of them.
|
|
3027
|
+
*
|
|
3028
|
+
* On a read-only database the returned promise resolves without syncing.
|
|
3029
|
+
*/
|
|
3030
|
+
syncWal() {
|
|
3031
|
+
return new Promise((resolve, reject) => this.store.db.syncWal(resolve, reject));
|
|
3032
|
+
}
|
|
3033
|
+
/**
|
|
2885
3034
|
* Returns the most recent {@link BackgroundError} observed on this database, or
|
|
2886
3035
|
* `null` when none has occurred. The `'error'` event is the push counterpart;
|
|
2887
3036
|
* this is the pull equivalent for on-demand checks (e.g. a health probe) and
|
|
@@ -3350,7 +3499,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3350
3499
|
async transaction(callback, options) {
|
|
3351
3500
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3352
3501
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3353
|
-
const txn = new Transaction(this.store, options
|
|
3502
|
+
const txn = new Transaction(this.store, options?.retryOnBusy === false && options.coordinatedRetry ? {
|
|
3503
|
+
...options,
|
|
3504
|
+
coordinatedRetry: false
|
|
3505
|
+
} : options);
|
|
3354
3506
|
let result;
|
|
3355
3507
|
this.notify("begin-transaction");
|
|
3356
3508
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -3364,7 +3516,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3364
3516
|
commitResult = await txn.commit();
|
|
3365
3517
|
} catch (commitErr) {
|
|
3366
3518
|
if (commitErr instanceof TransactionAlreadyAbortedError) return;
|
|
3367
|
-
if (commitErr instanceof TransactionRetryableError && (options?.retryOnBusy ?? commitErr.hasLog) && attempt < maxRetries) continue;
|
|
3519
|
+
if (commitErr instanceof TransactionRetryableError && (options?.retryOnBusy ?? (commitErr.hasLog || commitErr.refusedBeforeLog)) && attempt < maxRetries) continue;
|
|
3368
3520
|
this.#abandonTransaction(txn, commitErr);
|
|
3369
3521
|
return;
|
|
3370
3522
|
}
|
|
@@ -3396,10 +3548,13 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3396
3548
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3397
3549
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3398
3550
|
const isRetryable = (err, attempt) => {
|
|
3399
|
-
|
|
3551
|
+
if (!(err instanceof TransactionRetryableError && (options?.retryOnBusy ?? (err.hasLog || err.refusedBeforeLog)) && attempt <= maxRetries)) return false;
|
|
3552
|
+
const kind = txn.lastConflictKind();
|
|
3553
|
+
if (kind === ConflictKind.intentBeforeLog || kind === ConflictKind.intentAfterLog) Atomics.wait(backoffCell, 0, 0, Math.min(2 ** attempt, 50));
|
|
3554
|
+
return true;
|
|
3400
3555
|
};
|
|
3556
|
+
const txn = new Transaction(this.store, options);
|
|
3401
3557
|
const runAttempt = (attempt) => {
|
|
3402
|
-
const txn = new Transaction(this.store, options);
|
|
3403
3558
|
let result;
|
|
3404
3559
|
try {
|
|
3405
3560
|
result = callback(txn, attempt);
|
|
@@ -3415,7 +3570,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3415
3570
|
if (isRetryable(commitErr, attempt)) return runAttempt(attempt + 1);
|
|
3416
3571
|
this.#abandonTransaction(txn, commitErr);
|
|
3417
3572
|
}
|
|
3418
|
-
});
|
|
3573
|
+
}, (callbackErr) => this.#abortTransaction(txn, callbackErr));
|
|
3419
3574
|
try {
|
|
3420
3575
|
txn.commitSync();
|
|
3421
3576
|
return result;
|
|
@@ -3440,7 +3595,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3440
3595
|
try {
|
|
3441
3596
|
txn.abort();
|
|
3442
3597
|
} catch (abortErr) {
|
|
3443
|
-
if (abortErr instanceof TransactionAbandonedError) throw abortErr;
|
|
3598
|
+
if (abortErr instanceof TransactionAbandonedError || abortErr instanceof TransactionAbortUnresolvedError) throw abortErr;
|
|
3444
3599
|
}
|
|
3445
3600
|
throw commitErr;
|
|
3446
3601
|
}
|
|
@@ -3555,10 +3710,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3555
3710
|
}
|
|
3556
3711
|
};
|
|
3557
3712
|
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
const { TRANSACTION_LOG_TOKEN, TRANSACTION_LOG_ENTRY_HEADER_SIZE: TRANSACTION_LOG_ENTRY_HEADER_SIZE$1 } = constants;
|
|
3713
|
+
// src/parse-transaction-log.ts
|
|
3714
|
+
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;
|
|
3561
3715
|
const MIN_VALID_TIMESTAMP = Date.UTC(2026, 0, 27);
|
|
3716
|
+
const VALID_FLAGS_MASK = TRANSACTION_LOG_ENTRY_LAST_FLAG$1 | TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1;
|
|
3562
3717
|
/**
|
|
3563
3718
|
* Loads an entire transaction log file into memory.
|
|
3564
3719
|
* @param path - The path to the transaction log file.
|
|
@@ -3596,6 +3751,8 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3596
3751
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) anomalies.push(`Header timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3597
3752
|
const entries = [];
|
|
3598
3753
|
let entryAnomalyCount = 0;
|
|
3754
|
+
let previousEndedTransaction = true;
|
|
3755
|
+
let previousTimestamp = NaN;
|
|
3599
3756
|
while (fileOffset < size) {
|
|
3600
3757
|
const timestamp = read(8).readDoubleBE(0);
|
|
3601
3758
|
if (timestamp === 0) {
|
|
@@ -3612,7 +3769,11 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3612
3769
|
const data = read(length);
|
|
3613
3770
|
const entryAnomalies = [];
|
|
3614
3771
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) entryAnomalies.push(`timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3615
|
-
if ((flags &
|
|
3772
|
+
if ((flags & ~VALID_FLAGS_MASK) !== 0) entryAnomalies.push(`flags 0x${flags.toString(16).padStart(2, "0")} contains undefined bits (expected 0x00-0x03)`);
|
|
3773
|
+
const startsTransaction = previousEndedTransaction || timestamp !== previousTimestamp;
|
|
3774
|
+
if ((flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1) !== 0 && !startsTransaction) entryAnomalies.push("abort flag on an entry that does not start a transaction");
|
|
3775
|
+
previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG$1) !== 0;
|
|
3776
|
+
previousTimestamp = timestamp;
|
|
3616
3777
|
const entry = {
|
|
3617
3778
|
timestamp,
|
|
3618
3779
|
length,
|
|
@@ -3641,11 +3802,45 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3641
3802
|
}
|
|
3642
3803
|
}
|
|
3643
3804
|
|
|
3644
|
-
|
|
3645
|
-
//#region src/transaction-log-reader.ts
|
|
3805
|
+
// src/transaction-log-reader.ts
|
|
3646
3806
|
const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
|
|
3647
3807
|
const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
|
|
3648
|
-
const
|
|
3808
|
+
const EXACT_START_SPAN = /* @__PURE__ */ new Float64Array(3);
|
|
3809
|
+
const ITERATOR_TOKEN = Symbol("transactionLogIterator");
|
|
3810
|
+
const logEquals = TransactionLog.prototype.equals;
|
|
3811
|
+
var LogIterator = class {
|
|
3812
|
+
#sourceLog;
|
|
3813
|
+
#control;
|
|
3814
|
+
next;
|
|
3815
|
+
constructor(token, sourceLog, next, control) {
|
|
3816
|
+
if (token !== ITERATOR_TOKEN) throw new TypeError("Transaction log iterators require query()");
|
|
3817
|
+
this.#sourceLog = sourceLog;
|
|
3818
|
+
this.next = next;
|
|
3819
|
+
this.#control = control;
|
|
3820
|
+
}
|
|
3821
|
+
static cursor(value) {
|
|
3822
|
+
if (typeof value === "object" && value !== null && #control in value) return value;
|
|
3823
|
+
}
|
|
3824
|
+
static isFrom(value, sourceLog) {
|
|
3825
|
+
return logEquals.call(sourceLog, value.#sourceLog);
|
|
3826
|
+
}
|
|
3827
|
+
[Symbol.iterator]() {
|
|
3828
|
+
return this;
|
|
3829
|
+
}
|
|
3830
|
+
peek() {
|
|
3831
|
+
return this.#control("peek");
|
|
3832
|
+
}
|
|
3833
|
+
close() {
|
|
3834
|
+
this.#control("close");
|
|
3835
|
+
}
|
|
3836
|
+
static reached(value, logId, position, atSegmentEnd) {
|
|
3837
|
+
return value.#control("reached", logId, position, atSegmentEnd);
|
|
3838
|
+
}
|
|
3839
|
+
static scanEnd(value, logId, size) {
|
|
3840
|
+
return value.#control("scanEnd", logId, size);
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE, TRANSACTION_LOG_ENTRY_LAST_FLAG, TRANSACTION_LOG_ENTRY_ABORTED_FLAG } = constants;
|
|
3649
3844
|
/**
|
|
3650
3845
|
* Number of consecutive well-formed frames that, on their own, signal that real log data has
|
|
3651
3846
|
* resumed after a framing break. Kept in step with `RESYNC_MIN_FRAMES` in
|
|
@@ -3681,6 +3876,43 @@ var CorruptFrameError = class extends RangeError {
|
|
|
3681
3876
|
}
|
|
3682
3877
|
};
|
|
3683
3878
|
/**
|
|
3879
|
+
* Thrown when an iterator advances past segments it never read and that can no longer be read —
|
|
3880
|
+
* typically ones retention purged while the reader lagged. The entries they held cannot be
|
|
3881
|
+
* recovered from this log, so a consumer replicating it must not treat the stream as contiguous.
|
|
3882
|
+
* Missing sequence numbers prove only that continuity is unknown, not that each held entries.
|
|
3883
|
+
*
|
|
3884
|
+
* The iterator is left positioned at the start of `resumeLogId`, so a caller that accepts the
|
|
3885
|
+
* discontinuity calls `next()` again to continue from there.
|
|
3886
|
+
*
|
|
3887
|
+
* Not a `RangeError`: this is not broken framing, and consumers classify those by class.
|
|
3888
|
+
*/
|
|
3889
|
+
var TransactionLogGapError = class extends Error {
|
|
3890
|
+
/** Sequence number of the last segment this iterator read up to. */
|
|
3891
|
+
logId;
|
|
3892
|
+
/** Sequence number of the segment iteration resumes in. */
|
|
3893
|
+
resumeLogId;
|
|
3894
|
+
/** Segments between `logId` and `resumeLogId` that could not be read. Empty segments are not counted. */
|
|
3895
|
+
unavailableLogCount;
|
|
3896
|
+
constructor(logId, resumeLogId, unavailableLogCount) {
|
|
3897
|
+
super(`Transaction log iterator skipped ${unavailableLogCount} unavailable segment(s) between log ${logId} and log ${resumeLogId}; entries in them were not read`);
|
|
3898
|
+
this.name = "TransactionLogGapError";
|
|
3899
|
+
this.logId = logId;
|
|
3900
|
+
this.resumeLogId = resumeLogId;
|
|
3901
|
+
this.unavailableLogCount = unavailableLogCount;
|
|
3902
|
+
}
|
|
3903
|
+
};
|
|
3904
|
+
/** Readable input ended before the iterator's physical `until` bound. Retry after input is available. */
|
|
3905
|
+
var TransactionLogUntilUnreachedError = class extends Error {
|
|
3906
|
+
logId;
|
|
3907
|
+
position;
|
|
3908
|
+
constructor(logId, position) {
|
|
3909
|
+
super(`Transaction log reader stopped at position ${position.toString(16)} of log ${logId}, short of the iterator it reads until`);
|
|
3910
|
+
this.name = "TransactionLogUntilUnreachedError";
|
|
3911
|
+
this.logId = logId;
|
|
3912
|
+
this.position = position;
|
|
3913
|
+
}
|
|
3914
|
+
};
|
|
3915
|
+
/**
|
|
3684
3916
|
* Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
|
|
3685
3917
|
* the written extent: a single entry can legitimately exceed the rotation threshold (the first
|
|
3686
3918
|
* entry written to a fresh file is always written in full). A zero timestamp is the
|
|
@@ -3742,7 +3974,9 @@ function corruptFrame(message, transactionLog, dataView, logBuffer, position, li
|
|
|
3742
3974
|
* exactly matches the start timestamp, and then return all subsequent transactions in the log
|
|
3743
3975
|
* regardless of whether their timestamp is before or after the start
|
|
3744
3976
|
*/
|
|
3745
|
-
Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart } = {}) {
|
|
3977
|
+
Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart, until } = {}) {
|
|
3978
|
+
let untilCursor = LogIterator.cursor(until);
|
|
3979
|
+
if (until !== void 0 && (!untilCursor || !LogIterator.isFrom(untilCursor, this))) throw new TypeError("until must be an iterator from query() on the same transaction log");
|
|
3746
3980
|
if (!this._lastCommittedPosition) {
|
|
3747
3981
|
const lastCommittedPosition = this._getLastCommittedPosition();
|
|
3748
3982
|
this._lastCommittedPosition = new Float64Array(lastCommittedPosition.buffer);
|
|
@@ -3756,6 +3990,14 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3756
3990
|
let dataView;
|
|
3757
3991
|
let logBuffer = this._currentLogBuffer?.deref();
|
|
3758
3992
|
let foundExactStart = false;
|
|
3993
|
+
let stopLogId = 0;
|
|
3994
|
+
let stopPosition = 0;
|
|
3995
|
+
let tailLogId = 0;
|
|
3996
|
+
let tailPosition = 0;
|
|
3997
|
+
let segmentRemovals = 0;
|
|
3998
|
+
let previousEndedTransaction = true;
|
|
3999
|
+
let previousTimestamp = NaN;
|
|
4000
|
+
let skippingAborted = false;
|
|
3759
4001
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3760
4002
|
position = size;
|
|
3761
4003
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
@@ -3765,127 +4007,304 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3765
4007
|
FLOAT_TO_UINT32[0] = this._getLastFlushed();
|
|
3766
4008
|
if (FLOAT_TO_UINT32[0] === 0) FLOAT_TO_UINT32[0] = this._findPosition(0);
|
|
3767
4009
|
start ??= 0;
|
|
4010
|
+
} else if (exactStart) {
|
|
4011
|
+
const startPosition = this._findExactStart(start, EXACT_START_SPAN);
|
|
4012
|
+
FLOAT_TO_UINT32[0] = EXACT_START_SPAN[0];
|
|
4013
|
+
stopLogId = UINT32_FROM_FLOAT[1];
|
|
4014
|
+
stopPosition = UINT32_FROM_FLOAT[0];
|
|
4015
|
+
FLOAT_TO_UINT32[0] = EXACT_START_SPAN[1];
|
|
4016
|
+
tailLogId = UINT32_FROM_FLOAT[1];
|
|
4017
|
+
tailPosition = UINT32_FROM_FLOAT[0];
|
|
4018
|
+
segmentRemovals = EXACT_START_SPAN[2];
|
|
4019
|
+
FLOAT_TO_UINT32[0] = startPosition;
|
|
3768
4020
|
} else FLOAT_TO_UINT32[0] = this._findPosition(start);
|
|
3769
4021
|
logId = UINT32_FROM_FLOAT[1];
|
|
3770
4022
|
position = UINT32_FROM_FLOAT[0];
|
|
3771
4023
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3772
4024
|
}
|
|
4025
|
+
const positionedAt = position;
|
|
4026
|
+
if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition) && mayJumpTo(untilCursor, tailLogId, tailPosition)) {
|
|
4027
|
+
stopLogId = 0;
|
|
4028
|
+
if (skipToTail(this, logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
4029
|
+
logId = tailLogId;
|
|
4030
|
+
position = tailPosition;
|
|
4031
|
+
logBuffer = tailLogBuffer ?? logBuffer;
|
|
4032
|
+
}
|
|
4033
|
+
tailLogBuffer = void 0;
|
|
4034
|
+
}
|
|
4035
|
+
let gapFloor = logId;
|
|
4036
|
+
let startUnmapped = false;
|
|
4037
|
+
let startMissing = false;
|
|
3773
4038
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3774
4039
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3775
4040
|
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
3776
4041
|
if (logBuffer === void 0) {
|
|
3777
4042
|
logBuffer = Buffer.alloc(0);
|
|
3778
|
-
logBuffer.logId =
|
|
4043
|
+
logBuffer.logId = logId;
|
|
3779
4044
|
logBuffer.size = 0;
|
|
3780
4045
|
logBuffer.dataView = new DataView(logBuffer.buffer);
|
|
3781
4046
|
size = 0;
|
|
4047
|
+
startUnmapped = logId > 0;
|
|
4048
|
+
if (positionedAt <= TRANSACTION_LOG_FILE_HEADER_SIZE || logId > 0 && this.getLogFileSize(logId) > 0) gapFloor = logId - 1;
|
|
3782
4049
|
}
|
|
3783
4050
|
}
|
|
3784
4051
|
dataView = logBuffer.dataView;
|
|
3785
4052
|
if (latestLogId < logId) size = 0;
|
|
3786
|
-
else if (latestLogId > logId) {
|
|
4053
|
+
else if (latestLogId > logId && !startUnmapped) {
|
|
3787
4054
|
const cachedSize = logBuffer.size;
|
|
3788
4055
|
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3789
4056
|
else size = cachedSize;
|
|
3790
4057
|
}
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
4058
|
+
let peeked;
|
|
4059
|
+
let peekedLogId = 0;
|
|
4060
|
+
let peekedPosition = 0;
|
|
4061
|
+
let closed = false;
|
|
4062
|
+
let needsCursorCheck = untilCursor !== void 0 || startUnmapped;
|
|
4063
|
+
const iterator = new LogIterator(ITERATOR_TOKEN, transactionLog, function next() {
|
|
4064
|
+
let scanEnd = size;
|
|
4065
|
+
if (needsCursorCheck) {
|
|
4066
|
+
if (peeked) {
|
|
4067
|
+
const result = peeked;
|
|
4068
|
+
if (!closed) {
|
|
4069
|
+
peeked = void 0;
|
|
4070
|
+
needsCursorCheck = untilCursor !== void 0;
|
|
4071
|
+
}
|
|
4072
|
+
return result;
|
|
4073
|
+
}
|
|
4074
|
+
if (untilCursor) {
|
|
4075
|
+
const limit = LogIterator.scanEnd(untilCursor, logBuffer.logId, size);
|
|
4076
|
+
if (limit === void 0) {
|
|
4077
|
+
untilCursor = void 0;
|
|
4078
|
+
needsCursorCheck = startUnmapped;
|
|
4079
|
+
} else {
|
|
4080
|
+
if (position >= limit && LogIterator.reached(untilCursor, logBuffer.logId, position, position >= size && logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
|
|
4081
|
+
done: true,
|
|
4082
|
+
value: void 0
|
|
4083
|
+
};
|
|
4084
|
+
scanEnd = limit;
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
if (startUnmapped) {
|
|
4088
|
+
const mapped = getLogMemoryMap(transactionLog, logBuffer.logId);
|
|
4089
|
+
if (mapped) {
|
|
4090
|
+
logBuffer = mapped;
|
|
4091
|
+
dataView = mapped.dataView;
|
|
4092
|
+
} else {
|
|
4093
|
+
const latestLogId = loadLastPosition(transactionLog, !!readUncommitted).logId;
|
|
4094
|
+
const fileSize = transactionLog.getLogFileSize(logBuffer.logId);
|
|
4095
|
+
if (logBuffer.logId >= latestLogId || fileSize > position) return endOfReadable(untilCursor, logBuffer, position);
|
|
4096
|
+
if (fileSize === 0) startMissing = true;
|
|
4097
|
+
}
|
|
4098
|
+
startUnmapped = false;
|
|
4099
|
+
needsCursorCheck = untilCursor !== void 0;
|
|
4100
|
+
size = 0;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
let timestamp;
|
|
4104
|
+
if (position >= size) {
|
|
4105
|
+
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
4106
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
4107
|
+
if (latestLogId > logBuffer.logId) {
|
|
4108
|
+
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
4109
|
+
if (position >= size) {
|
|
4110
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
|
|
4111
|
+
done: true,
|
|
4112
|
+
value: void 0
|
|
4113
|
+
};
|
|
4114
|
+
const fromLogId = logBuffer.logId - (startMissing ? 1 : 0);
|
|
4115
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
4116
|
+
if (nextLogBuffer) {
|
|
4117
|
+
startMissing = false;
|
|
4118
|
+
const unavailable = unavailableLogCount;
|
|
4119
|
+
dataView = nextLogBuffer.dataView;
|
|
4120
|
+
logBuffer = nextLogBuffer;
|
|
4121
|
+
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
4122
|
+
else size = latestSize;
|
|
4123
|
+
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4124
|
+
previousEndedTransaction = true;
|
|
4125
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
|
|
4130
|
+
}
|
|
4131
|
+
if (position >= scanEnd) return endOfReadable(untilCursor, logBuffer, position);
|
|
4132
|
+
let jumpRefused = false;
|
|
4133
|
+
let limit = Math.min(size, logBuffer.length);
|
|
4134
|
+
do {
|
|
4135
|
+
try {
|
|
4136
|
+
timestamp = dataView.getFloat64(position);
|
|
4137
|
+
} catch (error) {
|
|
4138
|
+
error.message += ` at position ${position.toString(16)} of log ${logBuffer.logId} (size=${size}, log buffer length=${logBuffer.length})`;
|
|
4139
|
+
throw error;
|
|
4140
|
+
}
|
|
4141
|
+
if (!timestamp) return endOfReadable(untilCursor, logBuffer, position);
|
|
4142
|
+
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
|
|
4143
|
+
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);
|
|
4144
|
+
position = broken.nextPosition;
|
|
4145
|
+
previousEndedTransaction = true;
|
|
4146
|
+
throw broken.error;
|
|
4147
|
+
}
|
|
4148
|
+
const length = dataView.getUint32(position + 8);
|
|
4149
|
+
if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
|
|
4150
|
+
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);
|
|
4151
|
+
position = broken.nextPosition;
|
|
4152
|
+
previousEndedTransaction = true;
|
|
4153
|
+
throw broken.error;
|
|
4154
|
+
}
|
|
4155
|
+
position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
4156
|
+
const flags = logBuffer[position - 1];
|
|
4157
|
+
const aborted = (flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG) !== 0;
|
|
4158
|
+
if (previousEndedTransaction || timestamp !== previousTimestamp || aborted) skippingAborted = aborted;
|
|
4159
|
+
previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG) !== 0;
|
|
4160
|
+
previousTimestamp = timestamp;
|
|
4161
|
+
let matchesRange;
|
|
4162
|
+
if (skippingAborted) matchesRange = false;
|
|
4163
|
+
else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
4164
|
+
else if (exactStart) {
|
|
4165
|
+
matchesRange = false;
|
|
4166
|
+
if (timestamp === start) {
|
|
4167
|
+
matchesRange = !exclusiveStart;
|
|
4168
|
+
foundExactStart = true;
|
|
4169
|
+
stopLogId = 0;
|
|
4170
|
+
}
|
|
4171
|
+
} else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
|
|
4172
|
+
if (!matchesRange && stopLogId !== 0 && !jumpRefused && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
|
|
4173
|
+
if (mayJumpTo(untilCursor, tailLogId, tailPosition)) {
|
|
4174
|
+
stopLogId = 0;
|
|
4175
|
+
position += length;
|
|
4176
|
+
if (skipToTail(transactionLog, logBuffer.logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
4177
|
+
if (tailLogBuffer) {
|
|
4178
|
+
logBuffer = tailLogBuffer;
|
|
4179
|
+
dataView = tailLogBuffer.dataView;
|
|
3810
4180
|
}
|
|
4181
|
+
position = tailPosition;
|
|
4182
|
+
previousEndedTransaction = true;
|
|
4183
|
+
size = 0;
|
|
3811
4184
|
}
|
|
4185
|
+
tailLogBuffer = void 0;
|
|
4186
|
+
return iterator.next();
|
|
3812
4187
|
}
|
|
4188
|
+
jumpRefused = true;
|
|
3813
4189
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
4190
|
+
const entryStart = position;
|
|
4191
|
+
position += length;
|
|
4192
|
+
if (matchesRange) return {
|
|
4193
|
+
done: false,
|
|
4194
|
+
value: {
|
|
4195
|
+
timestamp,
|
|
4196
|
+
endTxn: previousEndedTransaction,
|
|
4197
|
+
data: logBuffer.subarray(entryStart, position)
|
|
3820
4198
|
}
|
|
3821
|
-
|
|
4199
|
+
};
|
|
4200
|
+
if (position >= scanEnd) {
|
|
4201
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position)) return {
|
|
3822
4202
|
done: true,
|
|
3823
4203
|
value: void 0
|
|
3824
4204
|
};
|
|
3825
|
-
const
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
position = broken.nextPosition;
|
|
3835
|
-
throw broken.error;
|
|
3836
|
-
}
|
|
3837
|
-
position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
3838
|
-
let matchesRange;
|
|
3839
|
-
if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
3840
|
-
else if (exactStart) {
|
|
3841
|
-
if (timestamp === start) {
|
|
3842
|
-
matchesRange = !exclusiveStart;
|
|
3843
|
-
foundExactStart = true;
|
|
3844
|
-
} else matchesRange = false;
|
|
3845
|
-
} else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
|
|
3846
|
-
const entryStart = position;
|
|
3847
|
-
position += length;
|
|
3848
|
-
if (matchesRange) return {
|
|
3849
|
-
done: false,
|
|
3850
|
-
value: {
|
|
3851
|
-
timestamp,
|
|
3852
|
-
endTxn: Boolean(logBuffer[entryStart - 1] & 1),
|
|
3853
|
-
data: logBuffer.subarray(entryStart, position)
|
|
4205
|
+
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
4206
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
4207
|
+
if (latestLogId > logBuffer.logId) {
|
|
4208
|
+
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
4209
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
4210
|
+
if (position < size) {
|
|
4211
|
+
scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
|
|
4212
|
+
limit = Math.min(size, logBuffer.length);
|
|
4213
|
+
continue;
|
|
3854
4214
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
else
|
|
3872
|
-
const cachedSize = logBuffer.size;
|
|
3873
|
-
if (cachedSize === void 0) {
|
|
3874
|
-
size = readableExtent(logBuffer);
|
|
3875
|
-
if (!readUncommitted) logBuffer.size = size;
|
|
3876
|
-
} else size = cachedSize;
|
|
3877
|
-
}
|
|
3878
|
-
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4215
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
|
|
4216
|
+
done: true,
|
|
4217
|
+
value: void 0
|
|
4218
|
+
};
|
|
4219
|
+
const fromLogId = logBuffer.logId;
|
|
4220
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
4221
|
+
if (!nextLogBuffer) return endOfReadable(untilCursor, logBuffer, position);
|
|
4222
|
+
const unavailable = unavailableLogCount;
|
|
4223
|
+
logBuffer = nextLogBuffer;
|
|
4224
|
+
dataView = logBuffer.dataView;
|
|
4225
|
+
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
4226
|
+
else {
|
|
4227
|
+
const cachedSize = logBuffer.size;
|
|
4228
|
+
if (cachedSize === void 0) {
|
|
4229
|
+
size = readableExtent(logBuffer);
|
|
4230
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
4231
|
+
} else size = cachedSize;
|
|
3879
4232
|
}
|
|
4233
|
+
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4234
|
+
previousEndedTransaction = true;
|
|
4235
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
3880
4236
|
}
|
|
4237
|
+
scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
|
|
4238
|
+
limit = Math.min(size, logBuffer.length);
|
|
4239
|
+
}
|
|
4240
|
+
} while (position < scanEnd);
|
|
4241
|
+
return endOfReadable(untilCursor, logBuffer, position);
|
|
4242
|
+
}, function control(operation, fromLogId, fromPosition, atSegmentEnd) {
|
|
4243
|
+
if (operation === "peek") {
|
|
4244
|
+
if (peeked) return peeked;
|
|
4245
|
+
const result = iterator.next();
|
|
4246
|
+
if (!result.done) {
|
|
4247
|
+
peeked = result;
|
|
4248
|
+
needsCursorCheck = true;
|
|
4249
|
+
peekedLogId = logBuffer.logId;
|
|
4250
|
+
peekedPosition = position - result.value.data.length - TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
3881
4251
|
}
|
|
3882
|
-
return
|
|
4252
|
+
return result;
|
|
4253
|
+
}
|
|
4254
|
+
if (operation === "close") {
|
|
4255
|
+
closed = true;
|
|
4256
|
+
needsCursorCheck = true;
|
|
4257
|
+
peeked = {
|
|
3883
4258
|
done: true,
|
|
3884
4259
|
value: void 0
|
|
3885
4260
|
};
|
|
4261
|
+
logBuffer = void 0;
|
|
4262
|
+
dataView = void 0;
|
|
4263
|
+
untilCursor = void 0;
|
|
4264
|
+
return;
|
|
3886
4265
|
}
|
|
3887
|
-
|
|
4266
|
+
if (closed) return;
|
|
4267
|
+
const boundLogId = peeked ? peekedLogId : logBuffer.logId;
|
|
4268
|
+
const boundPosition = peeked ? peekedPosition : position;
|
|
4269
|
+
if (operation === "scanEnd") {
|
|
4270
|
+
if (boundLogId !== fromLogId) return boundLogId > fromLogId ? fromPosition : 0;
|
|
4271
|
+
return boundPosition < fromPosition ? boundPosition : fromPosition;
|
|
4272
|
+
}
|
|
4273
|
+
return pastStop(fromLogId, fromPosition, boundLogId, boundPosition) || !!atSegmentEnd && boundLogId === fromLogId + 1 && boundPosition === TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4274
|
+
});
|
|
4275
|
+
return iterator;
|
|
3888
4276
|
} });
|
|
4277
|
+
function endOfReadable(untilCursor, logBuffer, position) {
|
|
4278
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer)) === false) throw new TransactionLogUntilUnreachedError(logBuffer.logId, position);
|
|
4279
|
+
return {
|
|
4280
|
+
done: true,
|
|
4281
|
+
value: void 0
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
function pastStop(logId, position, stopLogId, stopPosition) {
|
|
4285
|
+
return logId > stopLogId || logId === stopLogId && position >= stopPosition;
|
|
4286
|
+
}
|
|
4287
|
+
function mayJumpTo(untilCursor, logId, position) {
|
|
4288
|
+
return !untilCursor || LogIterator.reached(untilCursor, logId, position - 1) !== true;
|
|
4289
|
+
}
|
|
4290
|
+
let tailLogBuffer;
|
|
4291
|
+
/**
|
|
4292
|
+
* Whether an exact-start lookup at `position` of `fromLogId` may take its one-shot jump to the tail.
|
|
4293
|
+
* next() calls it on a frame boundary, so a mapping error leaves a resumable cursor; from query()
|
|
4294
|
+
* the error escapes before any iterator exists. The removal
|
|
4295
|
+
* check follows the mapping and holds the store's lock, so a purge either declines the jump (the
|
|
4296
|
+
* scan then reports the gap) or follows it like a purge of segments already read.
|
|
4297
|
+
*/
|
|
4298
|
+
function skipToTail(transactionLog, fromLogId, position, tailLogId, tailPosition, segmentRemovals) {
|
|
4299
|
+
tailLogBuffer = void 0;
|
|
4300
|
+
if (tailLogId === fromLogId) return tailPosition > position;
|
|
4301
|
+
if (tailLogId < fromLogId) return false;
|
|
4302
|
+
const mapped = getLogMemoryMap(transactionLog, tailLogId);
|
|
4303
|
+
if (mapped === void 0 || transactionLog._segmentRemovals() !== segmentRemovals) return false;
|
|
4304
|
+
tailLogBuffer = mapped;
|
|
4305
|
+
return true;
|
|
4306
|
+
}
|
|
4307
|
+
let unavailableLogCount = 0;
|
|
3889
4308
|
/**
|
|
3890
4309
|
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3891
4310
|
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
@@ -3894,15 +4313,22 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3894
4313
|
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3895
4314
|
* and so lands *past* a survivor sitting between two holes.
|
|
3896
4315
|
*/
|
|
3897
|
-
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
4316
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor) {
|
|
4317
|
+
unavailableLogCount = 0;
|
|
3898
4318
|
let candidateLogId = fromLogId + 1;
|
|
3899
4319
|
while (candidateLogId <= latestLogId) {
|
|
3900
4320
|
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3901
4321
|
if (logBuffer) {
|
|
3902
4322
|
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3903
|
-
} else
|
|
3904
|
-
|
|
3905
|
-
|
|
4323
|
+
} else {
|
|
4324
|
+
const fileSize = transactionLog.getLogFileSize(candidateLogId);
|
|
4325
|
+
if (fileSize > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
|
|
4326
|
+
if (fileSize === 0 && candidateLogId > gapFloor) unavailableLogCount++;
|
|
4327
|
+
}
|
|
4328
|
+
const nextLogId = transactionLog._nextLogId(candidateLogId);
|
|
4329
|
+
if (nextLogId === 0) return;
|
|
4330
|
+
unavailableLogCount += Math.max(0, nextLogId - 1 - Math.max(candidateLogId, gapFloor));
|
|
4331
|
+
candidateLogId = nextLogId;
|
|
3906
4332
|
}
|
|
3907
4333
|
}
|
|
3908
4334
|
/**
|
|
@@ -3956,13 +4382,11 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3956
4382
|
};
|
|
3957
4383
|
}
|
|
3958
4384
|
|
|
3959
|
-
|
|
3960
|
-
//#region src/index.ts
|
|
4385
|
+
// src/index.ts
|
|
3961
4386
|
const versions = {
|
|
3962
4387
|
rocksdb: version,
|
|
3963
|
-
"rocksdb-js": "2.
|
|
4388
|
+
"rocksdb-js": "2.14.0"
|
|
3964
4389
|
};
|
|
3965
4390
|
|
|
3966
|
-
|
|
3967
|
-
export { BackgroundError, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionLog, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getRegistryStatus, getWriteBufferManagerStats, parseTransactionLog, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
|
|
4391
|
+
export { BackgroundError, ConflictKind, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionAbortUnresolvedError, TransactionLog, TransactionLogGapError, TransactionLogUntilUnreachedError, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getRegistryStatus, getWriteBufferManagerStats, parseTransactionLog, raiseTimestampFloor, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
|
|
3968
4392
|
//# sourceMappingURL=index.mjs.map
|