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) <noreply@anthropic.com>

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);
     }