The scratch-pool lock is the guard's API rather than a convention, because one caller had skipped it - #1246
Merged
Conversation
…cause one caller had skipped it FractionFreeDeterminantTest.EliminationAgreesWithLaplaceWhereverBothApply fails on master whenever it runs beside MatrixConcurrencyTest, and it fails by reporting the determinant of a matrix of integers and a, b, c as containing sin(a) -- entries that belong to the other test's matrices. The guard added in #1230 covers GenericTensor's process-wide scratch matrix at all three of the library's call sites. It did not cover this one, which reaches DeterminantLaplace directly in order to compare Laplace against the elimination, and one unguarded caller is enough: it corrupts every guarded caller and reads their minors back. Taking the lock in the test would fix this instance. Making the lock private and the three pool operations -- DeterminantLaplace, Adjoint, InvertMatrix -- the guard's whole surface fixes the shape, since a caller can then no longer reach GenericTensor without it. That is what this does. Adjoint returns the entity rather than the tensor for the same reason. The lock has to cover reading the elements out, and Entity.Matrix's constructor is where they are actually read, so a GenTensor-returning signature would put that read on the far side of the lock. Reproduction, on master, this machine: dotnet test --filter "FullyQualifiedName~MatrixConcurrencyTest|FullyQualifiedName~FractionFreeDeterminantTest" Failed: 1 (13 of 300 determinants disagree) and with this change, three runs of the same command, Failed: 0 each time. Suites: UnitTests 8514 and 1508, FSharpWrapperUnitTests 134, InteractiveWrapperUnitTests 18, TerminalUnitTests 41. #1219 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
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.
FractionFreeDeterminantTest.EliminationAgreesWithLaplaceWhereverBothApplyfails onmasterwhenever it happens to run beside
MatrixConcurrencyTest, and it fails in the way #1219 wasfiled for — the determinant of a matrix whose entries are integers and
a,b,ccomes backcontaining
sin(a), which is an entry of the other test's matrices:Thirteen of the three hundred generated matrices disagreed. Reproducing it needs nothing but the
two classes together, since xUnit runs test classes in parallel:
Failed: 1onmaster. Each class on its own passes, which is why this has not been noticed.Why the #1230 guard did not cover it
It covers all three of the library's callers. This one is in the test tree: it reaches
DeterminantLaplacedirectly, because comparing Laplace against the elimination is the wholepoint of the test and
Determinantwould answer by elimination. One unguarded caller is enoughto break every guarded one — it writes into the shared scratch matrix while they are reading it,
and reads their minors back.
What this changes
Locking in the test would fix this instance. Instead the lock object becomes
privateand thethree pool operations —
DeterminantLaplace,Adjoint,InvertMatrix— become the guard's wholesurface, so a caller can no longer reach GenericTensor without the lock. The library's three call
sites lose their
lockstatements and gain a method call; the test calls the same method.Adjointreturns theEntityrather than the tensor deliberately. The lock has to cover readingthe elements out of the tensor, and
Entity.Matrix's constructor is where they are read, so aGenTensor-returning signature would put that read on the far side of the lock. That is thebracket the previous code had, kept.
No public API moves, and no answer changes on a single thread.
Measured
Three runs of the failing command with this change:
Failed: 0each time.UnitTests(!~Calculus)UnitTests(~Calculus)FSharpWrapperUnitTestsInteractiveWrapperUnitTestsTerminalUnitTestsAotSmokeTestwas left to CI — this change adds no reflection or generic instantiation, andCPPWrapperUnitTestsis a.bat.Part of #1219, which was closed by #1230 and turns out to have had one caller left.
🤖 Generated with Claude Code
https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura