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?
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.
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.
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.
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.
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,
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With