Limit the scope of forgotten reference checks #781
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.
Resolves #752
The
RefNeverReleased
exception appears relatively often nowadays while running tests in CI (see #752). I've been able to narrow down the cause of these exceptions to theprop_noSwallowedExceptions
property test. Forgotten references in that property test causeRefNeverReleased
exceptions in other tests, for example theunit_union_blobref_invalidation
test that is mentioned in #752. The thing is that we expect to forget references inprop_noSwallowedExceptions
because in that property test we are injecting disk faults in code that is not always exceptionsafe, which may lead to such forgotten references. We've gone to some lengths to ensure thatprop_noSwallowedExceptions
runs in isolation from other property tests with respect to forgotten references by turning off forgotten reference checks locally. As the test failures illustrate, it is tricky to do right, and the problem is persistent. So, we'll try to modify the forgotten refs checks approach a bit to get rid of the bug once and for all. First, some contents about the mechanism of checking forgotten refs:IORef
(the "forgotten refs variable") that we create usingunsafePerformIO
andNOINLINE
trickery.RefNeverReleased
exceptions. These assertions run both as part of other reference operations (likedupRef
), or they can be run manually. Depending on the RTS/GC scheduling, this exception can be raised at many points in the code even in other property tests than the one that the reference was created in, though eachRefNeverReleased
exception will be raised only precisely once.We cant really do 2 differently because only the RTS/GC can detect whether a value becomes unused. We shouldn't change 3 because we should probably report forgotten references as promptly as possible. We can do 1 differently and provide some isolation of the "forgotten refs variable" that is more granular than per program.
A natural candidate for the scope in which references could be tracked is the
Session
context. References do not appear outside of sessions. They are stored in tables and cursors, but tables and cursors are always stored in a session context, and their lifetimes are also limited by the lifetime of their parent session. So, this PR introduces a newRefCtx
that is created anew and stored in a session once a new session is created. ThisRefCtx
holds a "forgotten refs variable", which would previously have been a globalIORef
. Now, the variable is scoped only to the lifetime of theRefCtx
and therefore the lifetime of the parentSession
. Once a session is closed, we "close" theRefCtx
, which means that we check if any forgotten refs were recorded.The addition of
RefCtx
causes some churn in the library, because values of the type have to be threaded from top-level code (that uses aSession
) through to code that creates/destroys/modifies references. The upside is that the public API remains unchanged, only internal code changes. Moreover, forgotten reference checking is still compiled away in non-debug mode. The change also does not seem to impact performance, as demonstrated using some runs of theutxo-bench
benchmark in non-debug mode:The differences in measurement I would attribute to noise, seeing as the differences are so small. All other metrics, like allocated bytes, are also nearly identical.
Finally, we fix our issue with forgotten references from
prop_noSwallowedExceptions
causingRefNeverReleased
exceptions in other tests. Inprop_noSwallowedExceptions
, we turn off forgotten reference checks by reaching into theSession
to get out theRefCtx
and disable its forgotten reference mechanism.