I think at this point I have enough understanding of what's going on to file an issue, and create a PR with a fix. I also have a diagnostic test, (albeit not minimal enough to be committed) which currently only fails on J9. It's still unclear however whether I have enough buy-in from the community on the existence of the issue to get any PR I create merged. What would be the best next step here?
Chris On Tue, 10 Feb 2026 at 12:08, Chris Dennis <[email protected]> wrote: > > On Tue, 10 Feb 2026 at 06:27, Jochen Theodorou <[email protected]> wrote: > > > > > > > > On 2/9/26 22:33, Chris Dennis wrote: > > > On Mon, 9 Feb 2026 at 15:04, Jochen Theodorou <[email protected]> wrote: > > >> > > >> On 02.02.26 17:50, Chris Dennis wrote: > > [...] > > > The lazy mechanism is working correctly - the problem is that the > > > lazily created instances are only softly-referenced which means the GC > > > can come along later and clean that reference, and then a newly > > > arriving thread will create an additional instance of CachedClass for > > > the same type. If/when those two instances then meet they will falsely > > > compare not-equal and the Groovy runtime will think they represent > > > different types (which they don't). > > > > Maybe what happens is that the class is ready to be collected, but then > > we use ClassInfo to get the instance, while at the same time we are > > creating a new instance? Just trying to figure out why two instances > > even exist. > > The current mechanism I can see that allows you to end up with two > instances is because a ClassInfo softly references its CachedClass via > `ClassInfo.cachedClassRef` but CachedClass instances also softly > reference the CachedClass instances corresponding to the parameter > types of their methods via the `CachedClass.methods` field. When the > ClassInfo.cachedClassRef reference is cleared by the GC we are primed > for the construction of a new instance, but the old instance can still > be accessible via the `CachedClass.methods` field of any other type > with a method that takes that type as a parameter. In a more abstract > sense we can't rely on the current scheme to prevent multiple > instances from existing while we allow the instances to be > softly-reachable via paths involving more than one instance of > soft-reference, because those instances will not all be cleared at the > same time. > > > > > > > > I think the fix here is to > > > implement (and use) an equals method for CachedClass which uses this > > > referential comparison but only as an optimistic fast path. > > > > that is a workaround for me though... well... it depends on the > > conditions we want those constructs to fullfill > > To me this isn't a workaround, but an acceptance that we cannot > reliably maintain the 1:1 relationship between ClassInfo and > CachedClass - and that therefore the equality contract between them > cannot be a referential one. > > > > > > (There is > > > another theoretical fix here where all accesses of a given CachedClass > > > are always mediated through a single SoftReference, which I think > > > would make the existing scheme safe, but I fear it would be overly > > > brittle). > > > > This sounds a lot like the soft reference will reference an instance, > > that only this soft reference will reference. Which would be bad > > It's not bad... since the soft reference will not be cleared while the > referent is strongly referenced. So the CachedClass instance could not > be replaced while there was a strong reference to it that its future > equivalent could be compared with. I cannot think of an easy way of > preventing someone from breaking such a system though, (even if only > accidentally), so I think it's not worth the risk. > > > > > >>> I'm attempting to narrow down how exactly this is happening and whether > > >>> the cause is OpenJ9 incorrectly clearing a soft reference to a strongly > > >>> reachable instance, or is due to Groovy missing one or more > > >>> reachabilityFences to prevent early clearing of these references. > > >> > > >> Can you verify other JVMs as well? > > > > > > I've not been able to reproduce this on anything other than OpenJ9 - > > > which I suspect is due to OpenJ9 being much more eager to clear > > > references. I'm pretty sure I have a valid mechanism through which it > > > can happen though - it just seems to be impossible to make Hotspot > > > trigger it (so far). > > > > Haven't worked with the eclipse/IBM JVm for many years (since Java 9 or > > so), but I do remember having regularily trouble with the references > > stuff on there. > > > > bye Jochen > > > > Chris
