Invalid Thread Access even with XstartOnFirstThread in vm args
Asked Answered
T

2

7

I have an embryonic Java Web Start application with a single class. It runs on Windows and Linux but gets the dreaded Invalid Thread Access error on Mac OS X. I realise that this has been dealt with elsewhere. I have spent two full days scouring the Internet and have implemented all the solutions, but the problem persists.

My understanding is that calls to SWT must be made from the main thread which is the case here. Correct me if I am wrong in that.

I will post 3 snippets below, the source code of the application, the relevant part of the jnlp file and the error message on the Mac. The question is at the end.


JAVA SOURCE CODE

package client;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class AccountWindow {
 public static void main(String[] args) {
  Display display = new Display(); **// error occurs here**
  Shell shell = new Shell(display); shell.open();
  while (!shell.isDisposed()) {
   if (!display.readAndDispatch())
    display.sleep();
  }
  display.dispose();
 }
}

JNLP SNIPPET

<resources os="Mac\ OS\ X" arch="x86_64">
    <j2se version="1.5+" java-vm-args="-XstartOnFirstThread" />
    <nativelib href="swt-4.2-cocoa-macosx-x86_64.jar" />
</resources>

ERROR MESSAGE

org.eclipse.swt.SWTException: Invalid thread access
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.createDisplay(Unknown Source)
    at org.eclipse.swt.widgets.Display.create(Unknown Source)
    at org.eclipse.swt.graphics.Device.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at client.AccountWindow.main(AccountWindow.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.sun.javaws.Launcher.executeApplication(Launcher.java:1550)
    at com.sun.javaws.Launcher.executeMainClass(Launcher.java:1488)
    at com.sun.javaws.Launcher.doLaunchApp(Launcher.java:1299)
    at com.sun.javaws.Launcher.run(Launcher.java:114)
    at java.lang.Thread.run(Thread.java:637)

PLEASE NOTE
- The display.syncExec solution posted at http://www.eclipse.org/swt/faq.php#javawebstart is not applicable because before you can invoke it you need a display. The error here happens when I try to create the display.
- I have used JaNeLa to validate the jnlp file and there are no red errors.
- <resources os="Mac\ OS\ X" arch="i386"> is being correctly interpreted because the correct swt library is being loaded.
- You can reproduce the error at http://thelinkjuicer.com/gannonline/client.jnlp


AND NOW THE QUESTION
Can anyone see anything in the source code or the jnlp snippet that would cause the error?
Secondary question: how can you tell if the -XstartOnFirstThread argument is actually being read by the VM?

Towne answered 17/10, 2012 at 15:24 Comment(0)
S
6

Clearly, your main method is not being executed on the main thread. You can see in the stack trace that the launcher is actually started in another thread, and then the Launcher only indirectly calls main. This is unfortunately just the diagnostic, I am not sure about the solution. I have done a similar thing (SWT app through Java Web Start), but I can't remember how we solved this, if at all.

After checking the com.sun.javaws.Launcher source code, it is quite unclear how this could be made to work. The Launcher.launch method starts a new thread within which your main method is executed. You can follow the code to recreate the exact stacktrace you are getting.

The main entry point of Java Web Start shows that the main thread dies soon after starting.

Update

I dug something out: in this Eclipse bug report it is suggested that the problem could be related to this:

<resources>
  <j2se version="1.4+" />
  <jar href="client.jar" />
</resources>

The parser takes the j2se spec from here and ignores the later, more specific ones. Try removing the <j2se... line.

Update 2

Now I dug this up from here:

com.apple.concurrent.Dispatch.getInstance().getNonBlockingMainQueueExecutor().execute(
  new Runnable() { public void run() {
      final Display display = Display.getDefault(); 
      while (!display.isDisposed()) {
        if (!display.readAndDispatch())
          display.sleep();
      }
});

This actually sounds like something workable. It does exactly what I described in my comment below: patches into the main thread through a mechanism specifically put in place for this purpose. Try to adapt this to your need. You may not even need -XstartOnFirstThread with this.

Update 3

I finally found my old SWT-JWS project. It's got this in it:

<resources os="Mac OS X" arch="x86_64">
  <j2se version="1.6+" java-vm-args="-XstartOnFirstThread"/>
  <jar href="swt-cocoa-macosx-x86-64-3.6.2.jar" />
</resources>

and it works. It has no default j2se element, this element appears only in the OSX-specific entry.

Superfluity answered 17/10, 2012 at 15:54 Comment(17)
Thanks, Marko. Since there is no new thread created in the source code, and the new display call is made from the main() method, I suppose this means that -XstartOnFirstThread is not actually being recognised by the VM? Any ideas anyone?Towne
I just checked the source code. The option is probably being recognized, but that's irrelevant: the problem is that as soon as the Web Start framework transfers control to a new thread, and that happens not just once, but twice, it's game over. Check the Launcher.launch method in the source code.Superfluity
This is strange because there are JWS/SWT applications that work with Mac OS X. Is there any way to get a reference to the main thread in order to implement the display.syncExec solution?Towne
A reference to the Thread instance will not help. You cannot crash into a running thread of execution. This would work only if there was a specific mechanism in place that accepted a Runnable into the main thread.Superfluity
Thank you Marko. This is the most in-depth explanation of what is actually going on with respect to the Invalid Thread Access error that I have seen. What baffles me is that there are any SWT applications that work at all on Mac OS X!Towne
This baffles me, too. I'm still digging around to get to the bottom of this.Superfluity
The change has no effect. However, I know that the parser is reading the whole shebang because it is loading the correct jar file which is specified in the line after the j2se line. I also moved the j2se line so it is after the nativelib line, but this had no effect either.Towne
Can't get it to work. Maybe due to my limited knowledge of Java. If dDisplay is declared inside the runnable it is not accessible outside the block. If it is declared outside the runnable Java refuses to assign a value inside the block. I have tried making it a class member, declaring it static or final but it won't compile. Any suggestions? (I find Java a bit anal retentive sometimes)Towne
The code snippet was taken from a wider context that you can see in the linked bug report towards the top. But I don't think you need the Display in the outside context: you should run the event dispatch loop inside that Runnable.Superfluity
Thanks Marko for all your help. I will have to work on this a bit. The runnable thingy doesn't execute. Also, the line that worries me is this: com.apple.concurrent.Dispatch.getInstance().getNonBlockingMainQueueExecutor().execute( It would make the code platform-dependent, and the whole point of jws is to have a single executable with platform-dependent libraries and jnlp to sort it out. I will keep at it.Towne
I would try one thing, though: remove those backslashes in front of spaces in Mac\ OS\ X. They aren't necessary.Superfluity
If the name of the OS has spaces in it, you need the backslashes to tell the parser that it is one OS, else it will think it is three OS's. I tried without the backslashes and it does not recognise the OS at all.Towne
OK... I saw that other examples consistently do it without spaces, that's why I say it.Superfluity
I put in a division by zero right after the new display to provoke an exception on non-Mac platoforms. On Windows, where the exception occurs, when I look at the error message, the call chain is identical to the one on Mac. If the call chain were creating a new thread as you say, then the new display would fail in Windows as well, but it doesn't. So it must be running in the main thread, which takes us back to whether the VM is paying any attention to XstartOnFirstThread...Towne
I managed to do it in carbon. I think when you see someone on the Internet saying they got SWT to work with Mac OS X they are using carbon. It is a bit old but cocoa just doesn't seem to work at all. You need to set the Java preferences to 32 bit, that's all.Towne
I found my old project and it works. It has the setting I put into my update.Superfluity
Marko you are a wonderful person! That was it... the parser only reads one j2se element and disregards any others, so the default j2se element was preventing the parser from reading the Mac OS X specific one which had the XstartOnFirstThread argument. The window opens and the default menu appears, although I have to force quit because neither the Quit item in the menu nor the close button function. I suppose I must implement them in the code. Thanks a million!Towne
P
3

This is an answer to the secondary question, "How can you tell if the -XstartOnFirstThread argument is actually being read by the VM?" (or the related question, "how can you detect if -XstartOnFirstThread was passed to the VM?") I looked at java.lang.management.RuntimeMXBean.getInputArguments(), but -XstartOnFirstThread is not included in the returned List. After some research, I was able to figure out something, so I hope this helps someone else who was in my shoes.

According to this link, there are several environment variables set by the launcher. Among them are:

JAVA_MAIN_CLASS_pid
JAVA_STARTED_ON_FIRST_THREAD_pid

Use System.getenv() to obtain a Map of the environment variables. From there, you can iterate through the entrySet() until you find an Entry with a getKey() whose return value starts with "JAVA_MAIN_CLASS_" . If the discovered Entry's getValue() contains the name of your main class, you can use the rest of the key to determine the pid.

Once you have the pid, look up the string "JAVA_STARTED_ON_FIRST_THREAD_pid" in the environment Map. If it exists and has the value "1", the process was started with -XstartOnFirstThread. Otherwise, the process was started without the flag.

This probably won't work in an unsigned WebStart application, since the System.getenv() method is prohibited by default. But in a signed webstart application, or in a regular Java application, this does work.

Hope that helps,

Police answered 5/3, 2013 at 16:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.