fix(profile): stop MaddisonSlatkin profile scoring hanging on macOS (arm64)#272
Merged
Merged
Conversation
…ters PrepareDataProfile() -> StepInformation() runs the exact MaddisonSlatkin recursion for 3-5 token characters. Its memoisation uses fixed-capacity open-addressing maps (OAFlatMap: logB_cache, logPVec_idx) reserved on the assumption that a character never needs more entries than reserved. Large multistate characters on many tips violate that: e.g. a 3-token [42,9,2] character on 62 tips generates ~9000 distinct (token, leaves) keys, far above logPVec_idx's 4096-entry reserve. Once such a table fills, probe_slot() -- which stops only on an empty slot -- loops forever, and that loop lives outside LogB/LogPVec so the 2 s wall-clock budget cannot interrupt it. On x86-64 the time budget usually wins the race (bails to NA, and the R caller falls back to the Monte Carlo approximation), so the blowup is invisible. On Apple Silicon the table fills before 2 s elapses and the search hangs indefinitely -- reproduced as a 6 h CI timeout in "checking examples with --run-donttest" for TreeLength(concavity = "profile") on Agnarsson2004 (inapplicable.phyData[[1]]). Fix: add OAFlatMap::at_capacity() (true at load factor 0.5, i.e. the reserved size) and, alongside the existing time-budget guard in LogB()/LogPVec(), bail to the NA -> Monte-Carlo fallback the moment a memo table reaches its reserved capacity. Bailing at the reserve keeps entries_ within its reserved storage, so the table can neither fill (no probe_slot infinite loop) nor reallocate (which would also have dangled the FixedProbList* held across recursion) -- fixing both the hang and a latent dangling-pointer UB in the same blowup case. Characters within reserve are unaffected (exact result unchanged); over-reserve characters were already broken (UB/hang) and now return the correct MC approximation. Verified on x86-64: the [42,9,2] character now warns and completes in ~1.5 s; profile TreeLength(Agnarsson2004) completes; all profile/parsimony tests pass (MaddisonSlatkin 53, pp-info 131, ts-profile 43, tree_length 75, iw-profile 15). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assert StepInformation() on a skewed 3-state character that overflows the exact solver's memo tables (the inapplicable Agnarsson2004 col-83 pattern [42,9,2]) falls back to the Monte Carlo approximation with a warning instead of hanging. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ove before merge) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n macOS arm64) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… race Which fallback fires -- the capacity guard or the 2 s time budget -- is timing dependent (fast machine hits capacity first, slow machine hits 2 s first), so asserting the specific cache warning flaked on CI (passed once, failed on re-run). Assert the anti-hang property (completes, finite) plus that some fallback warning containing 'exceeded' fired, without pinning which guard won. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow r-conventions: no right-hand/non-local assignment (the withCallingHandlers handler used <<- to accumulate) and camelCase variables. testthat::capture_warnings() collects every warning with no superassignment and evaluates in-frame so si is still bound for the completion/finite assertions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
R CMD check --run-donttesthangs for 6 hours (the GitHub ceiling) on macOS, intermittently taking the ubuntu R 4.1 leg instead. The hang is inchecking examples with --run-donttest, on the pre-existingTreeLength(..., concavity = "profile")example intree_length.R(datasetinapplicable.phyData[[1]]= Agnarsson2004, 62 tips).Root cause
PrepareDataProfile()→StepInformation()runs the exact MaddisonSlatkin recursion for 3–5 token characters. Its memoization uses fixed-capacity open-addressing maps (OAFlatMap:logB_cache,logPVec_idx) reserved on the assumption that a character never needs more entries than reserved.A skewed 3-state character has a low split-count, so the feasibility gate classes it feasible for the exact solver — but on many tips it generates far more distinct
(token, leaves)keys than reserved. Agnarsson2004 column 83 is[42, 9, 2]: ~9000 keys vslogPVec_idx's 4096-entry reserve. Once the table fills,probe_slot()— which stops only on an empty slot — loops forever, and that loop lives outsideLogB/LogPVec, so the existing 2-second wall-clock budget cannot interrupt it.It's a race the platform decides:
probe_slot→ hang.This is pre-existing and independent of any feature work; it surfaced as a CI timeout because the profile example exercises exactly such a character.
Fix
Add
OAFlatMap::at_capacity()(true at load factor 0.5 — the reserved size) and, alongside the existing time-budget guard inLogB()/LogPVec(), bail to the NA → Monte-Carlo fallback the instant a memo table reaches its reserved capacity.Bailing at the reserve keeps
entries_within its reserved storage, so the table can neither fill (noprobe_slotinfinite loop) nor reallocate (which would also have dangled theFixedProbList*held across recursion) — fixing both the hang and a latent dangling-pointer UB in the same blowup case. Characters within reserve are unaffected (exact result unchanged); over-reserve characters were already broken (UB/hang) and now return the correct MC approximation.Verification
[42, 9, 2]character now completes in 0.91 s (was a 6 h hang) with the capacity warning; full profileTreeLength(Agnarsson2004)completes in 14 s. Confirmed green on macOS-latest, ubuntu-24.04 (R 4.1) and windows-latest.🤖 Generated with Claude Code