Mu2eG4MT: suppress spurious fatal Cache001 at end of job - #1952
Draft
oksuzian wants to merge 1 commit into
Draft
Conversation
Mu2eG4MT jobs abort stochastically after the output file closes: G4ThreadLocalSingleton<G4HadronicInteractionRegistry>'s static destructor runs at process exit on the main art thread and destroys ParticleHP models created on G4 worker threads; their G4Cache members' thread-local backing stores on the main thread are shorter than the worker-created cache ids, so G4CacheReference<V>::Destroy raises the fatal Cache001 (Geant4 >= ~11.2). The data on disk are complete and correct - only the exit status lies. Install, at endJob, a G4VExceptionHandler that suppresses exactly Cache001 (G4's Destroy has a clean handled-exception path that skips the store) and keeps default fatal behavior for everything else. Validated: 14 MT + 2 ST runs all exit 0; suppressed-vs-clean MT outputs bit-identical; one run suppressed 2801 contiguous ids (460-3260) that the abort-on-first had hidden. Native G4 MT is immune (its main thread is the G4 master), which is why this only bites art-hosted G4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R5HnfdYMwXXrJGAkYVE48c
Collaborator
|
☀️ The build tests passed at 3f2f82e.
N.B. These results were obtained from a build of this Pull Request at 3f2f82e after being merged into the base branch at 8af4faf. For more information, please check the job page here. |
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.
Summary
Mu2eG4MTjobs abort stochastically (~1-of-2 at 100 events / 2 schedules withShieldingM_EMZ) after the output file has closed, with:The art file on disk is complete and correct — only the exit status lies, which is fatal for grid production (good jobs marked failed). This is the failure discussed on the mu2e lists in Aug 2025 (Genser/Brown/Culbertson), present since Geant4 ~11.2, and one of the reasons MT was retired from production fcls (Production#449 context).
This PR makes the job exit cleanly by installing, at
Mu2eG4MT::endJob, aG4VExceptionHandlerthat suppresses exactlyCache001and keeps default fatal behavior for everything else.Root cause (gdb backtrace)
The crash is not in Mu2e's run-manager teardown. It happens on the main art thread inside
__run_exit_handlers():ParticleHP model instances are owned by per-thread
G4HadronicInteractionRegistrysingletons, destroyed only by aG4ThreadLocalSingletonstatic destructor at process exit — on whichever thread callsexit(). TheirG4Cache<V>members were created on G4 worker threads;G4Cachebacking stores are thread-local vectors grown lazily, so the main thread's store (size 459 here) is shorter than the worker-created ids (460–3260) andDestroyraises the fatal.Why suppression is correct (not error-hiding)
~G4Cacherunning on thread X can only ever free X's own slot; the values in other threads' stores are unreachable by construction. An id beyond X's store means X has no slot — there is nothing to free and nothing to corrupt. When aG4VExceptionHandlerconsumes the exception,Destroytakes Geant4's own written handled-exception path:return;without touching the store. At process exit that is exactly the right action (the OS reclaims the per-thread values). Geant4 half-acknowledges the scenario: the exception text guesses it, and the 11.5.0.beta rewrite of this file already made the sibling case (cache() == nullptr) silently tolerated.Scoping: the handler is installed only at
endJob, so a genuine mid-runCache001(real API misuse) still aborts under the default handler. OnlyCache001is swallowed; all other fatal exceptions still abort. The handler object is deliberately leaked so it survives intoexit(). Logs the first suppressed occurrence once, then stays silent.Validation (v13_12_10 + Run1Bak backing, ceSimReco 100 events, MT 2 threads x 2 schedules)
The real fix is 3 lines in Geant4 — recipe for Mu2e's spack-built G4
Mu2e compiles its own Geant4 through spack (the p094 envset links
spackages/.../geant4-11.3.2-<hash>), so the G4-level fix can be deployed centrally without waiting for CERN. The patch makes the size-mismatch branch ofG4CacheReference<V>::Destroy(both specializations) a silent no-op instead of aFatalException— the identical post-state to this PR's handler, so the validation above covers it:g4cachedetails-teardown-tolerant.patch (applies cleanly to 11.3.2 and 11.4.0 — the file is byte-identical in both; 11.5.0.beta rewrote the file and needs a trivial rebase to its three specializations)
Recipe (for the spack maintainer):
$SPACK_ROOT/var/spack/repos/builtin/packages/geant4/package.py:patch('g4cachedetails-teardown-tolerant.patch', when='@11.2:11.4')muse-al9-prof-e29-p094-style) — note the header is template code compiled intolibG4processes, so editing the installed header alone does nothing; the full geant4 rebuild is required.Upstream status
G4CacheDetails.hhis byte-identical to 11.3.2; the 11.5.0.beta rewrite tolerates only the empty-store case and keeps the size-mismatch fatal.Relation to #1951
Independent of the MT reproducibility fix (#1951): that one makes MT output deterministic; this one makes MT jobs exit cleanly. Both are needed to make
Mu2eG4MTusable in production; neither changes single-threaded behavior.🤖 Generated with Claude Code
https://claude.ai/code/session_01R5HnfdYMwXXrJGAkYVE48c