Skip to content

fix: restore original recursion limit after dendrogram computation#1306

Open
kunal-10-cloud wants to merge 3 commits intomalariagen:masterfrom
kunal-10-cloud:fix/issue-1305-recursion-limit-restore
Open

fix: restore original recursion limit after dendrogram computation#1306
kunal-10-cloud wants to merge 3 commits intomalariagen:masterfrom
kunal-10-cloud:fix/issue-1305-recursion-limit-restore

Conversation

@kunal-10-cloud
Copy link
Copy Markdown
Contributor

Summary

  • Wrapped sys.setrecursionlimit(10_000) calls in hapclust.py and dipclust.py with try/finally blocks to restore the original recursion limit after dendrogram computation
  • Previously, calling plot_haplotype_clustering() or plot_diplotype_clustering() permanently elevated the global Python recursion limit from 1,000 to 10,000 for the entire process
  • The original limit is now saved before elevation and restored in a finally block, ensuring cleanup even if an exception occurs

Problem

Both hapclust.py (line 89) and dipclust.py (line 92) called sys.setrecursionlimit(10_000) as a side effect of plotting dendrograms but never restored the original limit. This:

  1. Pollutes global state — a plotting function permanently modifies interpreter behaviour for the entire session
  2. Masks bugs — legitimate RecursionError bugs in user code or other libraries are silently suppressed because the limit is 10x higher
  3. Increases memory risk — deeper call stacks consume more memory per thread, compounding in multi-threaded environments (e.g., dask distributed workers)

Changes

malariagen_data/anoph/hapclust.py

  • Save sys.getrecursionlimit() before elevation
  • Wrap all computation in try/finally to guarantee restoration

malariagen_data/anoph/dipclust.py

  • Same pattern applied

Test plan

  • All 36 hapclust/dipclust tests pass (test_hapclust.py, test_dipclust.py)
  • Full test suite passes (1041 passed, 8 skipped)
  • ruff check passes with no issues
  • ruff format applied — no formatting violations

Fixes #1305

Copilot AI review requested due to automatic review settings April 15, 2026 19:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents plot_haplotype_clustering() and plot_diplotype_clustering() from permanently modifying the process-wide Python recursion limit by restoring the prior limit after dendrogram computation (even on exceptions), addressing issue #1305.

Changes:

  • Save the current recursion limit before temporarily increasing it for dendrogram computation.
  • Wrap clustering/plotting logic in try/finally to guarantee restoring the original recursion limit.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
malariagen_data/anoph/hapclust.py Wrapes recursion-limit elevation in try/finally so the original limit is restored after haplotype dendrogram plotting.
malariagen_data/anoph/dipclust.py Applies the same try/finally recursion-limit restoration pattern for diplotype dendrogram plotting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 90 to +95
# This is needed to avoid RecursionError on some clustering analyses
# with larger numbers of nodes.
sys.setrecursionlimit(10_000)

# Load sample metadata.
df_samples = self.sample_metadata(
sample_sets=sample_sets,
sample_query=sample_query,
sample_query_options=sample_query_options,
)
# with larger numbers of nodes. Save and restore the original limit to
# avoid permanently modifying global interpreter state.
_original_limit = sys.getrecursionlimit()
try:
sys.setrecursionlimit(10_000)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a regression test that asserts sys.getrecursionlimit() is unchanged after calling plot_diplotype_clustering() (and ideally also when an exception is raised during clustering/plotting). This ensures the try/finally restoration behavior remains covered and prevents future regressions.

Copilot uses AI. Check for mistakes.
Comment on lines 87 to +92
# This is needed to avoid RecursionError on some haplotype clustering analyses
# with larger numbers of haplotypes.
sys.setrecursionlimit(10_000)
# with larger numbers of haplotypes. Save and restore the original limit to
# avoid permanently modifying global interpreter state.
_original_limit = sys.getrecursionlimit()
try:
sys.setrecursionlimit(10_000)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a regression test that asserts sys.getrecursionlimit() is unchanged after calling plot_haplotype_clustering() (and ideally also when an exception is raised inside the dendrogram computation). This is the core behavior change, and without a test it’s easy to regress back to permanently altering the global recursion limit.

Copilot uses AI. Check for mistakes.
Both hapclust.py and dipclust.py called sys.setrecursionlimit(10_000)
without restoring the original limit, permanently modifying global
Python state for the entire process. This wraps the elevated limit
in a try/finally block to ensure the original limit is always restored,
preventing side-effect pollution in long-running sessions.

Fixes malariagen#1305
@kunal-10-cloud kunal-10-cloud force-pushed the fix/issue-1305-recursion-limit-restore branch from 048af0c to 74c5e31 Compare April 15, 2026 20:05
@kunal-10-cloud
Copy link
Copy Markdown
Contributor Author

Hi @jonbrenas

Just a friendly nudge on this PR whenever you get a chance!

Quick summary: plot_haplotype_clustering() and plot_diplotype_clustering() currently call sys.setrecursionlimit(10_000) but never restore the original limit. This means a single plotting call permanently elevates the recursion limit from 1,000 to 10,000 for the entire Python session — silently masking RecursionError bugs in user code and increasing memory risk in multi-threaded environments (e.g., dask workers).

The fix simply wraps the existing code in try/finally to restore the original limit after computation. No new dependencies, no API changes, no behaviour change for the dendrogram itself.

It's a small, self-contained fix — just two files changed with the same pattern applied to each. Full test suite passes (1041 passed). Should be a quick review!

Happy to address any feedback. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sys.setrecursionlimit(10_000) in hapclust/dipclust Permanently Alters Global Python State

3 participants