Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jvm] ExceptionInInitializerError #11236

Open
acarioni opened this issue May 24, 2023 · 21 comments
Open

[jvm] ExceptionInInitializerError #11236

acarioni opened this issue May 24, 2023 · 21 comments
Labels
platform-jvm Everything related to JVM

Comments

@acarioni
Copy link

I installed haxe 4.3.1 for a project that previously used haxe 4.3.0-rc.1+966864c. It works well on every target but java, where it throws the exception below. Haxe 4.3.0 has the same problem too.

Unfortunately I am not able to reproduce the error by means of a simple test, but the basic execution flow is trivial: I create an instance of hx.concurrent.executor.Executor in a static variable and, when I try to call the method submit on it, I get ExceptionInInitializerError.

I already contacted the author of the library haxe-concurrent and his opinion is that the exception is due to a bug of the jvm target.

java.lang.ExceptionInInitializerError
          at utest.Test.delay(test-util/utest/Test.hx:152)
          at utest.Runner.runCurrent(test-util/utest/Runner.hx:129)
          at utest.Runner.gotoFirstTest(test-util/utest/Runner.hx:85)
          at utest.Runner$Closure_evtRun_0.invoke(test-util/utest/Runner.hx:60)
          at utest.Runner$Closure_evtRun_0.invoke(test-util/utest/Runner.hx)
          at hx.concurrent.lock.AbstractAcquirable.execute(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/lock/Acquirable.hx:55)
          at utest.Runner.evtRun(test-util/utest/Runner.hx:55)
          at utest.Runner$Closure_run_0.invoke(test-util/utest/Runner.hx:52)
          at utest.Runner$Closure_run_0.invoke(test-util/utest/Runner.hx)
          at hx.concurrent.lock.AbstractAcquirable.execute(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/lock/Acquirable.hx:55)
          at utest.Runner.run(test-util/utest/Runner.hx:50)
          at haxe.root.TestAll.main(test/TestAll.hx:93)
          at haxe.root.TestAll.main(test/TestAll.hx:1)
      Caused by: java.lang.ClassCastException: class hx.concurrent.thread.ThreadPool$Closure_onStart_0 cannot be cast to class java.lang.Runnable (hx.concurrent.thread.ThreadPool$Closure_onStart_0 is in unnamed module of loader 'app'; java.lang.Runnable is in module java.base of loader 'bootstrap')
          at sys.thread.Thread$NativeHaxeThread.<init>(/Users/acarioni/haxe/versions/4.3.1/std/java/_std/sys/thread/Thread.hx:160)
          at sys.thread.Thread$HaxeThread.create(/Users/acarioni/haxe/versions/4.3.1/std/java/_std/sys/thread/Thread.hx:104)
          at hx.concurrent.thread.ThreadPool.onStart(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/thread/ThreadPool.hx:102)
          at hx.concurrent.ServiceBase$Closure_start_0.invoke(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/Service.hx:65)
          at hx.concurrent.ServiceBase$Closure_start_0.invoke(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/Service.hx)
          at hx.concurrent.lock.AbstractAcquirable.execute(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/lock/Acquirable.hx:55)
          at hx.concurrent.ServiceBase.start(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/Service.hx:58)
          at hx.concurrent.thread.ThreadPool.<init>(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/thread/ThreadPool.hx:61)
          at hx.concurrent.executor.ThreadPoolExecutor.<init>(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/executor/ThreadPoolExecutor.hx:41)
          at hx.concurrent.executor.Executor.create(/Users/acarioni/haxe/haxe_libraries/haxe-concurrent/5.1.3/haxelib/src/hx/concurrent/executor/Executor.hx:36)
          at utest.Test$Test_Fields_.<clinit>(test-util/utest/Test.hx:10)
          ... 13 more

@acarioni
Copy link
Author

acarioni commented May 24, 2023

I add some more context.

The code emitted by haxe 4.3.0-rc.1, after being decompiled, is

public void onStart() {
  this.set_state(ServiceState.RUNNING);
  ThreadPool _gthis = this;
  int _g = 0;
  int _g1 = this.threadCount;

  while (_g < _g1) {
    ++_g;
    HaxeThread.create((Function) (new ThreadPool.Closure_onStart_0(_gthis)), false);
  }

}

public static class Closure_onStart_0 extends Function implements Runnable {
  /*...*/
}

And this is the code from haxe 4.3.1

public void onStart() {
  this.set_state(ServiceState.RUNNING);
  ThreadPool _gthis = this;
  int _g = 0;
  int _g1 = this.threadCount;

  while (_g < _g1) {
    ++_g;
    HaxeThread.create((Function) (new ThreadPool.Closure_onStart_0(_gthis)), false);
  }

}

public static class Closure_onStart_0 extends Function
    implements
      PrivilegedAction<Object>,
      PrivilegedExceptionAction<Object>,
      Callable<Object>,
      Supplier<Object>,
      Function0<Object> {
  /*...*/
}

As you can see, the class Closure_onStart_0 doesn’t implement the interface Runnable, as reported by the exception ClassCastException.

@Simn
Copy link
Member

Simn commented Jun 1, 2023

This very likely comes from #11019. The Runnable interface used to be hardcoded, now the compiler relies on checking all known types. The behavior here suggests that the compiler either doesn't see Runnable or doesn't deem the closure compatible with it.

Not sure how to debug this without a reproducible example.

@Simn Simn added the platform-jvm Everything related to JVM label Jun 1, 2023
@acarioni
Copy link
Author

acarioni commented Jun 1, 2023

The test case was too big to paste it here, but the bug can be reproduced.

You have to follow these steps:

  1. clone this project
  2. install a few tools to build the tests as listed here (you only need Apache Ant)
  3. go to the folder tools/java and issue the command
    ant test -DUTEST_PATTERN='TestEventDispatcher.testAddListener\b'

If you need any help, I can assist you in the configuration of the test environment.

@acarioni
Copy link
Author

acarioni commented Jun 7, 2023

After the addition of the functional interfaces, a closure, that used to be a simple class extending Function and implementing Runnable, now is a monster implementing a dozen interfaces (see below).

Is this mess really necessary?

And the implementation is questionable too. For example the methods run and close execute the same code, which is indeed strange.

public static class Closure_onStart_0 extends Function
implements
  PrivilegedExceptionAction<Object>,
  PrivilegedAction<Object>,
  Callable<Object>,
  Supplier<Object>,
  Runnable,
  AutoCloseable,
  Closeable,
  Flushable,
  ObjectInputValidation,
  AsynchronousChannel,
  InterruptibleChannel {
		public final ThreadPool _gthis;

		public Closure_onStart_0(ThreadPool _gthis) {
			this._gthis = _gthis;
		}

		public void invoke() {
			// ...
		}

		public void close() {
			this.invoke();
		}

		public void validateObject() {
			this.invoke();
		}

		public void flush() {
			this.invoke();
		}

		public void run() {
			this.invoke();
		}

		public Object get() {
			return this.invoke();
		}

		public Object call() {
			return this.invoke();
		}

		public Object run() {
			return this.invoke();
		}
	}

@acarioni
Copy link
Author

acarioni commented Jun 8, 2023

🎉 I finally managed to create a straightforward test case.
The code below was run with Haxe 4.3.1 and target jvm.

import sys.thread.Thread;

function main() {
  var f = () -> 0;
  Thread.create(f);
}
Exception in thread "main" java.lang.ClassCastException: class foo._Main.Main_Fields_$Closure_main_f_0 cannot be cast to class java.lang.Runnable (foo._Main.Main_Fields_$Closure_main_f_0 is in unnamed module of loader 'app'; java.lang.Runnable is in module java.base of loader 'bootstrap')
        at sys.thread.Thread$NativeHaxeThread.<init>(/Users/acarioni/haxe/versions/4.3.1/std/java/_std/sys/thread/Thread.hx:160)
        at sys.thread.Thread$HaxeThread.create(/Users/acarioni/haxe/versions/4.3.1/std/java/_std/sys/thread/Thread.hx:104)
        at foo._Main.Main_Fields_.main(src/foo/Main.hx:7)
        at foo._Main.Main_Fields_.main(src/foo/Main.hx:1)

@Simn
Copy link
Member

Simn commented Jun 12, 2023

Hmm yeah, this is because the function returns something while Runnable expects a Void return. I'm not entirely sure if that should work, but it's a bit unfortunate because Haxe admits the assignment.

Also, are you sure that this particular example used to work before? I don't see how the previous implementation would support this either.

@acarioni
Copy link
Author

This particular example doesn’t work on older versions of haxe too (the compiler accepts the code but then it throws a ClassCastException).

However, for some strange reasons, haxe 4.3.0-rc.1 generates the right code when I compile my open source project with it (see my previous post).

Is there a chance you can look at my project?
The issue can be systematically reproduced when the code is compiled with haxe 4.3.1.

@acarioni
Copy link
Author

And how about the code below?

function fun() return 0;

function main() {
  var f = () -> {
    fun();
  }
  Thread.create(f);
  Sys.sleep(0.5);
}

It’s hard to argue that it shouldn’t work only because the last statement in the function block happens to return a value as a side effect.

@Simn
Copy link
Member

Simn commented Jun 26, 2023

I think this should fail properly during typing because it's simply not natively supported.

@acarioni
Copy link
Author

I have created a zip file that contains everything you need to reproduce the issue. You only have to install the lix package manager and run the included hxml file to trigger the bug.
A readme file in the zip folder summarizes the steps for your convenience.
haxe-issue.zip

@Simn
Copy link
Member

Simn commented Aug 31, 2023

Thanks! I know how to reproduce the issue, I just don't know what to do about it other than making it fail during typing.

@Simn
Copy link
Member

Simn commented Aug 31, 2023

Actually I don't even really know how to do that because all the compiler sees is a -> Something to -> Void assignment and it usually allows that. We would need some sort of ReallyVoid type to communicate the correct type relationship here...

@acarioni
Copy link
Author

Is there any possibility of introducing a flag to disable support for functional interfaces at least until they work in the expected way?

@acarioni
Copy link
Author

Please, consider the following program:

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.lang.Runnable;

final exec = Executors.newSingleThreadScheduledExecutor();
function schedule(f: ()->Void) exec.schedule((cast f: Runnable), 0, TimeUnit.MILLISECONDS);
function greeter(): Void trace("hello");

function main() {
  schedule(greeter);
}

When compiled with Haxe 4.3.3 and executed, it throws the error Exception in thread "main" java.lang.ClassCastException: class foo.Main.Main_Fields$Main_Fields__greeter cannot be cast to class java.lang.Runnable.

How can I help the compiler to generate the correct jvm types?

@Simn
Copy link
Member

Simn commented Feb 4, 2024

That example works on both development and with #11544. It hangs at run-time, which I'm not sure is expected.

@acarioni
Copy link
Author

acarioni commented Feb 5, 2024

Yes, it is expected because the executor creates a non-daemon thread.

@Simn
Copy link
Member

Simn commented Feb 5, 2024

Right, I see. Could you turn this into something that doesn't hang so that I can add it as a test (ideally without any sleeping)? I'd like to make sure that we don't break this again.

@acarioni
Copy link
Author

acarioni commented Feb 5, 2024

This should work. I added a shutdown to kill the executor after completing the task.

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.lang.Runnable;

final exec = Executors.newSingleThreadScheduledExecutor();
function schedule(f: ()->Void) exec.schedule((cast f: Runnable), 0, TimeUnit.MILLISECONDS);
function greeter(): Void {
  trace("hello");
  exec.shutdown();
}

function main() {
  schedule(greeter);
}

Simn added a commit that referenced this issue Feb 5, 2024
@Simn
Copy link
Member

Simn commented Feb 5, 2024

Thanks! Added to #11544, without the cast because that seems to just work now. If you have any other cases that should be added as a test please let me know.

I'd also appreciate if you could test your code against that PR (once CI behaves and we get builds) to make sure we're not breaking something else. There's not a whole lot of functional interface testing other than what @EliteMasterEric has been doing, so at the moment it's somewhat difficult to stabilize this.

@acarioni
Copy link
Author

acarioni commented Feb 5, 2024

I tested my project with haxe 5.0.0-alpha.1+7aab7b5. The exception ExceptionInInitializerError didn’t occur anymore and all the tests passed. However, I had to rewrite some expressions that used operator overloading, because the compiler could not resolve them.

@Simn
Copy link
Member

Simn commented Feb 5, 2024

Thanks for testing! I'm not aware of any deliberate changes to operator overloading, but there's always a chance that some bugfix affected this. If you can isolate something, please open an issue.

Simn added a commit that referenced this issue Feb 8, 2024
* [jvm] rework functional interface unification again

see #11390

* add test

see #11236

* lazily check functional interface lut if there's no value

see #11549
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform-jvm Everything related to JVM
Projects
None yet
Development

No branches or pull requests

2 participants