Hi!
Today is the 10th day (full time) that I've been searching for a solution in Java Forums and Internet, but I couldn't find anything that helps me. Help is extremely appreciated, otherwise I'll go crazy! :-)
The problem is that RMI-callback thread "somehow blocks" the Event Dispatch Thread.
I want to do following:
1) I push the button in the client (btDoSomething)
(MOUSE_RELEASED,(39,14),button=1,modifiers=Button1,clickCount=1 is automatically pushed onto EventQueue)
2) btDoSomethingActionPerformed is invoked
3) inside of it I make a call to RMI-server (server.doSomething())
4) from this server method I invoke RMI-callback back to the client (client.askUser())
5) in the callback I want to display a question to the user (JOptionPane.showConfirmDialog)
6) user should answers the question
7) callback returns to server
8) client call to the server returns
9) btDoSomethingActionPerformed returns and everybody is happy :-)
This works normally in normal Client, that means, while a button is pushed, you can show Dialogs, but with RMI callback I get problems.
I just made a small client-server sample to simulate real project. What I want to achieve is that client invokes server method, server method does something, but if server method doesn't have enough information to make the decision it needs to do call back to the client and wait for input, so the user gets an JOptionPane.
Here is my callback method:
/** this is the remote callback method, which is invoked from the sevrer */
public synchronized String askUser() throws java.rmi.RemoteException {
System.out.println("askUser() thread group: " + Thread.currentThread().getThreadGroup());
System.out.println("callback started...");
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
System.out.println("My event started...");
JOptionPane.showConfirmDialog(parentFrame, "Are you OK?", "Question", JOptionPane.YES_NO_OPTION);
System.out.println("My event finished.");
}
});
}catch (Exception e) {
e.printStackTrace();
}
try {
Thread.currentThread().sleep(10000);
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("callback finished.");
return "Yo!"; // just return anything
}
Here in this sample I let the callback thread sleep for 10 seconds, but in real project I let it sleep infinitely and I want to wake it up after the user has answered JOptionPane. But I have the DEADLOCK, because event queue is waiting for callback to finish and doesn't schedule the "showing dialog" event which contains the command for waking up the callback thread.
I looked very precisely when the event queue starts again to process events: it is precisely then when btDoSomethingActionPerformed is finished.
When I'm not accessing GUI inside of the callback, this callback mechanism works perfectly, but as soon as I want to show the dialog from inside of the RMI-callback method, the "AWT Event Dispatch Queue" is somehow frozen until RMI-callback thread is finished (and in turn btDoSomethingActionPerformed terminates) . I cannot explain this weird behaviour!!!
If I don't use SwingUtilities.invokeLater (I know this shoudn't be done outside of Event Dispatch Thread), but access the GUI directy, my JOptionPane is shown, but is not painted (it is blank gray) until callback thread returns.
Why showing (or painting) of dialog is not done until btDoSomethingActionPerformed is finished?
I also tried to spawn a new Thread inside of main RMI-callback thread, but nothing changed.
I also tried the workaround from some older posting to use myInvokeLater:
private final static ThreadGroup _applicationThreadGroup = Thread.currentThread().getThreadGroup();
public void myInvokeLater(final Runnable code) {
(new Thread(_applicationThreadGroup, new Runnable() {
public void run() { SwingUtilities.invokeLater(code); }
} )
).start();
}
but this didn't help either.
Then I tried to spawn a new Thread directly from the Client's constructor, so that I'm sure that it belongs to the main appplication thread group. I even started that thread there in the constructor and made it wait for the callback. When callback came in, it would wake up that sleeping thread which should then show the dialog. But this did't help either.
Now I think that it is IMPOSSIBLE to solve this problem this way.
Spawning a new Process could work I think, but I'm not really sure if I want do to that.
I know I could make a solution where server method is invoked and if some information is missing I can raise custom exception, provide the input on client side and call the same server mathod again with this additional data, but for that I need to change server RMI interfaces, etc... to fit in this concept. I thought callback would much easier, but after almost 10 days of trying the callback...I almost regreted it :-(
Is anyone able to help?
Thank you very much!
Please scroll down for the complete sample (with build and run batch files), in case someone wants to try it. Or for the time being for your convenience you can download the whole sample from
http://www.onlineloop.com/~tornado/download/rmi_callback_blocks_gui.zip
######### BEGIN CODE ####################################
package callbackdialog;
public interface ICallback extends java.rmi.Remote {
public String askUser() throws java.rmi.RemoteException;
}
#######################################################
package callbackdialog;
public interface IServer extends java.rmi.Remote {
public void doSomething() throws java.rmi.RemoteException;
}
#######################################################
package callbackdialog;
import java.rmi.Naming;
public class Server {
public Server() {
try {
IServer s = new ServerImpl();
Naming.rebind("rmi://localhost:1099/ServerService", s);
} catch (Exception e) {
System.out.println("Trouble: " + e);
}
}
public static void main(String args[]) {
new Server();
}
}
#######################################################
package callbackdialog;
import java.rmi.Naming;
public class ServerImpl extends java.rmi.server.UnicastRemoteObject implements IServer {
// Implementations must have an explicit constructor
// in order to declare the RemoteException exception
public ServerImpl() throws java.rmi.RemoteException {
super();
}
public void doSomething() throws java.rmi.RemoteException {
System.out.println("doSomething started...");
try {
// ask the client for the "missing" value via RMI callback
ICallback client = (ICallback)Naming.lookup("rmi://localhost/ICallback");
String clientValue = client.askUser();
System.out.println("Got value from callback: " + clientValue);
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("doSomething finished.");
}
}
#######################################################
package callbackdialog;
import java.rmi.server.RemoteStub;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.Naming;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
public class Client extends javax.swing.JFrame implements ICallback {
private final JFrame parentFrame = this;
private Registry mRegistry = null;
private RemoteStub remoteStub = null;
private IServer server = null;
private final static ThreadGroup _applicationThreadGroup = Thread.currentThread().getThreadGroup();
/** Creates new form Client */
public Client() {
initComponents();
System.out.println("Client constructor thread group: " + Thread.currentThread().getThreadGroup());
try {
server = (IServer)Naming.lookup("rmi://localhost/ServerService");
// register client to the registry, so the server can invoke callback on it
mRegistry = LocateRegistry.getRegistry("localhost", 1099);
remoteStub = (RemoteStub)UnicastRemoteObject.exportObject((ICallback)this);
Registry mRegistry = LocateRegistry.getRegistry("localhost", 1099);
mRegistry.bind("ICallback", remoteStub);
}catch (java.rmi.AlreadyBoundException e) {
try {
mRegistry.unbind("ICallback");
mRegistry.bind("ICallback", remoteStub);
}catch (Exception ex) {
// ignore it
}
}catch (Exception e) {
e.printStackTrace();
}
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
secondTestPanel = new javax.swing.JPanel();
btDoSomething = new javax.swing.JButton();
getContentPane().setLayout(new java.awt.GridBagLayout());
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setTitle("RMI-Callback-GUI-problem sample");
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
formWindowClosing(evt);
}
});
secondTestPanel.setLayout(new java.awt.GridBagLayout());
btDoSomething.setText("show dialog");
btDoSomething.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btDoSomethingActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.insets = new java.awt.Insets(10, 0, 0, 0);
secondTestPanel.add(btDoSomething, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
getContentPane().add(secondTestPanel, gridBagConstraints);
pack();
}
private void btDoSomethingActionPerformed(java.awt.event.ActionEvent evt) {
System.out.println(java.awt.EventQueue.getCurrentEvent().paramString());
try {
server.doSomething(); // invoke server RMI method, which will do the client RMI-Callback
// in order to show the dialog
}catch (Exception e) {
e.printStackTrace();
}
}
private void formWindowClosing(java.awt.event.WindowEvent evt) {
try {
mRegistry.unbind("ICallback");
}catch (Exception e) {
e.printStackTrace();
}
setVisible(false);
dispose();
System.exit(0);
}
/** this is the remote callback method, which is invoked from the sevrer */
public synchronized String askUser() throws java.rmi.RemoteException {
System.out.println("askUser() thread group: " + Thread.currentThread().getThreadGroup());
System.out.println("callback started...");
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
System.out.println("My event started...");
JOptionPane.showConfirmDialog(parentFrame, "Are you OK?", "Question", JOptionPane.YES_NO_OPTION);
System.out.println("My event finished.");
}
});
}catch (Exception e) {
e.printStackTrace();
}
try {
Thread.currentThread().sleep(10000);
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("callback finished.");
return "Yo!"; // just return anything
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Client client = new Client();
client.setSize(500,300);
client.setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton btDoSomething;
private javax.swing.JPanel secondTestPanel;
// End of variables declaration
}
#######################################################
1_build.bat
javac -cp . -d . *.java
rmic callbackdialog.ServerImpl
rmic callbackdialog.Client
pause
#######################################################
2_RunRmiRegistry.bat
rmiregistry
pause
#######################################################
3_RunServer.bat
rem java -classpath .\ Server
java callbackdialog.Server
pause
#######################################################
4_RunClient.bat
java callbackdialog.Client
pause
######### END CODE ####################################