animate/webGl/my-threejs-test/node_modules/lmdb/read.js

834 lines
28 KiB
JavaScript
Raw Normal View History

2024-06-24 09:24:00 +00:00
import { RangeIterable } from './util/RangeIterable.js';
import { getAddress, Cursor, Txn, orderedBinary, lmdbError, getByBinary, setGlobalBuffer, prefetch, iterate, position as doPosition, resetTxn, getCurrentValue, getCurrentShared, getStringByBinary, globalBuffer, getSharedBuffer, startRead, setReadCallback } from './native.js';
import { saveKey } from './keys.js';
const IF_EXISTS = 3.542694326329068e-103;
const ITERATOR_DONE = { done: true, value: undefined };
const Uint8ArraySlice = Uint8Array.prototype.slice;
let getValueBytes = globalBuffer;
if (!getValueBytes.maxLength) {
getValueBytes.maxLength = getValueBytes.length;
getValueBytes.isGlobal = true;
Object.defineProperty(getValueBytes, 'length', { value: getValueBytes.length, writable: true, configurable: true });
}
const START_ADDRESS_POSITION = 4064;
const NEW_BUFFER_THRESHOLD = 0x8000;
const SOURCE_SYMBOL = Symbol.for('source');
export const UNMODIFIED = {};
let mmaps = [];
export function addReadMethods(LMDBStore, {
maxKeySize, env, keyBytes, keyBytesView, getLastVersion, getLastTxnId
}) {
let readTxn, readTxnRenewed, asSafeBuffer = false;
let renewId = 1;
let outstandingReads = 0;
Object.assign(LMDBStore.prototype, {
getString(id, options) {
let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
let string = getStringByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), txn.address || 0);
if (typeof string === 'number') { // indicates the buffer wasn't large enough
this._allocateGetBuffer(string);
// and then try again
string = getStringByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), txn.address || 0);
}
if (string)
this.lastSize = string.length;
return string;
},
getBinaryFast(id, options) {
let rc;
let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
rc = this.lastSize = getByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), (options && options.ifNotTxnId) || 0, txn.address || 0);
if (rc < 0) {
if (rc == -30798) // MDB_NOTFOUND
return; // undefined
if (rc == -30004) // txn id matched
return UNMODIFIED;
if (rc == -30781 /*MDB_BAD_VALSIZE*/ && this.writeKey(id, keyBytes, 0) == 0)
throw new Error(id === undefined ?
'A key is required for get, but is undefined' :
'Zero length key is not allowed in LMDB');
if (rc == -30000) // int32 overflow, read uint32
rc = this.lastSize = keyBytesView.getUint32(0, true);
else if (rc == -30001) {// shared buffer
this.lastSize = keyBytesView.getUint32(0, true);
let bufferId = keyBytesView.getUint32(4, true);
return getMMapBuffer(bufferId, this.lastSize);
} else
throw lmdbError(rc);
}
let compression = this.compression;
let bytes = compression ? compression.getValueBytes : getValueBytes;
if (rc > bytes.maxLength) {
// this means the target buffer wasn't big enough, so the get failed to copy all the data from the database, need to either grow or use special buffer
return this._returnLargeBuffer(
() => getByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), 0, txn.address || 0));
}
bytes.length = this.lastSize;
return bytes;
},
getBFAsync(id, options, callback) {
let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
txn.refCount = (txn.refCount || 0) + 1;
outstandingReads++;
let address = recordReadInstruction(txn.address, this.db.dbi, id, this.writeKey, maxKeySize, ( rc, bufferId, offset, size ) => {
if (rc && rc !== 1)
callback(lmdbError(rc));
outstandingReads--;
let buffer = mmaps[bufferId];
if (!buffer) {
buffer = mmaps[bufferId] = getSharedBuffer(bufferId, env.address);
}
//console.log({bufferId, offset, size})
if (buffer.isSharedMap) {
// using LMDB shared memory
// TODO: We may want explicit support for clearing aborting the transaction on the next event turn,
// but for now we are relying on the GC to cleanup transaction for larger blocks of memory
let bytes = new Uint8Array(buffer, offset, size);
bytes.txn = txn;
callback(bytes, 0, size);
} else {
// using copied memory
txn.done(); // decrement and possibly abort
callback(buffer, offset, size);
}
});
if (address) {
startRead(address, () => {
resolveReads();
});
}
},
getAsync(id, options, callback) {
let promise;
if (!callback)
promise = new Promise(resolve => callback = resolve);
this.getBFAsync(id, options, (buffer, offset, size) => {
if (this.useVersions) {
// TODO: And get the version
offset += 8;
size -= 8;
}
let bytes = new Uint8Array(buffer, offset, size);
let value;
if (this.decoder) {
// the decoder potentially uses the data from the buffer in the future and needs a stable buffer
value = bytes && this.decoder.decode(bytes);
} else if (this.encoding == 'binary') {
value = bytes;
} else {
value = Buffer.prototype.utf8Slice.call(bytes, 0, size);
if (this.encoding == 'json' && value)
value = JSON.parse(value);
}
callback(value);
});
return promise;
},
retain(data, options) {
if (!data)
return
let source = data[SOURCE_SYMBOL];
let buffer = source ? source.bytes : data;
if (!buffer.isGlobal && !env.writeTxn) {
let txn = options?.transaction || (readTxnRenewed ? readTxn : renewReadTxn(this));
buffer.txn = txn;
txn.refCount = (txn.refCount || 0) + 1;
return data;
} else {
buffer = Uint8ArraySlice.call(buffer, 0, this.lastSize);
if (source) {
source.bytes = buffer;
return data;
} else
return buffer;
}
},
_returnLargeBuffer(getFast) {
let bytes;
let compression = this.compression;
if (asSafeBuffer && this.lastSize > NEW_BUFFER_THRESHOLD) {
// used by getBinary to indicate it should create a dedicated buffer to receive this
let bytesToRestore
try {
if (compression) {
bytesToRestore = compression.getValueBytes;
let dictionary = compression.dictionary || [];
let dictLength = (dictionary.length >> 3) << 3;// make sure it is word-aligned
bytes = makeReusableBuffer(this.lastSize);
compression.setBuffer(bytes.buffer, bytes.byteOffset, this.lastSize, dictionary, dictLength);
compression.getValueBytes = bytes;
} else {
bytesToRestore = getValueBytes;
setGlobalBuffer(bytes = getValueBytes = makeReusableBuffer(this.lastSize));
}
getFast();
} finally {
if (compression) {
let dictLength = (compression.dictionary.length >> 3) << 3;
compression.setBuffer(bytesToRestore.buffer, bytesToRestore.byteOffset, bytesToRestore.maxLength, compression.dictionary, dictLength);
compression.getValueBytes = bytesToRestore;
} else {
setGlobalBuffer(bytesToRestore);
getValueBytes = bytesToRestore;
}
}
return bytes;
}
// grow our shared/static buffer to accomodate the size of the data
bytes = this._allocateGetBuffer(this.lastSize);
// and try again
getFast();
bytes.length = this.lastSize;
return bytes;
},
_allocateGetBuffer(lastSize) {
let newLength = Math.min(Math.max(lastSize * 2, 0x1000), 0xfffffff8);
let bytes;
if (this.compression) {
let dictionary = this.compression.dictionary || Buffer.allocUnsafeSlow(0);
let dictLength = (dictionary.length >> 3) << 3;// make sure it is word-aligned
bytes = Buffer.allocUnsafeSlow(newLength + dictLength);
bytes.set(dictionary) // copy dictionary into start
// the section after the dictionary is the target area for get values
bytes = bytes.subarray(dictLength);
this.compression.setBuffer(bytes.buffer, bytes.byteOffset, newLength, dictionary, dictLength);
bytes.maxLength = newLength;
Object.defineProperty(bytes, 'length', { value: newLength, writable: true, configurable: true });
this.compression.getValueBytes = bytes;
} else {
bytes = makeReusableBuffer(newLength);
setGlobalBuffer(getValueBytes = bytes);
}
bytes.isGlobal = true;
return bytes;
},
getBinary(id, options) {
try {
asSafeBuffer = true;
let fastBuffer = this.getBinaryFast(id, options);
return fastBuffer && (fastBuffer.isGlobal ? Uint8ArraySlice.call(fastBuffer, 0, this.lastSize) : fastBuffer);
} finally {
asSafeBuffer = false;
}
},
getSharedBinary(id, options) {
let fastBuffer = this.getBinaryFast(id, options);
if (fastBuffer) {
if (fastBuffer.isGlobal || writeTxn)
return Uint8ArraySlice.call(fastBuffer, 0, this.lastSize)
fastBuffer.txn = (options && options.transaction);
options.transaction.refCount = (options.transaction.refCount || 0) + 1;
return fastBuffer;
}
},
get(id, options) {
if (this.decoderCopies) {
// the decoder copies any data, so we can use the fast binary retrieval that overwrites the same buffer space
let bytes = this.getBinaryFast(id, options);
return bytes && (bytes == UNMODIFIED ? UNMODIFIED : this.decoder.decode(bytes, options));
}
if (this.encoding == 'binary')
return this.getBinary(id, options);
if (this.decoder) {
// the decoder potentially uses the data from the buffer in the future and needs a stable buffer
let bytes = this.getBinary(id, options);
return bytes && (bytes == UNMODIFIED ? UNMODIFIED : this.decoder.decode(bytes));
}
let result = this.getString(id, options);
if (result) {
if (this.encoding == 'json')
return JSON.parse(result);
}
return result;
},
getEntry(id, options) {
let value = this.get(id, options);
if (value !== undefined) {
if (this.useVersions)
return {
value,
version: getLastVersion(),
//size: this.lastSize
};
else
return {
value,
//size: this.lastSize
};
}
},
resetReadTxn() {
resetReadTxn();
},
_commitReadTxn() {
if (readTxn) {
readTxn.isCommitted = true
readTxn.commit();
}
lastReadTxnRef = null;
readTxnRenewed = null;
readTxn = null;
},
ensureReadTxn() {
if (!env.writeTxn && !readTxnRenewed)
renewReadTxn(this);
},
doesExist(key, versionOrValue) {
if (versionOrValue == null) {
// undefined means the entry exists, null is used specifically to check for the entry *not* existing
return (this.getBinaryFast(key) === undefined) == (versionOrValue === null);
}
else if (this.useVersions) {
return this.getBinaryFast(key) !== undefined && (versionOrValue === IF_EXISTS || getLastVersion() === versionOrValue);
}
else {
if (versionOrValue && versionOrValue['\x10binary-data\x02'])
versionOrValue = versionOrValue['\x10binary-data\x02'];
else if (this.encoder)
versionOrValue = this.encoder.encode(versionOrValue);
if (typeof versionOrValue == 'string')
versionOrValue = Buffer.from(versionOrValue);
return this.getValuesCount(key, { start: versionOrValue, exactMatch: true}) > 0;
}
},
getValues(key, options) {
let defaultOptions = {
key,
valuesForKey: true
};
if (options && options.snapshot === false)
throw new Error('Can not disable snapshots for getValues');
return this.getRange(options ? Object.assign(defaultOptions, options) : defaultOptions);
},
getKeys(options) {
if (!options)
options = {};
options.values = false;
return this.getRange(options);
},
getCount(options) {
if (!options)
options = {};
options.onlyCount = true;
return this.getRange(options).iterate();
},
getKeysCount(options) {
if (!options)
options = {};
options.onlyCount = true;
options.values = false;
return this.getRange(options).iterate();
},
getValuesCount(key, options) {
if (!options)
options = {};
options.key = key;
options.valuesForKey = true;
options.onlyCount = true;
return this.getRange(options).iterate();
},
getRange(options) {
let iterable = new RangeIterable();
if (!options)
options = {};
let includeValues = options.values !== false;
let includeVersions = options.versions;
let valuesForKey = options.valuesForKey;
let limit = options.limit;
let db = this.db;
let snapshot = options.snapshot;
let compression = this.compression;
iterable.iterate = () => {
let currentKey = valuesForKey ? options.key : options.start;
const reverse = options.reverse;
let count = 0;
let cursor, cursorRenewId, cursorAddress;
let txn;
let flags = (includeValues ? 0x100 : 0) | (reverse ? 0x400 : 0) |
(valuesForKey ? 0x800 : 0) | (options.exactMatch ? 0x4000 : 0) |
(options.inclusiveEnd ? 0x8000 : 0) |
(options.exclusiveStart ? 0x10000 : 0);
let store = this;
function resetCursor() {
try {
if (cursor)
finishCursor();
let txnAddress;
txn = options.transaction
if (txn) {
if (txn.isDone) throw new Error('Can not iterate on range with transaction that is already' +
' done');
txnAddress = txn.address;
cursor = null;
} else {
let writeTxn = env.writeTxn;
if (writeTxn)
snapshot = false;
txn = env.writeTxn || options.transaction || (readTxnRenewed ? readTxn : renewReadTxn(store));
cursor = !writeTxn && db.availableCursor;
}
if (cursor) {
db.availableCursor = null;
flags |= 0x2000;
} else {
cursor = new Cursor(db, txnAddress || 0);
}
cursorAddress = cursor.address;
txn.refCount = (txn.refCount || 0) + 1; // track transaction so we always use the same one
if (snapshot === false) {
cursorRenewId = renewId; // use shared read transaction
txn.renewingRefCount = (txn.renewingRefCount || 0) + 1; // need to know how many are renewing cursors
}
} catch(error) {
if (cursor) {
try {
cursor.close();
} catch(error) { }
}
throw error;
}
}
resetCursor();
if (options.onlyCount) {
flags |= 0x1000;
let count = position(options.offset);
if (count < 0)
lmdbError(count);
finishCursor();
return count;
}
function position(offset) {
if (!env.address) {
throw new Error('Can not iterate on a closed database');
}
let keySize = currentKey === undefined ? 0 : store.writeKey(currentKey, keyBytes, 0);
let endAddress;
if (valuesForKey) {
if (options.start === undefined && options.end === undefined)
endAddress = 0;
else {
let startAddress;
if (store.encoder.writeKey) {
startAddress = saveKey(options.start, store.encoder.writeKey, iterable, maxKeySize);
keyBytesView.setFloat64(START_ADDRESS_POSITION, startAddress, true);
endAddress = saveKey(options.end, store.encoder.writeKey, iterable, maxKeySize);
} else if ((!options.start || options.start instanceof Uint8Array) && (!options.end || options.end instanceof Uint8Array)) {
startAddress = saveKey(options.start, orderedBinary.writeKey, iterable, maxKeySize);
keyBytesView.setFloat64(START_ADDRESS_POSITION, startAddress, true);
endAddress = saveKey(options.end, orderedBinary.writeKey, iterable, maxKeySize);
} else {
throw new Error('Only key-based encoding is supported for start/end values');
let encoded = store.encoder.encode(options.start);
let bufferAddress = encoded.buffer.address || (encoded.buffer.address = getAddress(encoded.buffer) - encoded.byteOffset);
startAddress = bufferAddress + encoded.byteOffset;
}
}
} else
endAddress = saveKey(options.end, store.writeKey, iterable, maxKeySize);
return doPosition(cursorAddress, flags, offset || 0, keySize, endAddress);
}
function finishCursor() {
if (txn.isDone)
return;
if (iterable.onDone)
iterable.onDone()
if (cursorRenewId)
txn.renewingRefCount--;
if (--txn.refCount <= 0 && txn.notCurrent) {
cursor.close();
txn.abort(); // this is no longer main read txn, abort it now that we are done
txn.isDone = true;
} else {
if (db.availableCursor || txn != readTxn) {
cursor.close();
} else { // try to reuse it
db.availableCursor = cursor;
db.cursorTxn = txn;
}
}
}
return {
next() {
let keySize, lastSize;
if (cursorRenewId && (cursorRenewId != renewId || txn.isDone)) {
resetCursor();
keySize = position(0);
}
if (count === 0) { // && includeValues) // on first entry, get current value if we need to
keySize = position(options.offset);
} else
keySize = iterate(cursorAddress);
if (keySize <= 0 ||
(count++ >= limit)) {
if (count < 0)
lmdbError(count);
finishCursor();
return ITERATOR_DONE;
}
if (!valuesForKey || snapshot === false) {
if (keySize > 20000) {
if (keySize > 0x1000000)
lmdbError(keySize - 0x100000000)
throw new Error('Invalid key size ' + keySize.toString(16))
}
currentKey = store.readKey(keyBytes, 32, keySize + 32);
}
if (includeValues) {
let value;
lastSize = keyBytesView.getUint32(0, true);
let bufferId = keyBytesView.getUint32(4, true);
let bytes;
if (bufferId) {
bytes = getMMapBuffer(bufferId, lastSize);
} else {
bytes = compression ? compression.getValueBytes : getValueBytes;
if (lastSize > bytes.maxLength) {
store.lastSize = lastSize;
asSafeBuffer = store.encoding == 'binary';
try {
bytes = store._returnLargeBuffer(() => getCurrentValue(cursorAddress));
} finally {
asSafeBuffer = false;
}
} else
bytes.length = lastSize;
}
if (store.decoder) {
value = store.decoder.decode(bytes, lastSize);
} else if (store.encoding == 'binary')
value = bytes.isGlobal ? Uint8ArraySlice.call(bytes, 0, lastSize) : bytes;
else {
value = bytes.toString('utf8', 0, lastSize);
if (store.encoding == 'json' && value)
value = JSON.parse(value);
}
if (includeVersions)
return {
value: {
key: currentKey,
value,
version: getLastVersion()
}
};
else if (valuesForKey)
return {
value
};
else
return {
value: {
key: currentKey,
value,
}
};
} else if (includeVersions) {
return {
value: {
key: currentKey,
version: getLastVersion()
}
};
} else {
return {
value: currentKey
};
}
},
return() {
finishCursor();
return ITERATOR_DONE;
},
throw() {
finishCursor();
return ITERATOR_DONE;
}
};
};
return iterable;
},
getMany(keys, callback) {
// this is an asynchronous get for multiple keys. It actually works by prefetching asynchronously,
// allowing a separate to absorb the potentially largest cost: hard page faults (and disk I/O).
// And then we just do standard sync gets (to deserialized data) to fulfil the callback/promise
// once the prefetch occurs
let promise = callback ? undefined : new Promise(resolve => callback = (error, results) => resolve(results));
this.prefetch(keys, () => {
let results = new Array(keys.length);
for (let i = 0, l = keys.length; i < l; i++) {
results[i] = get.call(this, keys[i]);
}
callback(null, results);
});
return promise;
},
getSharedBufferForGet(id, options) {
let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
this.lastSize = this.keyIsCompatibility ? txn.getBinaryShared(id) : this.db.get(this.writeKey(id, keyBytes, 0));
if (this.lastSize === -30798) { // not found code
return; //undefined
}
return this.lastSize;
this.lastSize = keyBytesView.getUint32(0, true);
let bufferIndex = keyBytesView.getUint32(12, true);
lastOffset = keyBytesView.getUint32(8, true);
let buffer = buffers[bufferIndex];
let startOffset;
if (!buffer || lastOffset < (startOffset = buffer.startOffset) || (lastOffset + this.lastSize > startOffset + 0x100000000)) {
if (buffer)
env.detachBuffer(buffer.buffer);
startOffset = (lastOffset >>> 16) * 0x10000;
console.log('make buffer for address', bufferIndex * 0x100000000 + startOffset);
buffer = buffers[bufferIndex] = Buffer.from(getBufferForAddress(bufferIndex * 0x100000000 + startOffset));
buffer.startOffset = startOffset;
}
lastOffset -= startOffset;
return buffer;
return buffer.slice(lastOffset, lastOffset + this.lastSize);/*Uint8ArraySlice.call(buffer, lastOffset, lastOffset + this.lastSize)*/
},
prefetch(keys, callback) {
if (!keys)
throw new Error('An array of keys must be provided');
if (!keys.length) {
if (callback) {
callback(null);
return;
} else
return Promise.resolve();
}
let buffers = [];
let startPosition;
let bufferHolder = {};
let lastBuffer;
for (let key of keys) {
let position
if (key && key.key !== undefined && key.value !== undefined) {
position = saveKey(key.value, this.writeKey, bufferHolder, maxKeySize, 0x80000000);
saveKey(key.key, this.writeKey, bufferHolder, maxKeySize);
} else {
position = saveKey(key, this.writeKey, bufferHolder, maxKeySize);
}
if (!startPosition)
startPosition = position;
if (bufferHolder.saveBuffer != lastBuffer) {
buffers.push(bufferHolder);
lastBuffer = bufferHolder.saveBuffer;
bufferHolder = { saveBuffer: lastBuffer };
}
}
saveKey(undefined, this.writeKey, bufferHolder, maxKeySize);
outstandingReads++;
prefetch(this.dbAddress, startPosition, (error) => {
outstandingReads--;
if (error)
console.error('Error with prefetch', buffers, bufferHolder); // partly exists to keep the buffers pinned in memory
else
callback(null);
});
if (!callback)
return new Promise(resolve => callback = resolve);
},
useReadTransaction() {
let txn = readTxnRenewed ? readTxn : renewReadTxn(this);
if (!txn.use) {
throw new Error('Can not use read transaction from a closed database');
}
txn.use();
return txn;
},
close(callback) {
this.status = 'closing';
let txnPromise;
if (this.isRoot) {
// if it is root, we need to abort and/or wait for transactions to finish
if (readTxn) readTxn.abort();
else readTxn = {};
readTxn.isDone = true;
Object.defineProperty(readTxn,'renew', {
value: () => {
throw new Error('Can not read from a closed database');
}, configurable: true
});
Object.defineProperty(readTxn,'use', {
value: () => {
throw new Error('Can not read from a closed database');
}, configurable: true
});
readTxnRenewed = null;
txnPromise = this._endWrites && this._endWrites();
}
const doClose = () => {
if (this.isRoot) {
if (outstandingReads > 0) {
return new Promise(resolve => setTimeout(() => resolve(doClose()), 1));
}
env.address = 0;
env.close();
} else
this.db.close();
this.status = 'closed';
if (callback)
callback();
}
if (txnPromise)
return txnPromise.then(doClose);
else {
doClose();
return Promise.resolve();
}
},
getStats() {
let txn = env.writeTxn || (readTxnRenewed ? readTxn : renewReadTxn(this));
let dbStats = this.db.stat();
dbStats.root = env.stat();
Object.assign(dbStats, env.info());
dbStats.free = env.freeStat();
return dbStats;
},
});
let get = LMDBStore.prototype.get;
let lastReadTxnRef;
function getMMapBuffer(bufferId, size) {
let buffer = mmaps[bufferId];
if (!buffer) {
buffer = mmaps[bufferId] = getSharedBuffer(bufferId, env.address);
}
let offset = keyBytesView.getUint32(8, true);
return new Uint8Array(buffer, offset, size);
}
function renewReadTxn(store) {
if (!env.address) {
throw new Error('Can not renew a transaction from a closed database');
}
if (!readTxn) {
let retries = 0;
let waitArray;
do {
try {
let lastReadTxn = lastReadTxnRef && lastReadTxnRef.deref();
readTxn = new Txn(env, 0x20000, lastReadTxn && !lastReadTxn.isDone && lastReadTxn);
if (readTxn.address == 0) {
readTxn = lastReadTxn;
if (readTxn.notCurrent)
readTxn.notCurrent = false;
}
break;
} catch (error) {
if (error.message.includes('temporarily')) {
if (!waitArray)
waitArray = new Int32Array(new SharedArrayBuffer(4), 0, 1);
Atomics.wait(waitArray, 0, 0, retries * 2);
} else
throw error;
}
} while (retries++ < 100);
}
// we actually don't renew here, we let the renew take place in the next
// lmdb native read/call so as to avoid an extra native call
readTxnRenewed = setTimeout(resetReadTxn, 0);
store.emit('begin-transaction');
return readTxn;
}
function resetReadTxn() {
renewId++;
if (readTxnRenewed) {
readTxnRenewed = null;
if (readTxn.refCount - (readTxn.renewingRefCount || 0) > 0) {
readTxn.notCurrent = true;
lastReadTxnRef = new WeakRef(readTxn);
readTxn = null;
} else if (readTxn.address && !readTxn.isDone) {
resetTxn(readTxn.address);
} else {
console.warn('Attempt to reset an invalid read txn', readTxn);
throw new Error('Attempt to reset an invalid read txn');
}
}
}
}
export function makeReusableBuffer(size) {
let bytes = typeof Buffer != 'undefined' ? Buffer.alloc(size) : new Uint8Array(size);
bytes.maxLength = size;
Object.defineProperty(bytes, 'length', { value: size, writable: true, configurable: true });
return bytes;
}
Txn.prototype.done = function() {
this.refCount--;
if (this.refCount === 0 && this.notCurrent) {
this.abort();
this.isDone = true;
}
}
Txn.prototype.use = function() {
this.refCount = (this.refCount || 0) + 1;
}
let readInstructions, readCallbacks = new Map(), uint32Instructions, instructionsDataView = { setFloat64() {}, setUint32() {} }, instructionsAddress;
let savePosition = 8000;
let DYNAMIC_KEY_BUFFER_SIZE = 8192;
function allocateInstructionsBuffer() {
readInstructions = typeof Buffer != 'undefined' ? Buffer.alloc(DYNAMIC_KEY_BUFFER_SIZE) : new Uint8Array(DYNAMIC_KEY_BUFFER_SIZE);
uint32Instructions = new Int32Array(readInstructions.buffer, 0, readInstructions.buffer.byteLength >> 2);
uint32Instructions[2] = 0xf0000000; // indicates a new read task must be started
instructionsAddress = readInstructions.buffer.address = getAddress(readInstructions.buffer);
readInstructions.dataView = instructionsDataView = new DataView(readInstructions.buffer, readInstructions.byteOffset, readInstructions.byteLength);
savePosition = 0;
}
export function recordReadInstruction(txnAddress, dbi, key, writeKey, maxKeySize, callback) {
if (savePosition > 7800) {
allocateInstructionsBuffer();
}
let start = savePosition;
let keyPosition = savePosition + 16;
try {
savePosition = key === undefined ? keyPosition :
writeKey(key, readInstructions, keyPosition);
} catch (error) {
if (error.name == 'RangeError') {
if (8180 - start < maxKeySize) {
allocateInstructionsBuffer(); // try again:
return recordReadInstruction(txnAddress, dbi, key, writeKey, maxKeySize, callback);
}
throw new Error('Key was too large, max key size is ' + maxKeySize);
} else
throw error;
}
let length = savePosition - keyPosition;
if (length > maxKeySize) {
savePosition = start;
throw new Error('Key of size ' + length + ' was too large, max key size is ' + maxKeySize);
}
uint32Instructions[(start >> 2) + 3] = length; // save the length
uint32Instructions[(start >> 2) + 2] = dbi;
savePosition = (savePosition + 12) & 0xfffffc;
instructionsDataView.setFloat64(start, txnAddress, true);
let callbackId = addReadCallback(() => {
let position = start >> 2;
let rc = thisInstructions[position];
callback(rc, thisInstructions[position + 1], thisInstructions[position + 2], thisInstructions[position + 3]);
});
let thisInstructions = uint32Instructions;
//if (start === 0)
return startRead(instructionsAddress + start, callbackId, {}, 'read');
//else
//nextRead(start);
}
let nextCallbackId = 0;
let addReadCallback = globalThis.__lmdb_read_callback;
if (!addReadCallback) {
addReadCallback = globalThis.__lmdb_read_callback = function(callback) {
let callbackId = nextCallbackId++;
readCallbacks.set(callbackId, callback);
return callbackId;
};
setReadCallback(function(callbackId) {
readCallbacks.get(callbackId)();
readCallbacks.delete(callbackId);
})
}