OpenHFT / Chronicle-Values

http://chronicle.software
Other
104 stars 37 forks source link

Exception when attempting to generate heap classes concurrently #33

Closed moore-ryan closed 2 years ago

moore-ryan commented 3 years ago

Encountered on jdk11.0.8, using chronicle-values:2.22ea1 (but also reproducible on chronicle-values:2.20.80)

When attempting to fetch multiple heap classes for the first time concurrently, it is possible to hit an exception that seems to be caused by a threading issue when defining / loading classes.

There seem to be two different exceptions that are thrown, depending on the order things get executed in: First type of exception:

Exception in thread "Thread-0" java.lang.AssertionError: java.lang.LinkageError: loader 'app' attempted duplicate class definition for org.example.Value1$$Heap. (org.example.Value1$$Heap is in unnamed module of loader 'app')
    at net.openhft.chronicle.values.CompilerUtils.defineClass(CompilerUtils.java:87)
    at net.openhft.chronicle.values.CachedCompiler.loadFromJava(CachedCompiler.java:95)
    at net.openhft.chronicle.values.ValueModel.createClass(ValueModel.java:326)
    at net.openhft.chronicle.values.ValueModel.createHeapClass(ValueModel.java:310)
    at net.openhft.chronicle.values.ValueModel.heapClass(ValueModel.java:300)
    at org.example.ValueInitTest.lambda$main$0(ValueInitTest.java:21)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.LinkageError: loader 'app' attempted duplicate class definition for org.example.Value1$$Heap. (org.example.Value1$$Heap is in unnamed module of loader 'app')
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:878)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at net.openhft.chronicle.values.CompilerUtils.defineClass(CompilerUtils.java:82)
    ... 6 more

Second type of exception:

Exception in thread "Thread-0" Exception in thread "Thread-1" net.openhft.chronicle.values.ImplGenerationFailedException: java.lang.ClassNotFoundException: org.example.Value1$$Heap
    at net.openhft.chronicle.values.ValueModel.createClass(ValueModel.java:329)
    at net.openhft.chronicle.values.ValueModel.createHeapClass(ValueModel.java:310)
    at net.openhft.chronicle.values.ValueModel.heapClass(ValueModel.java:300)
    at org.example.ValueInitTest.lambda$main$0(ValueInitTest.java:21)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.ClassNotFoundException: org.example.Value1$$Heap
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at net.openhft.chronicle.values.CachedCompiler.loadFromJava(CachedCompiler.java:101)
    at net.openhft.chronicle.values.ValueModel.createClass(ValueModel.java:326)
    ... 4 more

Test code (using chronicle-values:2.22ea1):

package org.example

import net.openhft.chronicle.values.ValueModel;

public class ValueInitTest {

    static Class[] tests = {
            Value1.class,
            Value2.class
    };

    public static void main(String[] args) {

        // This works:
        // for(Class test : tests) {
        //   ValueModel.acquire(test).heapClass();
        // }

        // This does not usually work:
        for(Class test : tests) {
            final Thread t = new Thread(() -> ValueModel.acquire(test).heapClass());
            t.start();
        }
    }
}

The layout of the value interfaces aren't really important, so long as they need to be compiled

Value1 interface:

package org.example

public interface Value1 {
    int getValue();
    void setValue(int value);
}

Value2 interface:

package org.example

public interface Value2 {
    int getValue();
    void setValue(int value);
}
JerryShea commented 2 years ago

@moore-ryan we have fixed some concurrency bugs in Java-Runtime-Compiler. Can you reproduce with the latest code?

moore-ryan commented 2 years ago

@JerryShea Thank you for the response. I am still able to reproduce the issue with both chronicle-values:2.23.3 and with chronicle-values:2.24ea0

JerryShea commented 2 years ago

Quick response ;) Thanks @moore-ryan

I thought we were using Java-Runtime-Compiler (which is thread-safe) but we were not. I've made a PR to fix but will wait for review in case there was a reason not to have that dependency.

moore-ryan commented 2 years ago

Excellent, thank you for the quick update!

hft-team-city commented 1 year ago

Released in Chronicle-Values-2.24ea1, BOM-2.24ea86