Open acarioni opened 1 year ago
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.
This very likely comes from https://github.com/HaxeFoundation/haxe/pull/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.
The test case was too big to paste it here, but the bug can be reproduced.
You have to follow these steps:
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.
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();
}
}
🎉 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)
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.
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.
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.
I think this should fail properly during typing because it's simply not natively supported.
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
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.
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...
Is there any possibility of introducing a flag to disable support for functional interfaces at least until they work in the expected way?
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.MainFields$Main_Fields_greeter cannot be cast to class java.lang.Runnable.
How can I help the compiler to generate the correct jvm types?
That example works on both development and with #11544. It hangs at run-time, which I'm not sure is expected.
Yes, it is expected because the executor creates a non-daemon thread.
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.
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);
}
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.
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.
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.
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 methodsubmit
on it, I getExceptionInInitializerError
.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.