diff --git a/resources/RecordEncoder.ts b/resources/RecordEncoder.ts index ac4a7f41d..e53be5860 100644 --- a/resources/RecordEncoder.ts +++ b/resources/RecordEncoder.ts @@ -21,6 +21,8 @@ import { blobsWereEncoded, decodeFromDatabase, deleteBlobsInObject, encodeBlobsW import { recordAction } from './analytics/write.ts'; import { RocksDatabase } from '@harperfast/rocksdb-js'; import { when } from '../utility/when.ts'; +import { CONFIG_PARAMS } from '../utility/hdbTerms.ts'; +import * as envMngr from '../utility/environment/environmentManager.js'; export type Entry = { key: any; value: any; @@ -485,16 +487,18 @@ export function handleLocalTimeForGets(store, rootStore) { return store; } const trackedTxns: WeakRef[] = []; -setInterval(() => { +const configValue = envMngr.get(CONFIG_PARAMS.STORAGE_MAXREADTRANSACTIONOPENTIME) ?? 300000; +let READ_TXN_TIMEOUT_TICKS = Math.round(configValue / 15000); +export function checkReadTxnTimeouts() { for (let i = 0; i < trackedTxns.length; i++) { const txn = trackedTxns[i].deref(); if (!txn || txn.isDone || txn.isCommitted) trackedTxns.splice(i--, 1); else if (txn.notCurrent) { if (txn.openTimer) { if (txn.openTimer > 3) { - if (txn.openTimer > 60) { + if (txn.openTimer > READ_TXN_TIMEOUT_TICKS) { harperLogger.error( - 'Read transaction detected that has been open too long (over 15 minutes), ending transaction', + `Read transaction detected that has been open too long (over ${Math.round(READ_TXN_TIMEOUT_TICKS * 15)} seconds), ending transaction`, txn ); txn.done(); @@ -508,7 +512,12 @@ setInterval(() => { } else txn.openTimer = 1; } } -}, 15000).unref(); +} +setInterval(checkReadTxnTimeouts, 15000).unref(); +export function setReadTxnExpiration(ms: number) { + READ_TXN_TIMEOUT_TICKS = Math.round(ms / 15000); +} + export function recordUpdater(store, tableId, auditStore) { return function ( id, diff --git a/unitTests/resources/txn-tracking.test.js b/unitTests/resources/txn-tracking.test.js index b8b05a00d..fd96ce075 100644 --- a/unitTests/resources/txn-tracking.test.js +++ b/unitTests/resources/txn-tracking.test.js @@ -3,6 +3,7 @@ const assert = require('assert'); const { setupTestDBPath } = require('../testUtils'); const { setTxnExpiration } = require('#src/resources/DatabaseTransaction'); const { setTxnExpiration: setLMDBTxnExpiration } = require('#src/resources/LMDBTransaction'); +const { setReadTxnExpiration, checkReadTxnTimeouts } = require('#src/resources/RecordEncoder'); const { setMainIsWorker } = require('#js/server/threads/manageThreads'); const { table } = require('#src/resources/databases'); const { setTimeout: delay } = require('node:timers/promises'); @@ -54,3 +55,57 @@ describe('Txn Expiration', () => { setTxnExpiration(30000); }); }); + +describe('Read Txn Expiration', () => { + let SlowReadResource; + before(async function () { + setupTestDBPath(); + setMainIsWorker(true); + let BasicTable = table({ + table: 'ReadTxnTable', + database: 'test', + attributes: [{ name: 'id', isPrimaryKey: true }, { name: 'name' }], + }); + SlowReadResource = class extends BasicTable { + async get(query) { + const result = super.get(query); + await delay(50); + return result; + } + }; + }); + + it('Read txn will be ended after timeout', async function () { + await SlowReadResource.put(1, { name: 'one' }); + + // set timeout to minimum, 15s = 1 tick, openTimer > 1 means txn is expired + setReadTxnExpiration(15000); + + const readPromise = SlowReadResource.get(1); + await delay(20); + + // simulate timer ticks + checkReadTxnTimeouts(); + checkReadTxnTimeouts(); + + await readPromise; + }); + + it('Read txn below threshold is not expired', async function () { + setReadTxnExpiration(60000); + + await SlowReadResource.put(2, { name: 'two' }); + const readPromise = SlowReadResource.get(2); + await delay(20); + + // only 2 ticks + checkReadTxnTimeouts(); + + const result = await readPromise; + assert.equal(result.name, 'two'); + }); + + after(function () { + setReadTxnExpiration(300000); + }); +}); diff --git a/utility/hdbTerms.ts b/utility/hdbTerms.ts index 694d56a21..c43dec384 100644 --- a/utility/hdbTerms.ts +++ b/utility/hdbTerms.ts @@ -560,6 +560,7 @@ export const CONFIG_PARAMS = { STORAGE_ENCRYPTION: 'storage_encryption', STORAGE_MAXTRANSACTIONQUEUETIME: 'storage_maxTransactionQueueTime', STORAGE_MAXTRANSACTIONOPENTIME: 'storage_maxTransactionOpenTime', + STORAGE_MAXREADTRANSACTIONOPENTIME: 'storage_maxReadTransactionOpenTime', STORAGE_DEBUGLONGTRANSACTIONS: 'storage_debugLongTransactions', STORAGE_PATH: 'storage_path', STORAGE_BLOBPATHS: 'storage_blobPaths',