diff --git a/CHANGELOG.md b/CHANGELOG.md index b8848679..2dfc4bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fixed: Added retry delay to fix dropped transaction thrashing. + ## 3.8.6 (2025-10-13) - fixed: (QTUM) Explorer URLs diff --git a/src/common/utxobased/engine/UtxoEngineProcessor.ts b/src/common/utxobased/engine/UtxoEngineProcessor.ts index 555aa547..1c63f603 100644 --- a/src/common/utxobased/engine/UtxoEngineProcessor.ts +++ b/src/common/utxobased/engine/UtxoEngineProcessor.ts @@ -115,6 +115,17 @@ export function makeUtxoEngineProcessor( transactionUpdateCache: {} } + // Track pending timeouts so they can be cleared on stop + const pendingTimeouts: Set = new Set() + + // Clear all pending timeouts + const clearPendingTimeouts = (): void => { + for (const timeoutId of pendingTimeouts) { + clearTimeout(timeoutId) + } + pendingTimeouts.clear() + } + const clearTaskCache = (): void => { for (const key of Object.keys(taskCache.addressForTransactionsCache)) { removeItem(taskCache.addressForTransactionsCache, key) @@ -212,6 +223,7 @@ export function makeUtxoEngineProcessor( dataLayer, emitter, taskCache, + pendingTimeouts, updateProgressRatio, updateSeenTxCheckpoint, io, @@ -358,6 +370,7 @@ export function makeUtxoEngineProcessor( async stop(): Promise { serverStates.stop() clearTaskCache() + clearPendingTimeouts() running = false }, @@ -628,6 +641,7 @@ interface CommonParams { dataLayer: DataLayer emitter: EngineEmitter taskCache: TaskCache + pendingTimeouts: Set updateProgressRatio: () => void updateSeenTxCheckpoint: () => void io: EdgeIo @@ -1154,9 +1168,18 @@ async function* processCheckTransactionConfirmation( } catch (err) { console.error(err) common.log('error while processing transaction update:', err) - common.taskCache.transactionUpdateCache[txId] = { - processing: false - } + // Delay putting the transaction back into the cache to avoid thrashing + // on dropped transactions. 10 seconds is a reasonable compromise between + // thrashing and not waiting too long. + addPendingTimeout( + common, + () => { + common.taskCache.transactionUpdateCache[txId] = { + processing: false + } + }, + 10000 + ) return false } } @@ -1681,3 +1704,22 @@ const addToDataLayerUtxoCache = ( dataLayerUtxoCache[scriptPubkey] = dataLayerUtxos dataLayerUtxos.full = dataLayerUtxos.utxos.length >= requiredCount } + +/** + * This adds a pending timeout to the common.pendingTimeouts set. + * It will call the callback after the timeout. + * + * This utility is a safe way to add pending timeouts for the processor because + * it will clear the timeout when the processor stops. + */ +const addPendingTimeout = ( + common: CommonParams, + callback: () => void, + timeout: number +): void => { + const timeoutId = setTimeout(() => { + common.pendingTimeouts.delete(timeoutId) + callback() + }, timeout) + common.pendingTimeouts.add(timeoutId) +}