Subject: | Cycle in Test::Most's injected Test::Builder::DESTROY |
Discovered via https://github.com/Test-More/test-more/issues/816
"[...] Test::Most injects a DESTROY sub into Test::Builder. This is a bad thing to do, first off it would mask any DESTROY block Test::Builder might add in the future. Second Test::Builder has no DESTROY block normally, and internal logic was written with that in mind.
This causes GC issues because the object being DESTROYED is normally a singleton in a variable, but the GC has made that variable undef. This also occurs after Test2 GC has occurred. Big chain reaction."
"to clarify, the new DESTROY block calls an action handler, the action handler in this case calls Test::More::BAIL_OUT, which then tries to re-create the singleton instead of using the object being DESTROYED.
I think the fix needs to be made in Test::Most, but is really simple. The DESTROY it injects should localize $Test::Builder::Test to the object being destroyed:
local $Test::Builder::Test = $_[0]; a simple 1-line fix that will probably solve this. But it needs to be fixed in Test::Most. This is actually a Test::Most bug, the new internals of Test::Builder simply exposed it. It was a bug to rely on Test::Builder creating a new singleton after the first is DESTROY'd"
To try and be super clear:
Test::Builder::DESTROY is triggered when the singleton is destroyed, meaning $Test::Builder::Test is undef now. One of the triggered behaviors however causes Test::Builder->new to build a new singleton and call methods on it. By the time this happens much of the program state has already been destroyed so it is random chance that it ever succeeded.
I suspect this can be mitigated, if not completely fixed by having the injected DESTROY block localize the $Test::Builder::Test variable back to the original singleton (the one being destroyed). However it is always possible other necessary objects have been destroyed, it is is end-of-program GC afterall.