RMI RMI Policies Anfängerfrage

CHE123

Mitglied
Hallo,

habe leider folgendes Problem:

Auf einem Serverrechner läuft ein Server-Prozess, dessen Methode via RMI von einem Client-Prozess,
der auf einem anderen Rechner im Netzwerk läuft, aufgerufen wird.
Die RMI-Registry läuft auf dem Serverrechner.
Das Server-Object kann vom Client über die Registry aufgelöst werden, der Zugriff auf eine Methode
wirft allerdings eine Exception.
Die gleiche JAR-Datei liegt auf beiden Rechnern.

Hier der Code...

serverseitig

Java:
public interface Server extends Remote
{
  static public final String RmiRegistryName = "TestServer";
  
  String foo() throws RemoteException;
}

Java:
public class ServerImpl implements Server
{
  public static void main(String[] args) throws Exception
  {
    if(System.getSecurityManager() == null) {
      System.setSecurityManager(new SecurityManager());
    }

    final Server server = new ServerImpl();

    final Server stub = (Server) UnicastRemoteObject.exportObject(server, 0);
    System.err.println("stub: " + stub);
    
    final Registry registry = LocateRegistry.getRegistry(1097);
    System.err.println("registry: " + registry);
    
    registry.rebind(Server.RmiRegistryName, stub);
    System.err.println("server bound and running...");
  }

  public Main() { 
    super();
  }

  public String foo() throws RemoteException {
    System.err.println("foo() called");
    return "foo";
  }
}

Datei server.policy

grant codeBase "file:/users/xyz/rmitest/-"
{
permission java.security.AllPermission;
};


Aufruf:
java -cp .:./RmiTest.jar
-Djava.rmi.server.hostname=10.17.146.179
-Djava.security.policy=server.policy
-Djava.security.codebase=file:/users/xyz/rmitest/RmiTest.jar
rmitest.Main

clientseitig:

Java:
public class Client
{
  public static void main(String[] args) throws Exception
  {
    if(System.getSecurityManager() == null) {
      System.setSecurityManager(new SecurityManager());
    }
    
    final Registry registry = LocateRegistry.getRegistry(System.getProperty("hostname"), 1097);
    System.err.println("registry: " + registry);
    final Server server = (Server) registry.lookup(Server.RmiRegistryName);
    System.err.println("server: " + server);

    final String s = server.foo();
    System.err.println("server.foo() returned " + s);
  }
}

Datei client.policy

grant codeBase "file:/users/xyz/rmitest/-"
{
permission java.security.AllPermission;
};


Aufruf:
java -cp .:./RmiTest.jar -Dhostname=10.17.146.179
-Djava.security.policy=client.policy
-Djava.security.codebase=file:/users/xyz/rmitest/RmiTest.jar
rmitest.Client


läuft Server und Client auf einem Rechner, funktioniert das ganze - auch ohne SecurityManager,
luafen die Programme auf verschiedenen Rechnern, wirft der Client beim Aufruf der Methode foo()
folgende Exception:

Java:
Exception in thread "main" java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
        java.net.ConnectException: Connection refused
        at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:574)
        at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:185)
        at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:171)
        at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:94)
        at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:179)
        at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:132)
        at $Proxy0.foo(Unknown Source)
        at rmitest.Client.main(Client.java:49)
Caused by: java.net.ConnectException: Connection refused
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
        at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
        at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
        at java.net.Socket.connect(Socket.java:520)
        at java.net.Socket.connect(Socket.java:470)
        at java.net.Socket.<init>(Socket.java:367)
        at java.net.Socket.<init>(Socket.java:180)
        at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(RMIDirectSocketFactory.java:22)
        at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(RMIMasterSocketFactory.java:128)
        at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:569)
        ... 7 more

Denke, dass ich hier die Policies bzw. die Verwendung der Codebase wohl nicht verstanden habe:noe:

Bin für jede Hilfe dankbar!

g,
Christian


EDIT:

habe die Object-Referenz, die der Client ausgibt, vorenthalten - dort liegt anscheinend der Fehler:

server: Proxy[Server,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:[localhost:41468](remote),objID:[-61e09ca0:12963049d61:-8000, 0]]]]]

warum steht hier localhost und nicht die IP-Adresse des Servers auf dem das Objekt resistiert...
 
Zuletzt bearbeitet:
T

tuxedo

Gast
In Zeile 14 deines Server erzeiugst du keine Registry, du holst sie dir. Ich denke das macht einen Unterschied. Mach da mal ein createRegistry().

Auch das holen der Registry auf Clientseite kommt mir komisch vor. Müsste jetzt aber selbst erst googeln und schauen warum.

Aber vielleicht magst du dir ja mal meine RMI Alternative anschauen?!

- Alex
 

CHE123

Mitglied
Hallo Alex,

hmm, da die registry als eigenständiger Prozess läuft, hole ich sie mir anstatt eine zu instanzieren - was ein createRegistry() machen würde. Die registry wird sowohl vom Server als auch vom Client
erreicht, auch kann das Server-Objekt durch den Client aufgelöst werden, nur der Methoden-Aufruf auf das Server-Objekt geht schief, was imo an der falschen Hostadresse (localhost) in der Objekt-Referenz liegt

erwartet hätte ich mir

server: Proxy[Server,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:[10.17.146.179:41468](remote),objID:[-61e09ca0:12963049d61:-8000, 0]]]]]

nicht

server: Proxy[Server,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:[localhost:41468](remote),objID:[-61e09ca0:12963049d61:-8000, 0]]]]]

somit findet der Client wohl nicht mehr zurück...

Apropos: gibt es einen Browser für die registry? kenne sowas von CORBA, um die Einträge im NamingService zu begutachten.

Danke,

G,
christian
 
T

tuxedo

Gast
Hallo Alex,

hmm, da die registry als eigenständiger Prozess läuft, hole ich sie mir anstatt eine zu instanzieren - was ein createRegistry() machen würde.

Ah, okay. Das wusste ich ja nicht.

Die registry wird sowohl vom Server als auch vom Client
erreicht, auch kann das Server-Objekt durch den Client aufgelöst werden, nur der Methoden-Aufruf auf das Server-Objekt geht schief, was imo an der falschen Hostadresse (localhost) in der Objekt-Referenz liegt

erwartet hätte ich mir

server: Proxy[Server,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:[10.17.146.179:41468](remote),objID:[-61e09ca0:12963049d61:-8000, 0]]]]]

nicht

server: Proxy[Server,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:[localhost:41468](remote),objID:[-61e09ca0:12963049d61:-8000, 0]]]]]

somit findet der Client wohl nicht mehr zurück...

Ich geh mal davon aus dass du Client und Server auf getrennten Maschinen laufen lässt?
Dann ist das in der Tat etwas "seltsam".

Ich seh nur noch einen möglichen Fehler:

Java:
final Server stub = (Server) UnicastRemoteObject.exportObject(server, 0);

Wieso gibst du hier als Port 0 an? Wieso nimmst du nicht die Variante die nur ein Argument hat?
Die Doku verrät leider nicht was passiert wenn man hier als Port 0 angibt. Aber es könnte ja sein dass das Objekt dann nur lokal freigegeben wird und deshalb die Adresse auf localhost steht?

Apropos: gibt es einen Browser für die registry? kenne sowas von CORBA, um die Einträge im NamingService zu begutachten.

Es gibt da ein kommerzielles RMI Plugin für Eclipse. Das konnte man zu zeiten, wo man die Stubs extra compilieren musste noch gut gebrauchen. Das das nun aber on-the-fly von selbst geht, braucht man das Plugin nicht mehr. ABER es hatte einen Browser für die Registry.

Allerdings ist das kein Hexenwerk selbst nen Browser zu basteln. Hatte mal nen sehr rudimentären Browser gebastelt. Der hat einem angezeigt was in der Registry rumliegt. Ich schau mal dass ich's hier anhänge. Ist ziemlich Quick'n'Dirty. Aber für meine Zwecke war's ausreichend. Sourcecode ist im JAR enthalten.

- Alex
 

CHE123

Mitglied
hallo Alex,

danke fü die Antwort, und sry für die späte Rückantwort...

hab auf SIMON umgestellt - funktioniert bis auf eine Sache... hier hab noch eine Frage:
wenn ich den Server abdrehe, bekomm ich folgende Exception (was ja OK ist)

Java:
de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, com.knapp.ksd.RemoteMatrixWrapper)" on already closed session.
        at de.root1.simon.Dispatcher.checkForInvalidState(Dispatcher.java:600)
        at de.root1.simon.Dispatcher.invokeLookup(Dispatcher.java:206)
        at de.root1.simon.Simon.lookup(Simon.java:518)
        at de.root1.simon.Simon.lookup(Simon.java:278)
        at de.root1.simon.Simon.lookup(Simon.java:246)
        at com.knapp.ksd.marvin.backend.proxy.RemoteMatrixWrapper$ProxyLookupThread.run(RemoteMatrixWrapper.java:63)

wenn ich den server wieder aufdreh, bleibt die Exception allerdings bestehen, obwohl die
registry wieder lauscht.

g,
Christian
 
T

tuxedo

Gast
:)

Ist doch klar.

Jede Verbindung zum Server wird in einer internen Session behandelt. Schaltest du den Server ab oder ziehst du den Netzwerkstecker, so wird die Verbindung unterbrochen. Die Session ist somit "tot". Und genau das sagt die Exception: Man kann keine Methode aufrufen deren zugrundeliegende Verbindung/Session nicht mehr da ist.

Startest du den Server wieder, oder steckst du den Stecker wieder rein, so ist die alte Session nach wie vor tod. Die kann sich nicht von selbst heilen. Der Client muss eine neue Session aufbauen und die eine oder andere Aktion triggern bevor er da weitermachen kann wo er aufgehört hat.

Der Grund für den nicht vorhandenen Selbstheilungs/Reconnect Modus:

Stell dir eine komplexe Client-Server Anwendung vor. Ein Client verbindet sich zum Server, loggt sich ein, triggert eine Aktion hier, triggert eine Aktion da. Der Client befindet sich also in einem gewissen Kontext. Der Server weiß in der Regel genau in welchem Kontext welcher Client gerade ist.

Raucht die Verbindung ab, müsste der Client ja nicht nur einfach reconnecten. Er müsste auch den Kontext auf Serverseite wiederherstellen/sicherstellen. Und wie soll eine Library wie SIMON (oder RMI) das automatisiert tun? Einloggen, Aktion XYZ triggern etc... ?!
Das ist Sache der Anwendungs-/Business-Logik. SIMON ist nur das Kommunikationsframework das sich einer verbindungsorientierten, permanenten Verbindung (TCP) bedient.

- Alex
 

CHE123

Mitglied
hallo Alex,

ich dachte, genau dieser Lookup entspricht einem Reconnect, vl stell ich mal den Source rein:

Java:
    @Override
    public void run() {
      try {
        log.info("proxy lookup thread is running");
        for(running = 2; running == 2; ) {
          try {
            final MatrixWrapperProxy tmp = (MatrixWrapperProxy) Simon.lookup(rmiRegistryHost, rmiRegistryPort, MatrixWrapperProxy.RmiRegistryName);
            if(tmp != null) {
              if(!tmp.equals(remoteMatrixWrapper)) {
                log.info("proxy rebound to new <" + tmp + "> from old <" + remoteMatrixWrapper + ">");
                synchronized(lock) {
                  remoteMatrixWrapper = tmp;
                  if(remoteMatrixWrapper != null) {
                    remoteMatrixWrapper.registerIpcMessageConsumer(remoteIpcMessageConsumer);
                  }
                }
              }
            }
            else if(remoteMatrixWrapper != null) {
              synchronized(lock) {
                remoteMatrixWrapper = null;
              }
              log.warn("proxy not available");
            }
          }
          catch(final SimonRemoteException x) {
            synchronized(lock) {
              remoteMatrixWrapper = null;
            }
            log.warn("proxy not available while performing lookup("
              + rmiRegistryHost + ", " + rmiRegistryPort + ", " + MatrixWrapperProxy.RmiRegistryName + ")", x);
          }
          Thread.sleep(3000); // TODO cfg
        }
      }
      catch(final Exception x) { // also MalformedURLException
        log.error("unhandled exception in proxy lookup thread", x);
      }
      finally {
        log.info("proxy lookup thread ran out");
        running = 0;
      }
    }

Weiters dachte ich, die Verbindung sei stateless und der Kontext ist die Objektreferenz selbst.
Da der Server zu jeder Zeit restarted werden kann, somit eine neue Registry oder/und ein neues Remote-Objekt publiziert werden kann, und
die lookup Methode mit allem Notwendigen (Registry-server, -port, Objektname) argumentiert wird, bin ich wohl diesemTrugschluss erlegen.

Welche Strategy ist notwendig, um die tote Session zu kicken und - wenn die Registry wieder erreichbar ist, ein neues Objekt zur
Verfügung steht - ein lebendes Objekt zu erhalten, bzw. wie sieht ein Reconnect aus?

Danke,
Christian
 
T

tuxedo

Gast
hallo Alex,

ich dachte, genau dieser Lookup entspricht einem Reconnect, vl stell ich mal den Source rein:

Java:
    @Override
    public void run() {
      try {
        log.info("proxy lookup thread is running");
        for(running = 2; running == 2; ) {
          try {
            final MatrixWrapperProxy tmp = (MatrixWrapperProxy) Simon.lookup(rmiRegistryHost, rmiRegistryPort, MatrixWrapperProxy.RmiRegistryName);
            if(tmp != null) {
              if(!tmp.equals(remoteMatrixWrapper)) {
                log.info("proxy rebound to new <" + tmp + "> from old <" + remoteMatrixWrapper + ">");
                synchronized(lock) {
                  remoteMatrixWrapper = tmp;
                  if(remoteMatrixWrapper != null) {
                    remoteMatrixWrapper.registerIpcMessageConsumer(remoteIpcMessageConsumer);
                  }
                }
              }
            }
            else if(remoteMatrixWrapper != null) {
              synchronized(lock) {
                remoteMatrixWrapper = null;
              }
              log.warn("proxy not available");
            }
          }
          catch(final SimonRemoteException x) {
            synchronized(lock) {
              remoteMatrixWrapper = null;
            }
            log.warn("proxy not available while performing lookup("
              + rmiRegistryHost + ", " + rmiRegistryPort + ", " + MatrixWrapperProxy.RmiRegistryName + ")", x);
          }
          Thread.sleep(3000); // TODO cfg
        }
      }
      catch(final Exception x) { // also MalformedURLException
        log.error("unhandled exception in proxy lookup thread", x);
      }
      finally {
        log.info("proxy lookup thread ran out");
        running = 0;
      }
    }

Weiters dachte ich, die Verbindung sei stateless und der Kontext ist die Objektreferenz selbst.

Wie bist du denn darauf gekommen? HTTP ist stateless. Aber da muss man ja ständig eine neue Verbindung aufbauen. Wäre das SIMON ebenfalls so gestrickt wie HTTP, wäre es wohl nicht so performant und würde so weiteren Overhead mitbringen.

Die Verbindung muss mit "lookup" explizit aufgebaut und mit "release" wieder beendet werden.
Vielleicht sollte ich die Sache mit der stateful connection im Wiki etwas deutlicher machen....

Da der Server zu jeder Zeit restarted werden kann, somit eine neue Registry oder/und ein neues Remote-Objekt publiziert werden kann, und
die lookup Methode mit allem Notwendigen (Registry-server, -port, Objektname) argumentiert wird, bin ich wohl diesemTrugschluss erlegen.
Naja. Das kommt drauf an wie du deine Logik aufbauen auf SIMON strickst:

Wenn du keinerlei Callbacks benutzt und in deiner Programmlogik im Remote-Objekt stateless arbeitest, dann kannst du natürlich nach belieben die Registry runterfahren, hochfahren und den Client mit einem einfachen lookup wieder neu verbinden.
Du kannst auch deine Serverlogik so bauen, dass der Kontext, in dem sich der eingeloggte Client befindet, persistiert wird. Allerdings wird das beliebig komplex wenn du mit Callbacks arbeitest.

Welche Strategy ist notwendig, um die tote Session zu kicken und - wenn die Registry wieder erreichbar ist, ein neues Objekt zur
Verfügung steht - ein lebendes Objekt zu erhalten, bzw. wie sieht ein Reconnect aus?

Danke,
Christian

Eine tote Session ist tot. Da muss nix gekickt werden.
Du musst, wenn die Registry wieder da ist, einfach einen neuen lookup machen und per Programmlogik den Client wieder in den Zustand setzen in den du ihn gerne hättest.

Ganz vereinfacht könnte man sagen: SIMON ist wie ein Telefon:

Man kann jemanden Anrufen (ein lookup auf der Registry) und man kann sogar innerhalb eines Gesprächs weitere Gespräche tunneln ohne das ursprüngliche Gespräch zu stören(Callback-RemoteObjekte).

Bricht allerdings die Telefonleitung zusammen (session closed), so muss von Grund auf neu gewählt werden (lookup) und ebenso die getunnelten Gespräche müssen neu aufgebaut werden (Callback-Objekte neu übergeben/anfordern).

Will man nun das Gespräch beenden, so legt man den Hörer auf (release). Wenn man in rage ist kann man auch das Telefonkabel aus der Wand reißen (Anwendung hart terminieren). Beides führt zum selben Ergebnis: Die Verbindung wird unterbrochen.

- Alex
 

CHE123

Mitglied
Hallo Alex,

Eine tote Session ist tot. Da muss nix gekickt werden.
Du musst, wenn die Registry wieder da ist, einfach einen neuen lookup machen und per Programmlogik den Client wieder in den Zustand setzen in den du ihn gerne hättest.

da hab ich genau das Problem: ich mache ständig einen Lookup, aber dieser läuft schief, auch wenn die Registry wieder da ist un ein neues Object gebunden ist.
Habe mir erlaubt, kurz einen Test zu machen und den Source in der lookup() Methode zu ändern:

Java:
        ...
        logger.trace("+dispatcher.invokeLookup");
        MsgLookupReturn msg = null;
        try {
          msg = dispatcher.invokeLookup(session, remoteObjectName);
          logger.trace("-dispatcher.invokeLookup");
        }
        catch(de.root1.simon.exceptions.SessionException x) {
          logger.trace("-dispatcher.invokeLookup failed with " + x);
          synchronized(serverDispatcherRelation) {
            serverDispatcherRelation.clear();
          }
        }

        if (msg == null || msg.hasError()) {
            logger.trace("Lookup failed. Releasing dispatcher.");
            
        ...

dass das keine nachhaltige Lösung ist, ist mir klar, aber es funktioniert soweit, der Client erholt sich
nach einem Server Restart.

lg,
Christian
 
T

tuxedo

Gast
Welche Version benutzt du denn? 1.0.0? 1.1.0? oder was älteres?

Hört sich jetzt mal stark nach einem Bug an. Kannst du einen Reproducer basteln?

- Alex
 

CHE123

Mitglied
hi,

verwende 1.0.0, dachte das sei die letzgültige???:L

test ist supersimpel:

Java:
package test;

import de.root1.simon.SimonRemote;
import de.root1.simon.exceptions.SimonRemoteException;

public interface Server extends SimonRemote 
{
  void foo() throws SimonRemoteException;
}

Java:
package test;

import de.root1.simon.Registry;
import de.root1.simon.Simon;
import de.root1.simon.exceptions.SimonRemoteException;

public class ServerImpl implements Server
{
  public static void main(String[] args) throws Exception {
    final Server server = new ServerImpl();

    final Registry registry = Simon.createRegistry(22222);
    registry.rebind("server", server);
    System.err.println("Server up and running!");
  }

  public void foo() throws SimonRemoteException {
    System.err.println("foo()");
  }
}

Java:
package test;

import de.root1.simon.Simon;

public class Client
{
  public static void main(String[] args) throws Exception {
    Server server = null;
    for(;;) {
      try {
        server = (Server) Simon.lookup("127.0.0.1", 22222, "server");
        server.foo();
      }
      catch(final Exception x) {
        System.err.println(x);
        if(server != null) {
          try {
            Simon.release(server);
          }
          catch(final Exception xx) {
            System.err.println("release()" + xx);
          }
          finally {
            server = null;
          }
        }
      }
      Thread.sleep(1000);
    }
  }
}

lib gefällt mir sonst gut,

Danke,
Christian
 
T

tuxedo

Gast
Danke für den reproducer. Ich schau's mir gleich an. Wenn's tatsächlich ein Bug ist kommt's in das Service-Release 1.0.1 rein ...

1.0.0 ist die neuste stable-release.
Aktuell arbeite ich an 1.1.0 die ein paar neue Features und Erleichterungen mit sich bringt (SimonRemoteException ist da z.B. eine RuntimeException ...).
 

CHE123

Mitglied
hallo nochmal,

hab den test ein wenig modifizert, dann braucht man den server prozess nicht immer stoppen starten (Server interface bliebt selbiges):


Java:
package test;

import de.root1.simon.Registry;
import de.root1.simon.Simon;
import de.root1.simon.exceptions.SimonRemoteException;

public class ServerImpl implements Server
{
  static private class Client implements Runnable
  {
    public void run() {
      try {
        Server server = null;
        for(;;) {
          try {
            server = (Server) Simon.lookup("127.0.0.1", 22222, "server");
            server.foo();
          }
          catch(final Exception x) {
            System.err.println("client reported " + x);
            if(server != null) {
              try {
                Simon.release(server);
              }
              catch(final Exception xx) {
                System.err.println("relleas()" + xx);
              }
              finally {
                server = null;
              }
            }
          }
          Thread.sleep(1000);
        }
      }
      catch(final Exception x) {
        System.err.println("client ran out with " + x);
      }
    }
  }

  public static void main(String[] args) throws Exception {
    new Thread(new Client()).start();
    for(int i = 0; i < 10; i++) {
      final Server server = new ServerImpl();
      final Registry registry = Simon.createRegistry(22222);
      registry.rebind("server", server);
      System.err.println("Server up and running!");

      Thread.sleep(3000);
      try {
        registry.unbind("server");
      }
      catch(Exception x) {
        System.err.println("unbind server " + x);
      }
      try {
        registry.stop();
      }
      catch(Exception x) {
        System.err.println("stop registry " + x);
      }
      Thread.sleep(3000);
    }
    System.exit(0);
  }

  public void foo() throws SimonRemoteException {
    System.err.println("foo()");
  }
}

output ohne änderung:
Code:
Server up and running!
foo()
foo()
foo()
client reported de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, server)" on already closed session.
client reported de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, server)" on already closed session.
client reported de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, server)" on already closed session.
Server up and running!
client reported de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, server)" on already closed session.
client reported de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, server)" on already closed session.
client reported de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, server)" on already closed session.
client reported de.root1.simon.exceptions.SessionException: Cannot handle method call "Simon.lookup({...}, server)" on already closed session.


mit der änderung:
Code:
Server up and running!
foo()
foo()
foo()
client reported de.root1.simon.exceptions.LookupFailedException: ???
client reported de.root1.simon.exceptions.EstablishConnectionFailed: Could not establish connection to Connection[/127.0.0.1:22222]. Maybe host or network is down?
Server up and running!
foo()
foo()
foo()
client reported de.root1.simon.exceptions.LookupFailedException: ???
client reported de.root1.simon.exceptions.EstablishConnectionFailed: Could not establish connection to Connection[/127.0.0.1:22222]. Maybe host or network is down?
Server up and running!
foo()
foo()
foo()
client reported de.root1.simon.exceptions.LookupFailedException: ???

lg
 
T

tuxedo

Gast
Dein Reproducer ist nicht fehlerfrei...

Du rufst ein "release()" nur im Fehlerfall auf.

Läuft alles gut, holst du dir ein Remote-Objekt nach dem anderen. Und hier liegt der Hund zu 50% begraben:

Wenn du einen Client hast, der mehrere RemoteObjekte vom Server holt, dann wird nicht bei jedem Lookup zu ein und demselben Server eine neue Socketverbindung geöffnet, sondern die bereits existierende Socketverbindung wird mitbenutzt. Das spart die Zeit für den Verbindungsaufbau und Ressourcen. Performance-Einbußen konnte ich damit noch keine feststellen. Der Netzwerkkarte ist es egal ob die volle Bandbreite über einen Socket läuft, oder über mehrere.

SIMON zählt intern mit, wieviele Remoteobjekte über die eine Sessiuon genutzt werden. Erst wenn das letzte RemoteObjekt mit "release" freigelassen wurde geht der interne Zähler auf 0, und die Verbindung zum Server wird gekappt.
Rufst du nun niemals
Code:
Simon.release()
auf, so steigt der Zähler ins unermessliche, die Verbindung bleibt weiterhin erhalten und die ganzen notwendigen internen Threads und Sockets werden nicht aufgeräumt, was zur Folge hat, dass deine Anwendung ncith auf "natürlichem Weg" terminieren kann. Sie müsste mit
Code:
System.exit()
zum terminieren gezwungen werden.

Um dein Beispiel zu korrigieren, müsste man also im Server nach Zeile 34 ein
Code:
Simon.release(server);
einbauen.

Damit geht der reference-count immer schön brav auf 0 runter und die Verbindung wird beendet.
Da du aber nicht für jeden Call einen neuen Lookup brauchst, müsste der Reproducer den Lookup außerhalb der Endlosschleife absetzen und nur im Fehlerfall einen neuen Lookup machen.

Nun gut. Aber auch da ist dann noch ein Bug drin den ich mir gerade anschaue. Melde mich wieder wenn ich neues weiß.

- Alex
 
T

tuxedo

Gast
Sodele... jetzt hab ich's.

Ist kein wirklicher Bug in SIMON. Du hast's nur nicht korrekt benutzt.

Entweder du bastelst deinen Client so:

Java:
        for (;;) {
            try {
                server = (Server) Simon.lookup("127.0.0.1", 22222, "server");
                server.foo();
            } catch (final Exception x) {
                System.err.println(x);
                if (server != null) {
                    try {
                        System.out.println("releasing instance");
                        Simon.release(server);
                    } catch (final Exception xx) {
                        System.err.println("release()" + xx);
                    } finally {
                        server = null;
                    }
                }
            }
            Thread.sleep(1000);
            if (server != null) {
                Simon.release(server);
            }
        }

oder so

Java:
Server server = (Server) Simon.lookup("127.0.0.1", 22222, "server");
        for (;;) {
            try {
                server.foo();
            } catch (SimonRemoteException x) {
                Simon.release(server);
                server = (Server) Simon.lookup("127.0.0.1", 22222, "server");
            }
            Thread.sleep(1000);
        }

Wie ich aktuell gesehen hab, muss man eine defekte RemoteInstance doch noch mit release() aufräumen. Das wird aber in Zukunft wegfallen. Wenn man's trotzdem aufräumt wird das dann einfach ignoriert. Wird also keine Änderung im Code bei dir erfodern.

Wichtigste Regel ist eigentlich:

Wenn man ein lookup macht, muss man, wenn man das RemoteObjekt nicht mehr braucht, aufräumen.

Das mag in erster Linie (auch im direkten Vergleich zu RMI) als "überflüssig" oder "lästig" wirken, denn schließlich könnte sich die SIMON Implementierung ja darum kümmern.

ABER: Die Erfahrung (zumindest bei mir) hat gezeigt, dass der Release-Automatismus - den RMI bietet - bei Anwendungen die größer als die üblichen RMI-Hausaufgabenstellungen sind, nicht mehr durchschaubar ist und man doch wieder manuell RemoteObjekte freigibt (
Code:
unexportObject(...)
).

Analog zum Telefon: Wenn man fertig ist mit telefonieren legt man den Hörer ja auch nicht einfach neben das Telefon und läuft weg ;-) Getreu dem Motto: Es wird sich schon jemand drüm kümmern. :toll:

- Alex
 

CHE123

Mitglied
hi,

Läuft alles gut, holst du dir ein Remote-Objekt nach dem anderen. Und hier liegt der Hund zu 50% begraben

uiii, da bin ich folgendem Irrtum unterlegen: der hashCode des Objektes bleibt bei einem lookup() derselbe, solange kein rebind() des Objektes erfolgt, somit dachte ich, es sei auch das selbe Objekt;

verstehe ich richtig, dass wenn der counter auf 0 geht und die Connection geschlossen wird, danach ein neuerlicher lookup gut gehen würde - natürlich unter der Voraussetzung, dass registry und Object wieder vorhanden sind?

OK, werde mal grundsätzlich meinen Lookup-Thread so umbauen, dass er notifiziert wird, wenn ein
Objekt-Consumer einen Fehler registriert, erst dann soll der Thread einen lookup machen.

lg,
Christian

PS: warst inszwischen schneller... werd folgende Straetegie implementieren (wie lookup auf EJB): sobald ich es benötige, hole ich es mir und gibs nach der benutzung eben wieder frei. spricht da was dagegen?

lg,
Christian
 
T

tuxedo

Gast
hi,



uiii, da bin ich folgendem Irrtum unterlegen: der hashCode des Objektes bleibt bei einem lookup() derselbe, solange kein rebind() des Objektes erfolgt, somit dachte ich, es sei auch das selbe Objekt;

Hmm, muss ich nochmal rein schauen. Der Hash-Code sollte idealerweise solange gleich bleiben wie die Objekt-Instanz auf der Serverseite lebt. Egal ob zwischendrin die Registry beendet, neu erzeugt und wieder gestartet wurde, was ein erneutes bind() erfordert.

verstehe ich richtig, dass wenn der counter auf 0 geht und die Connection geschlossen wird, danach ein neuerlicher lookup gut gehen würde - natürlich unter der Voraussetzung, dass registry und Object wieder vorhanden sind?

Der Counter geht auf 0, wenn alle RemotObjekte mit release() freigelassen wurden. Dann wird die Verbindung intern getrennt. Raucht die Verbindung ab, solltest du sicherheitshalber trotzdem ein release() aufrufen.
Ein Lookup geht dann gut, wenn

a) noch gar keine Verbindung da ist, die Registry aber auf eine Verbindung wartet und das entsprechende RemoteObjekt anbietet
b) wenn eine funktionierende Verbindung bereits vorhanden ist und das entsprechende RemoteObjekt angeboten wird.

OK, werde mal grundsätzlich meinen Lookup-Thread so umbauen, dass er notifiziert wird, wenn ein
Objekt-Consumer einen Fehler registriert, erst dann soll der Thread einen lookup machen.

lg,
Christian

Ideale Vorgehensweise:
* Beim Programmstart einen lookup machen.
* Sollte es während des Programmablaufs zu einem Verbindungsproblem kommen: relase() benutzen und erneuten lookup machen
* beim Programmende: release() benutzen

PS: warst inszwischen schneller... werd folgende Straetegie implementieren (wie lookup auf EJB): sobald ich es benötige, hole ich es mir und gibs nach der benutzung eben wieder frei. spricht da was dagegen?

Aus Performancegründen solltest du nicht für jeden Remote-Call erneut einen Lookup machen und anschließend mit release() aufräumen. Jeder lookup kostet etliche Bytes und Zeit. Ein einzelner Methodenaufruf würde somit einiges an Overhead mit sich bringen.

Wenn das in deinem Programm egal ist, weil ein Client eh nur in recht großen Abständen vereinzelte Calls an den Server absetzt, dann kannst du das wohl so machen. Für eine rege Kommunikation zwischen Client und Server mit einigen Clients solltest du aber obiger Empfehlung folgen.

btw: Für RMI gilt das gleiche ...

- Alex

P.S. Hab mal für 1.0.0 eine Bugfix Version gebaut 1.0.1-SNAPSHOT: --> Index of /content/repositories/snapshots/de/root1/simon/1.0.1-SNAPSHOT/

Falls ich noch was finde, landen die fixes in diesem Verzeichnis. Einfach aufs Dateidatum achten (oder Maven benutzen).
 
Zuletzt bearbeitet von einem Moderator:

CHE123

Mitglied
hi,

herzlichen Dank erstmal (Danke geklickt), werde mal das Erfahren umsetzen, die ein oder andere Frage
kann durchaus noch auftauchen:)

lg,
Christian
 
T

tuxedo

Gast
Klar hilft das. Weiterer Vorteil: Wenn du einen Bug findest kannst du auch ein neues Ticket aufmachen und wirst über den aktuellen Stand per Mail informiert.
 

Ähnliche Java Themen


Oben