diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/sp-wallet.js | 41 |
1 files changed, 29 insertions, 12 deletions
diff --git a/src/features/sp-wallet.js b/src/features/sp-wallet.js index 116f2e0..2ac3e70 100644 --- a/src/features/sp-wallet.js +++ b/src/features/sp-wallet.js @@ -103,7 +103,19 @@ export function installSpWallet(wallet) { const id = ++this._spReqId; this._spPending.set(id, { res, rej }); this._spWorker.postMessage({ id, op, ...data }); - setTimeout(() => { if (this._spPending.has(id)) { this._spPending.delete(id); rej(new Error('sp worker timeout')); } }, 30000); + setTimeout(() => { + if (!this._spPending.has(id)) return; + // Timed out. TERMINATE the worker rather than let it keep grinding the + // orphaned request in the background — otherwise the caller's inline + // fallback runs the SAME work on the main thread and both threads peg. + // _ensureSpWorker() respawns a fresh worker (and re-sends keys) on the + // next call, so this only costs us the current chunk. With the tweak + // budget above, calls stay sub-second and this should never fire. + try { this._spWorker && this._spWorker.terminate(); } catch {} + this._spWorker = null; + for (const p of this._spPending.values()) p.rej(new Error('sp worker timeout')); + this._spPending.clear(); + }, 30000); }); }, @@ -222,17 +234,22 @@ export function installSpWallet(wallet) { const have = new Set(this.spUtxos.map((u) => `${u.txid}:${u.vout}`)); const found = []; const blocks = res.blocks || []; - // Process in chunks. An imported wallet's first catch-up can be the - // indexer's whole window (hundreds of thousands of tweaks, ~25MB) — - // sent as ONE worker message it blew the 30s worker timeout, fell - // back to the inline loop (janking the UI for minutes while the - // worker kept grinding the orphaned request), and held the giant - // buffers live the whole time. Chunks keep each call sub-second, let - // the scan watermark advance as blocks complete (interruptions - // resume instead of restarting), and free memory as they go. - const CHUNK = 100; - for (let c = 0; c < blocks.length; c += CHUNK) { - const slice = blocks.slice(c, c + CHUNK); + // Process in chunks bounded by TWEAK count, not block count. The cost is + // one EC point-multiply per tweak, and mainnet blocks are wildly uneven + // (a few to 2500+ tweaks each). A fixed 100-block chunk could be ~25k + // tweaks — seconds of work that blew the 30s worker timeout, which then + // dropped the same chunk onto the main thread while the orphaned worker + // kept grinding it (both threads pegged). Budgeting by tweaks keeps every + // worker call sub-second regardless of block density; the watermark still + // advances per chunk so interruptions resume instead of restarting. + const TWEAK_BUDGET = 2000; + for (let c = 0; c < blocks.length; ) { + // grow the slice until it holds ~TWEAK_BUDGET tweaks (always ≥1 block, + // so a single dense block still goes as its own chunk). + let end = c, tw = 0; + do { tw += (blocks[end].tweaks || []).length; end++; } while (end < blocks.length && tw < TWEAK_BUDGET); + const slice = blocks.slice(c, end); + c = end; const hits = await this._spHitBlocks(slice); for (const block of slice) { if (!hits.has(block.height)) continue; |
