a8d453018226a38dc8bc7fce5891a2e064116029 braney Tue May 12 10:24:39 2026 -0700 quickLift: fix hgTracks OOM with multiple lifted bigBed-style tracks, refs #37536 In quickLiftGetIntervals (src/hg/lib/quickLift.c) the first chainList from chainLoadIdRangeHub was reassigned without being freed, leaking the chain heads and their full cBlock lists for every quickLifted track in the window. And the second-pass padding used maxGap * 2 with no upper bound, so a single long item in the source data file (gencodeV49 has genes up to ~800 kb) could pull in chains -- and their dense cBlock lists -- covering many megabases. Free the first chainList, and cap the padding at 1 Mb. On the failing mm10 chr4:148,273,516-148,627,317 session with the hg38 hub_190793 quickLift attached, the request went from OOM at 11+ GB RSS (truncated 6 kb response, 80+ s) to a complete 1.7 MB response in ~1.6 s at ~430 MB RSS. Co-Authored-By: Claude Opus 4.7 (1M context) diff --git src/hg/lib/quickLift.c src/hg/lib/quickLift.c index a74ba6adf10..e7045c9add4 100644 --- src/hg/lib/quickLift.c +++ src/hg/lib/quickLift.c @@ -70,41 +70,49 @@ { int gap = qStart - bb->start; if (gap > maxGapBefore) maxGapBefore = gap; } if (bb->end > qEnd) { int gap = bb->end - qEnd; if (gap > maxGapAfter) maxGapAfter = gap; } } bbList = slCat(thisInterval, bbList); } +// We are done with the chains we used to bound the data query; +// release them before loading the wider set for the lift map below. +// Without this, every quickLifted track leaked the cBlocks of every +// chain overlapping the window. +chainFreeList(&chainList); + // now we need to grab the links outside of our viewport so we can map long items // probably we could reuse the chains from above but for the moment this is easier // For the moment we use the same padding on both sides so we don't have to worry about strand if (maxGapBefore > maxGapAfter) maxGapAfter = maxGapBefore; else maxGapBefore = maxGapAfter; -// sometimes the edges are way too large. Needs research -//if (maxGapBefore > 1000000) - //maxGapBefore = maxGapAfter = 1000000; +// Cap the padding so a single oversized item doesn't drag in chains +// (and their dense cBlock lists) covering many megabases. +#define QUICKLIFT_MAX_GAP_PAD 1000000 +if (maxGapBefore > QUICKLIFT_MAX_GAP_PAD) + maxGapBefore = maxGapAfter = QUICKLIFT_MAX_GAP_PAD; int newStart = start - maxGapBefore * 2; if (newStart < 0) newStart = 0; int newEnd = end + maxGapAfter * 2; chainList = chainLoadIdRangeHub(NULL, quickLiftFile, linkFileName, chrom, newStart, newEnd, -1); for(chain = chainList; chain; chain = chain->next) { chainSwap(chain); if (*pChainHash == NULL) *pChainHash = newHash(0); liftOverAddChainHash(*pChainHash, chain); }