reprod_hmm.hmm.txt
Summary
Calling pyhmmer.plan7.TopHits.merge(*partials) on TopHits returned
from Pipeline.search_hmm() calls that ran in parallel Python threads
crashes the interpreter with SIGSEGV (exit 139).
The same crash surfaces through pyhmmer.hmmer.hmmsearch(hmms, targets)
whenever it auto-selects parallel="targets" (i.e. len(hmms) < cpus
and targets are a DigitalSequenceBlock), because the reverse dispatcher
calls TopHits.merge on partial results internally
(hmmer/_hmmsearch.py:263).
Environment
Reproduces on both platforms tested; likely OS-independent.
- pyhmmer 0.12.1 (also reproduces on 0.12.0)
- macOS 15.7.7 arm64 (Darwin 24.6.0), Python 3.12.12
- Linux 6.10.14 x86_64, glibc 2.41 (Debian slim in Docker), Python 3.12.13
backend="threading" (the default)
Minimal reproducer
The HMM (reprod_hmm.hmm, 60 KB, attached) is a single IMGT-derived
antibody heavy-chain profile (M=128). A Builder.build_msa-constructed
HMM of similar size does not reproduce the crash, so something about
hmmbuild-produced profiles is required to trigger it.
import pyhmmer
from concurrent.futures import ThreadPoolExecutor
alphabet = pyhmmer.easel.Alphabet.amino()
with pyhmmer.plan7.HMMFile("reprod_hmm.hmm") as hf:
hmm = next(iter(hf))
seq = ("EVQLVESGGGLVQPGRSLRLSCAASGFTFDDYAMHWVRQAPGKGLEWVSAITWNSGHIDYADSVEGRF"
"TISRDNAKNSLYLQMNSLRAEDTAVYYCAKVSYLSTASSLDYWGQGTLVTVSS")
def search(_):
p = pyhmmer.plan7.Pipeline(alphabet,
background=pyhmmer.plan7.Background(alphabet))
b = pyhmmer.easel.DigitalSequenceBlock(alphabet)
for j in range(25):
b.append(pyhmmer.easel.TextSequence(
name=f"s{j}".encode(), sequence=seq
).digitize(alphabet))
return p.search_hmm(hmm, b) # returns TopHits
with ThreadPoolExecutor(max_workers=4) as ex:
partials = list(ex.map(search, range(4)))
print("partials sizes:", [len(p) for p in partials]) # prints [25,25,25,25]
merged = pyhmmer.plan7.TopHits.merge(*partials) # SIGSEGV here
print("merged OK", len(merged)) # never reached
$ python reprod.py ; echo "exit=$?"
partials sizes: [25, 25, 25, 25]
exit=139
What I've ruled out
- Not the search itself. Iterating each partial
TopHits individually
after the ThreadPoolExecutor block (without calling merge) works
cleanly.
- Not a data race in workers. Replacing
return p.search_hmm(hmm, b)
with return len(p.search_hmm(hmm, b)) — same work, just discarding
the TopHits before it leaves the worker — runs to completion. Keeping
a reference to the worker's Pipeline alive alongside the TopHits
does not prevent the crash. Points at a raw-pointer or lifetime
issue inside TopHits.merge itself.
- Not the HMM size. A 120-position HMM built via
Builder.build_msa(...) and searched the same way does not crash.
Notes
Passing parallel="queries" (or cpus=1) to hmmer.hmmsearch avoids
the merge code path entirely, but sacrifices the target-parallel
speedup — for len(hmms)=1 this equals single-threaded execution.
Attachments
reprod_hmm.hmm.txt — rename to .hmm after download. GitHub blocks
.hmm uploads; the file is unchanged plain-ASCII HMMER3 format (60 KB).
reprod_hmm.hmm.txt
reprod_hmm.hmm.txt
Summary
Calling
pyhmmer.plan7.TopHits.merge(*partials)onTopHitsreturnedfrom
Pipeline.search_hmm()calls that ran in parallel Python threadscrashes the interpreter with SIGSEGV (exit 139).
The same crash surfaces through
pyhmmer.hmmer.hmmsearch(hmms, targets)whenever it auto-selects
parallel="targets"(i.e.len(hmms) < cpusand targets are a
DigitalSequenceBlock), because the reverse dispatchercalls
TopHits.mergeon partial results internally(
hmmer/_hmmsearch.py:263).Environment
Reproduces on both platforms tested; likely OS-independent.
backend="threading"(the default)Minimal reproducer
The HMM (
reprod_hmm.hmm, 60 KB, attached) is a single IMGT-derivedantibody heavy-chain profile (M=128). A
Builder.build_msa-constructedHMM of similar size does not reproduce the crash, so something about
hmmbuild-produced profiles is required to trigger it.
What I've ruled out
TopHitsindividuallyafter the
ThreadPoolExecutorblock (without callingmerge) workscleanly.
return p.search_hmm(hmm, b)with
return len(p.search_hmm(hmm, b))— same work, just discardingthe
TopHitsbefore it leaves the worker — runs to completion. Keepinga reference to the worker's
Pipelinealive alongside theTopHitsdoes not prevent the crash. Points at a raw-pointer or lifetime
issue inside
TopHits.mergeitself.Builder.build_msa(...)and searched the same way does not crash.Notes
Passing
parallel="queries"(orcpus=1) tohmmer.hmmsearchavoidsthe
mergecode path entirely, but sacrifices the target-parallelspeedup — for
len(hmms)=1this equals single-threaded execution.Attachments
reprod_hmm.hmm.txt— rename to.hmmafter download. GitHub blocks.hmmuploads; the file is unchanged plain-ASCII HMMER3 format (60 KB).reprod_hmm.hmm.txt