@harperfast/rocksdb-js 2.9.1 → 2.11.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 +377 -56
- package/bin/rocksdb-js.mjs +49 -5
- package/dist/index.cjs +206 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +180 -7
- package/dist/index.d.mts +180 -7
- package/dist/index.mjs +205 -33
- package/dist/index.mjs.map +1 -1
- package/docs/backups.md +3 -2
- package/docs/performance/mutex-contention-2026-09-16.json +684 -0
- package/docs/performance/mutex-contention-admission-2026-09-17.json +730 -0
- package/docs/performance/mutex-contention.md +204 -0
- package/docs/stats.md +48 -46
- package/docs/steady-clock-design.md +121 -0
- package/docs/transaction-log.md +120 -1
- package/docs/transaction-timestamp-integrity-design.md +70 -8
- package/package.json +16 -16
package/bin/rocksdb-js.mjs
CHANGED
|
@@ -160,13 +160,32 @@ function createRL() {
|
|
|
160
160
|
const iface = createInterface({ input: stdin, output: stdout, completer });
|
|
161
161
|
iface.on('SIGINT', onSIGINT);
|
|
162
162
|
iface.on('SIGBREAK', () => process.exit(0));
|
|
163
|
+
iface.on('close', () => {
|
|
164
|
+
rlClosed = true;
|
|
165
|
+
});
|
|
166
|
+
iface.on('error', (err) => {
|
|
167
|
+
console.error(bad(`stdin error: ${err.message ?? err}`));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
});
|
|
163
170
|
return iface;
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
let rl = createRL();
|
|
174
|
+
let lineIterator = rl[Symbol.asyncIterator]();
|
|
175
|
+
let rlClosed = false;
|
|
167
176
|
let ctrlC = false;
|
|
168
177
|
let currentAbortController = null;
|
|
169
178
|
|
|
179
|
+
function whenAborted(signal) {
|
|
180
|
+
return new Promise((_resolve, reject) => {
|
|
181
|
+
signal.addEventListener(
|
|
182
|
+
'abort',
|
|
183
|
+
() => reject(Object.assign(new Error('The operation was aborted'), { code: 'ABORT_ERR' })),
|
|
184
|
+
{ once: true }
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
170
189
|
function str(text) {
|
|
171
190
|
try {
|
|
172
191
|
if (typeof text === 'symbol') {
|
|
@@ -240,17 +259,32 @@ async function run(fn) {
|
|
|
240
259
|
return { time: end - start, result };
|
|
241
260
|
}
|
|
242
261
|
|
|
262
|
+
// Returns the next line, or `null` once the iterator itself reports `done`. `close` can
|
|
263
|
+
// fire before lineIterator's already-buffered lines are drained, so `rlClosed` never
|
|
264
|
+
// gates reading — only resume()/setPrompt()/prompt()/pause(), which throw
|
|
265
|
+
// ERR_USE_AFTER_CLOSE once closed.
|
|
243
266
|
async function ask(prompt) {
|
|
244
|
-
|
|
267
|
+
if (!rlClosed) {
|
|
268
|
+
rl.resume();
|
|
269
|
+
rl.setPrompt(prompt);
|
|
270
|
+
rl.prompt();
|
|
271
|
+
}
|
|
272
|
+
// A second next() would queue behind this one and steal the next real line out from
|
|
273
|
+
// under a Ctrl-C retry below, so retries re-await this same promise instead.
|
|
274
|
+
const pending = lineIterator.next();
|
|
245
275
|
while (true) {
|
|
246
276
|
try {
|
|
247
277
|
currentAbortController = new AbortController();
|
|
248
|
-
const
|
|
249
|
-
rl.pause();
|
|
278
|
+
const result = await Promise.race([pending, whenAborted(currentAbortController.signal)]);
|
|
250
279
|
ctrlC = false;
|
|
251
|
-
return
|
|
280
|
+
if (result.done) return null;
|
|
281
|
+
if (!rlClosed) rl.pause();
|
|
282
|
+
return result.value;
|
|
252
283
|
} catch (err) {
|
|
253
284
|
if (err.code !== 'ABORT_ERR') throw err;
|
|
285
|
+
if (rlClosed) continue;
|
|
286
|
+
rl.clearLine();
|
|
287
|
+
rl.prompt();
|
|
254
288
|
}
|
|
255
289
|
}
|
|
256
290
|
}
|
|
@@ -842,6 +876,12 @@ async function removeCommand(args) {
|
|
|
842
876
|
}
|
|
843
877
|
|
|
844
878
|
async function replCommand() {
|
|
879
|
+
// A non-TTY stdin can't feed the sub-REPL interactively, and input already read out
|
|
880
|
+
// of it has no defined recipient once this CLI's interface closes.
|
|
881
|
+
if (!stdin.isTTY) {
|
|
882
|
+
console.log(bad('The JS sub-REPL needs an interactive terminal; stdin is not one here.\n'));
|
|
883
|
+
process.exit(1);
|
|
884
|
+
}
|
|
845
885
|
const history = [...rl.history];
|
|
846
886
|
rl.close();
|
|
847
887
|
r = repl.start({ prompt: styleText('magenta', 'js> '), useColors: true, useGlobal: false });
|
|
@@ -852,6 +892,8 @@ async function replCommand() {
|
|
|
852
892
|
console.log();
|
|
853
893
|
rl = createRL();
|
|
854
894
|
rl.history = history;
|
|
895
|
+
lineIterator = rl[Symbol.asyncIterator]();
|
|
896
|
+
rlClosed = false;
|
|
855
897
|
}
|
|
856
898
|
|
|
857
899
|
function statsCommand() {
|
|
@@ -942,7 +984,9 @@ async function main() {
|
|
|
942
984
|
console.log(`Type "help" for more information.`);
|
|
943
985
|
|
|
944
986
|
while (true) {
|
|
945
|
-
const
|
|
987
|
+
const raw = await ask('> ');
|
|
988
|
+
if (raw === null) break;
|
|
989
|
+
const line = raw.trim().split(/[ \t]+/);
|
|
946
990
|
const command = line[0];
|
|
947
991
|
if (!command) continue;
|
|
948
992
|
const commandFn = COMMANDS[command];
|
package/dist/index.cjs
CHANGED
|
@@ -130,10 +130,60 @@ const NativeDatabase = binding.Database;
|
|
|
130
130
|
const NativeIterator = binding.Iterator;
|
|
131
131
|
const NativeTransaction = binding.Transaction;
|
|
132
132
|
const TransactionLog = binding.TransactionLog;
|
|
133
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Returns the status of every database currently held by the native registry.
|
|
135
|
+
* Walks every registered descriptor, so it samples live state rather than
|
|
136
|
+
* reading a stored property.
|
|
137
|
+
*/
|
|
138
|
+
const getRegistryStatus = binding.registryStatus;
|
|
139
|
+
/**
|
|
140
|
+
* @deprecated Use {@link getRegistryStatus} instead. This alias will be removed
|
|
141
|
+
* in v3.0.0.
|
|
142
|
+
*/
|
|
143
|
+
const registryStatus = getRegistryStatus;
|
|
144
|
+
/**
|
|
145
|
+
* Flushes every open database and waits for outstanding compactions, then
|
|
146
|
+
* releases the registry.
|
|
147
|
+
*
|
|
148
|
+
* **Throws** the first failure it encounters: a close whose native teardown
|
|
149
|
+
* failed (that descriptor stays quarantined — call `shutdown()` again to retry
|
|
150
|
+
* it), or a `lifecycleWaitSeconds` timeout waiting on a concurrent
|
|
151
|
+
* open/destroy/shutdown. A quarantining close also emits
|
|
152
|
+
* `database:closeFailed`; a timeout has no event, so the throw is its only
|
|
153
|
+
* signal.
|
|
154
|
+
*
|
|
155
|
+
* A `process.on('exit')` listener must therefore wrap it: an exception thrown
|
|
156
|
+
* from an `exit` listener skips every `exit` listener registered after it (and
|
|
157
|
+
* sets exit code 1 unless an `uncaughtException` handler is installed).
|
|
158
|
+
*
|
|
159
|
+
* ```typescript
|
|
160
|
+
* process.on('exit', () => {
|
|
161
|
+
* try {
|
|
162
|
+
* shutdown();
|
|
163
|
+
* } catch (error) {
|
|
164
|
+
* console.error('rocksdb-js shutdown failed', error);
|
|
165
|
+
* }
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
134
169
|
const shutdown = binding.shutdown;
|
|
135
170
|
const currentThreadId = binding.currentThreadId;
|
|
136
171
|
/**
|
|
172
|
+
* Reads the process-wide steady clock: `std::chrono::steady_clock` as
|
|
173
|
+
* fractional milliseconds from an unspecified origin that is fixed for the life
|
|
174
|
+
* of the process. Every sample in the process — main thread and every
|
|
175
|
+
* `worker_threads` worker, whenever started or restarted, on Node, Bun and Deno
|
|
176
|
+
* — is in one domain, and the difference between two samples is real elapsed
|
|
177
|
+
* time unaffected by wall-clock steps. Non-decreasing but not unique (two
|
|
178
|
+
* samples may be equal; treat `==` as "not later"). Windows QPC samples from
|
|
179
|
+
* different threads within one native tick also have ambiguous ordering.
|
|
180
|
+
* Clock readings alone do not prove causality. Not comparable with
|
|
181
|
+
* `Date.now()`, `db.getMonotonicTimestamp()` or transaction timestamps, and not
|
|
182
|
+
* meaningful across processes or restarts. Time spent in host suspend is
|
|
183
|
+
* platform-defined. No open database is needed.
|
|
184
|
+
*/
|
|
185
|
+
const steadyClockNow = binding.steadyClockNow;
|
|
186
|
+
/**
|
|
137
187
|
* Advises the kernel that the file-backed pages of every mapped transaction log
|
|
138
188
|
* are cold (Linux MADV_COLD), so they are reclaimed first under memory pressure
|
|
139
189
|
* without being freed — useful during replication catch-up, where a full read of
|
|
@@ -154,11 +204,32 @@ const coolTransactionLogs = binding.coolTransactionLogs;
|
|
|
154
204
|
*/
|
|
155
205
|
const transactionLogMapCount = binding.transactionLogMapCount;
|
|
156
206
|
/**
|
|
207
|
+
* Number of coordinated-retry wake callbacks registered on verification-table
|
|
208
|
+
* locks across the process. Internal — used by tests to verify that a park
|
|
209
|
+
* ending by timeout, env teardown, or close leaves no registration behind.
|
|
210
|
+
*/
|
|
211
|
+
const lockWakeCallbackCount = binding.lockWakeCallbackCount;
|
|
212
|
+
/**
|
|
157
213
|
* Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
|
|
158
214
|
* no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
|
|
159
215
|
* disarm. Used by the ERR_TRY_AGAIN retry regression test.
|
|
160
216
|
*/
|
|
161
217
|
const forceTryAgainForTesting = binding.forceTryAgainForTesting;
|
|
218
|
+
/**
|
|
219
|
+
* Test-only seam: makes the physical column-family drop report failure.
|
|
220
|
+
* `0` = inert, `1` = fail without dropping (the retry performs the drop),
|
|
221
|
+
* `2` = drop for real and then report failure (the retry resolves as
|
|
222
|
+
* "already dropped"). Process-global, like `forceTryAgainForTesting`.
|
|
223
|
+
*/
|
|
224
|
+
const forceDropFailureForTesting = binding.forceDropFailureForTesting;
|
|
225
|
+
/** Parks one selected transaction write after its retirement precheck. */
|
|
226
|
+
const setTransactionStagingDelayForTesting = binding.setTransactionStagingDelayForTesting;
|
|
227
|
+
/** Reports whether the selected transaction write is parked. */
|
|
228
|
+
const isTransactionStagingDelayedForTesting = binding.isTransactionStagingDelayedForTesting;
|
|
229
|
+
/** Reports whether a transaction is parked at the native commit seam. */
|
|
230
|
+
const isTransactionCommitExecuteDelayedForTesting = binding.isTransactionCommitExecuteDelayedForTesting;
|
|
231
|
+
const setTransactionCommitAdmissionDelayForTesting = binding.setTransactionCommitAdmissionDelayForTesting;
|
|
232
|
+
const isTransactionCommitAdmissionDelayedForTesting = binding.isTransactionCommitAdmissionDelayedForTesting;
|
|
162
233
|
/** Delays one selected watchdog join in the concurrent-shutdown regression test. */
|
|
163
234
|
const setWriteBufferManagerJoinDelayForTesting = binding.setWriteBufferManagerJoinDelayForTesting;
|
|
164
235
|
/**
|
|
@@ -1676,6 +1747,7 @@ var Store = class {
|
|
|
1676
1747
|
/**
|
|
1677
1748
|
* The target maximum size of a transaction log before rotation. Transactions
|
|
1678
1749
|
* are never split across files, so one transaction may exceed this target.
|
|
1750
|
+
* Defaults to 16 MiB. `0` (formerly unlimited) is rejected at open.
|
|
1679
1751
|
*/
|
|
1680
1752
|
transactionLogMaxSize;
|
|
1681
1753
|
/**
|
|
@@ -1690,6 +1762,11 @@ var Store = class {
|
|
|
1690
1762
|
*/
|
|
1691
1763
|
transactionLogsPath;
|
|
1692
1764
|
/**
|
|
1765
|
+
* The transaction log whose batch keys this process originates; seeds the
|
|
1766
|
+
* monotonic timestamp floor at open. See `StoreOptions.timestampFloorLog`.
|
|
1767
|
+
*/
|
|
1768
|
+
timestampFloorLog;
|
|
1769
|
+
/**
|
|
1693
1770
|
* Whether this store's column family participates in the VerificationTable.
|
|
1694
1771
|
*/
|
|
1695
1772
|
verificationTable;
|
|
@@ -1748,6 +1825,7 @@ var Store = class {
|
|
|
1748
1825
|
this.transactionLogMaxSize = options?.transactionLogMaxSize;
|
|
1749
1826
|
this.transactionLogRetention = options?.transactionLogRetention;
|
|
1750
1827
|
this.transactionLogsPath = options?.transactionLogsPath;
|
|
1828
|
+
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1751
1829
|
this.verificationTable = options?.verificationTable;
|
|
1752
1830
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1753
1831
|
this.writeKey = writeKey;
|
|
@@ -2112,6 +2190,7 @@ var Store = class {
|
|
|
2112
2190
|
transactionLogMaxSize: this.transactionLogMaxSize,
|
|
2113
2191
|
transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
|
|
2114
2192
|
transactionLogsPath: this.transactionLogsPath,
|
|
2193
|
+
timestampFloorLog: this.timestampFloorLog,
|
|
2115
2194
|
verificationTable: this.verificationTable,
|
|
2116
2195
|
writeBufferSize: this.writeBufferSize
|
|
2117
2196
|
});
|
|
@@ -2119,12 +2198,12 @@ var Store = class {
|
|
|
2119
2198
|
return false;
|
|
2120
2199
|
}
|
|
2121
2200
|
putSync(context, key, value, options) {
|
|
2122
|
-
if (!this.db.opened) throw new Error("Database not open");
|
|
2201
|
+
if (!this.db.opened) throw new Error(this.db.closing ? "Database is closing" : "Database not open");
|
|
2123
2202
|
const valueBuffer = this.encodeValue(value);
|
|
2124
2203
|
context.putSync(this.encodeKey(key), valueBuffer, this.getTxnId(options));
|
|
2125
2204
|
}
|
|
2126
2205
|
removeSync(context, key, options) {
|
|
2127
|
-
if (!this.db.opened) throw new Error("Database not open");
|
|
2206
|
+
if (!this.db.opened) throw new Error(this.db.closing ? "Database is closing" : "Database not open");
|
|
2128
2207
|
context.removeSync(this.encodeKey(key), this.getTxnId(options));
|
|
2129
2208
|
}
|
|
2130
2209
|
/**
|
|
@@ -2593,6 +2672,66 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2593
2672
|
return this.store.db.getCompression();
|
|
2594
2673
|
}
|
|
2595
2674
|
/**
|
|
2675
|
+
* Dynamically changes the compression algorithm (and optional level) for
|
|
2676
|
+
* this database's column family on an already-open database — no close, no
|
|
2677
|
+
* reopen, and it does not conflict with another handle that already has
|
|
2678
|
+
* this column family open (unlike an explicit open-time `compression`
|
|
2679
|
+
* mismatch). That other handle's live options change too, silently,
|
|
2680
|
+
* without notice; a later explicit reopen of it is compared against the
|
|
2681
|
+
* new value, so a stale request it still holds can then conflict. Backed
|
|
2682
|
+
* by RocksDB's `DB::SetOptions()`, which documents both `compression` and
|
|
2683
|
+
* `blob_compression_type` as mutable column family options.
|
|
2684
|
+
*
|
|
2685
|
+
* This governs only *newly written* files (the next flush and any future
|
|
2686
|
+
* compaction output) going forward; SST and blob files already on disk
|
|
2687
|
+
* keep their existing compression until they are rewritten by a later
|
|
2688
|
+
* compaction. `compact()`'s default options can skip already-bottommost
|
|
2689
|
+
* files; use `compact({ bottommost: true })` to rewrite them under the new
|
|
2690
|
+
* codec. This is the live-mutation counterpart to the open-time
|
|
2691
|
+
* `compression` option — see the README's Compression section for when to
|
|
2692
|
+
* use each.
|
|
2693
|
+
*
|
|
2694
|
+
* @example
|
|
2695
|
+
* ```typescript
|
|
2696
|
+
* const db = RocksDatabase.open('/path/to/db', { compression: 'none' });
|
|
2697
|
+
* db.setCompression({ algorithm: 'zstd', level: 19 });
|
|
2698
|
+
* // new writes are now compressed with zstd; existing SSTs are untouched
|
|
2699
|
+
* // until compacted.
|
|
2700
|
+
* ```
|
|
2701
|
+
*/
|
|
2702
|
+
setCompression(compression) {
|
|
2703
|
+
const { compression: algorithm, compressionLevel } = normalizeCompression(compression);
|
|
2704
|
+
if (algorithm === void 0) throw new TypeError("setCompression requires a compression algorithm");
|
|
2705
|
+
try {
|
|
2706
|
+
this.store.db.setCompression(algorithm, compressionLevel);
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
if (error?.appliedInMemoryOnly) this.syncStoreCompressionFromLive();
|
|
2709
|
+
throw error;
|
|
2710
|
+
}
|
|
2711
|
+
this.syncStoreCompressionFromLive();
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Resyncs the store's cached open-time compression option from the live
|
|
2715
|
+
* database state after `setCompression()`. Also clears
|
|
2716
|
+
* `compressionForAllColumnFamilies`: that flag means "apply this option to
|
|
2717
|
+
* every column family opened for this database", which no longer describes
|
|
2718
|
+
* a live change scoped to a single column family — leaving it set would
|
|
2719
|
+
* restamp every other family with this algorithm on the next
|
|
2720
|
+
* same-instance `close()` + `open()`.
|
|
2721
|
+
*
|
|
2722
|
+
* The flag clear is unconditional (a plain assignment, so it cannot itself
|
|
2723
|
+
* fail) and comes first: a concurrent close racing the live-state read
|
|
2724
|
+
* below throws "Database not open" from the getter, and that read failing
|
|
2725
|
+
* must not also skip the flag clear -- otherwise the same restamp this
|
|
2726
|
+
* method exists to prevent survives on the next open().
|
|
2727
|
+
*/
|
|
2728
|
+
syncStoreCompressionFromLive() {
|
|
2729
|
+
this.store.compressionForAllColumnFamilies = false;
|
|
2730
|
+
try {
|
|
2731
|
+
this.store.compression = this.compression;
|
|
2732
|
+
} catch {}
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2596
2735
|
* The informational-log settings currently in effect for this database,
|
|
2597
2736
|
* read live from RocksDB, as `{ maxLogFileSize, infoLogLevel }`. These are
|
|
2598
2737
|
* database-wide settings (not per-column-family): `maxLogFileSize` bounds
|
|
@@ -2628,7 +2767,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2628
2767
|
* native binding use namespaced keys (e.g. `'transactionLog:warning'`).
|
|
2629
2768
|
*
|
|
2630
2769
|
* Listeners are not tied to any specific database — they fire for every
|
|
2631
|
-
* matching event emitted in this process.
|
|
2770
|
+
* matching event emitted in this process. Native lifecycle failures use
|
|
2771
|
+
* `'database:closeFailed'` with `(path, error)` string arguments. The event
|
|
2772
|
+
* reports both completed and incomplete teardowns; an incomplete close can
|
|
2773
|
+
* be retried by `shutdown()` or explicitly deleted by `destroy()`.
|
|
2632
2774
|
*
|
|
2633
2775
|
* @example
|
|
2634
2776
|
* ```typescript
|
|
@@ -2712,7 +2854,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2712
2854
|
this.store.db.catchUpWithPrimarySync();
|
|
2713
2855
|
}
|
|
2714
2856
|
destroy() {
|
|
2715
|
-
this.store.db.destroy();
|
|
2857
|
+
this.store.db.destroy(this.store.readOnly);
|
|
2716
2858
|
}
|
|
2717
2859
|
async drop() {
|
|
2718
2860
|
return new Promise((resolve, reject) => {
|
|
@@ -3578,8 +3720,8 @@ function frameFits(dataView, pos, dataEnd) {
|
|
|
3578
3720
|
* terminator, let a chain "end" anywhere in megabytes of padding. Against the written extent the
|
|
3579
3721
|
* end is a single offset, so a chain landing on it is ~1/2^32 to be coincidence.
|
|
3580
3722
|
*
|
|
3581
|
-
* The JS counterpart of `
|
|
3582
|
-
*
|
|
3723
|
+
* The JS counterpart of `findFramingResumeOffset()` in `transaction_log_recovery.cpp`, which the
|
|
3724
|
+
* open-time scan and the timestamp index use to keep walking past the same break.
|
|
3583
3725
|
*/
|
|
3584
3726
|
function findResyncPosition(dataView, from, dataEnd) {
|
|
3585
3727
|
const lastStart = dataEnd - TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
@@ -3593,18 +3735,11 @@ function findResyncPosition(dataView, from, dataEnd) {
|
|
|
3593
3735
|
}
|
|
3594
3736
|
}
|
|
3595
3737
|
/**
|
|
3596
|
-
* Builds the report for a broken frame, with the offset iteration should continue from.
|
|
3597
|
-
* the written extent here rather than per frame: `getLogFileSize` crosses into native and takes
|
|
3598
|
-
* the store mutex, which a healthy read must never pay.
|
|
3738
|
+
* Builds the report for a broken frame, with the offset iteration should continue from.
|
|
3599
3739
|
*/
|
|
3600
3740
|
function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
|
|
3601
3741
|
let dataEnd = limit;
|
|
3602
|
-
if (readUncommitted)
|
|
3603
|
-
const writtenExtent = transactionLog.getLogFileSize(logBuffer.logId);
|
|
3604
|
-
dataEnd = writtenExtent > 0 ? Math.min(logBuffer.length, writtenExtent) : 0;
|
|
3605
|
-
} catch {
|
|
3606
|
-
dataEnd = 0;
|
|
3607
|
-
}
|
|
3742
|
+
if (readUncommitted) dataEnd = Math.min(logBuffer.length, logBuffer.size ?? readableExtent(logBuffer));
|
|
3608
3743
|
const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
|
|
3609
3744
|
return {
|
|
3610
3745
|
error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
|
|
@@ -3634,7 +3769,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3634
3769
|
let logId = latestLogId;
|
|
3635
3770
|
let position = 0;
|
|
3636
3771
|
let dataView;
|
|
3637
|
-
let logBuffer = this._currentLogBuffer;
|
|
3772
|
+
let logBuffer = this._currentLogBuffer?.deref();
|
|
3638
3773
|
let foundExactStart = false;
|
|
3639
3774
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3640
3775
|
position = size;
|
|
@@ -3652,7 +3787,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3652
3787
|
}
|
|
3653
3788
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3654
3789
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3655
|
-
if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
|
|
3790
|
+
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
3656
3791
|
if (logBuffer === void 0) {
|
|
3657
3792
|
logBuffer = Buffer.alloc(0);
|
|
3658
3793
|
logBuffer.logId = 0;
|
|
@@ -3662,9 +3797,11 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3662
3797
|
}
|
|
3663
3798
|
}
|
|
3664
3799
|
dataView = logBuffer.dataView;
|
|
3665
|
-
if (latestLogId
|
|
3666
|
-
|
|
3667
|
-
|
|
3800
|
+
if (latestLogId < logId) size = 0;
|
|
3801
|
+
else if (latestLogId > logId) {
|
|
3802
|
+
const cachedSize = logBuffer.size;
|
|
3803
|
+
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3804
|
+
else size = cachedSize;
|
|
3668
3805
|
}
|
|
3669
3806
|
return {
|
|
3670
3807
|
[Symbol.iterator]() {
|
|
@@ -3674,15 +3811,15 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3674
3811
|
let timestamp;
|
|
3675
3812
|
if (position >= size) {
|
|
3676
3813
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3677
|
-
size = latestSize;
|
|
3814
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3678
3815
|
if (latestLogId > logBuffer.logId) {
|
|
3679
|
-
size = logBuffer.size ?? (logBuffer.size =
|
|
3816
|
+
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3680
3817
|
if (position >= size) {
|
|
3681
|
-
const nextLogBuffer =
|
|
3818
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3682
3819
|
if (nextLogBuffer) {
|
|
3683
3820
|
dataView = nextLogBuffer.dataView;
|
|
3684
3821
|
logBuffer = nextLogBuffer;
|
|
3685
|
-
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size =
|
|
3822
|
+
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3686
3823
|
else size = latestSize;
|
|
3687
3824
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3688
3825
|
}
|
|
@@ -3733,19 +3870,25 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3733
3870
|
};
|
|
3734
3871
|
if (position >= size) {
|
|
3735
3872
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3736
|
-
size = latestSize;
|
|
3873
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3737
3874
|
if (latestLogId > logBuffer.logId) {
|
|
3738
|
-
|
|
3875
|
+
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
3876
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3877
|
+
if (position < size) continue;
|
|
3878
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3739
3879
|
if (!nextLogBuffer) return {
|
|
3740
3880
|
done: true,
|
|
3741
3881
|
value: void 0
|
|
3742
3882
|
};
|
|
3743
3883
|
logBuffer = nextLogBuffer;
|
|
3744
3884
|
dataView = logBuffer.dataView;
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
if (
|
|
3885
|
+
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
3886
|
+
else {
|
|
3887
|
+
const cachedSize = logBuffer.size;
|
|
3888
|
+
if (cachedSize === void 0) {
|
|
3889
|
+
size = readableExtent(logBuffer);
|
|
3890
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3891
|
+
} else size = cachedSize;
|
|
3749
3892
|
}
|
|
3750
3893
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3751
3894
|
}
|
|
@@ -3758,6 +3901,35 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3758
3901
|
}
|
|
3759
3902
|
};
|
|
3760
3903
|
} });
|
|
3904
|
+
/**
|
|
3905
|
+
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3906
|
+
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
3907
|
+
*
|
|
3908
|
+
* The jump target is `_nextLogId()`, the successor over the store's registered segments — *not*
|
|
3909
|
+
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3910
|
+
* and so lands *past* a survivor sitting between two holes.
|
|
3911
|
+
*/
|
|
3912
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
3913
|
+
let candidateLogId = fromLogId + 1;
|
|
3914
|
+
while (candidateLogId <= latestLogId) {
|
|
3915
|
+
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3916
|
+
if (logBuffer) {
|
|
3917
|
+
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3918
|
+
} else if (transactionLog.getLogFileSize(candidateLogId) > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
|
|
3919
|
+
candidateLogId = transactionLog._nextLogId(candidateLogId);
|
|
3920
|
+
if (candidateLogId === 0) return;
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* The bound for reading `logBuffer`. Native advances this append-owned extent only after a
|
|
3925
|
+
* successful write. It remains attached to the mapping after purge forgets the file object, so
|
|
3926
|
+
* readers retain committed history without interpreting a partial failed append as another frame.
|
|
3927
|
+
*/
|
|
3928
|
+
function readableExtent(logBuffer) {
|
|
3929
|
+
const extent = logBuffer.readableExtent;
|
|
3930
|
+
if (extent === void 0) throw new Error(`Transaction log buffer ${logBuffer.logId} has no readableExtent`);
|
|
3931
|
+
return Math.min(logBuffer.length, extent);
|
|
3932
|
+
}
|
|
3761
3933
|
function getLogMemoryMap(transactionLog, logId) {
|
|
3762
3934
|
if (logId <= 0) return;
|
|
3763
3935
|
let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
|
|
@@ -3803,7 +3975,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3803
3975
|
//#region src/index.ts
|
|
3804
3976
|
const versions = {
|
|
3805
3977
|
rocksdb: version,
|
|
3806
|
-
"rocksdb-js": "2.
|
|
3978
|
+
"rocksdb-js": "2.11.0"
|
|
3807
3979
|
};
|
|
3808
3980
|
|
|
3809
3981
|
//#endregion
|
|
@@ -3821,11 +3993,13 @@ exports.constants = constants;
|
|
|
3821
3993
|
exports.coolTransactionLogs = coolTransactionLogs;
|
|
3822
3994
|
exports.currentThreadId = currentThreadId;
|
|
3823
3995
|
exports.fileLockRelease = fileLockRelease;
|
|
3996
|
+
exports.getRegistryStatus = getRegistryStatus;
|
|
3824
3997
|
exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
|
|
3825
3998
|
exports.parseTransactionLog = parseTransactionLog;
|
|
3826
3999
|
exports.registryStatus = registryStatus;
|
|
3827
4000
|
exports.shutdown = shutdown;
|
|
3828
4001
|
exports.stats = stats;
|
|
4002
|
+
exports.steadyClockNow = steadyClockNow;
|
|
3829
4003
|
exports.supportedCompression = supportedCompression;
|
|
3830
4004
|
exports.tryFileLock = tryFileLock;
|
|
3831
4005
|
exports.validateTransactionLogStore = validateTransactionLogStore;
|