2017-04-07 2 views
0

J'essaie d'utiliser py4j pour ouvrir une passerelle que je peux utiliser pour passer des objets de Java en python. Lorsque j'essaie d'ouvrir une passerelle avec la fonction py4j launch_gateway, il ne semble pas se connecter correctement à ma classe Java. Cependant, lorsque je lance ma classe java dans la ligne de commande, puis que je me connecte en python avec JavaGateway, tout fonctionne comme prévu. Je voudrais pouvoir utiliser la méthode intégrée car je suis sûr que je ne suis pas responsable des choses qui ont déjà été considérées dans la conception de py4j, mais je ne suis pas sûr de ce que je fais mal.Py4j launch_gateway ne se connecte pas correctement

Disons que je voulais créer une passerelle vers la classe sandbox.demo.solver.UtilityReporterEntryPoint.class. Dans la ligne de commande, je peux le faire en exécutant la commande suivante:

java -cp /Users/grr/anaconda/share/py4j/py4j0.10.4.jar: sandbox.demo.solver.UtilityReporterEntryPoint py4j.GatewayServer 

Cela lance comme prévu et je peux utiliser les méthodes dans ma classe à partir de python après la connexion à la passerelle. Jusqu'ici tout va bien.

Ma compréhension de la documentation py4j me faire croire que je devrais faire ce qui suit pour lancer la passerelle en python:

port = launch_gateway(classpath='sandbox.demo.solver.UtilityReporterEntryPoint') 
params = GatewayParameters(port=port) 
gateway= JavaGateway(gateway_parameters=params) 

Je reçois aucune erreur lors de l'exécution de ces trois lignes, mais quand j'essaie d'accéder mes méthodes de classe java avec gateway.entry_point.someMethod() il échoue avec l'erreur suivante:

Py4JError: An error occurred while calling t.getReport. Trace: py4j.Py4JException: Target Object ID does not exist for this gateway :t at py4j.Gateway.invoke(Gateway.java:277) at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132) at py4j.commands.CallCommand.execute(CallCommand.java:79) at py4j.GatewayConnection.run(GatewayConnection.java:214) at java.lang.Thread.run(Thread.java:745)

il est évident que quelque chose n'est pas appelé correctement dans launch_gateway ou je le nourrir des informations erronées.

Dans le code source py4j pour launch_gateway, vous pouvez voir que, compte tenu des entrées que vous fournissez et de celles construites par la fonction, une commande est construite qui sera finalement appelée par subprocess.Popen. Ainsi, compte tenu de l'entrée passée à launch_gateway au-dessus de la commande passée en Popen serait:

command = ['java', '-classpath', '/Users/grr/anaconda/share/py4j/py4j0.10.4.jar:sandbox.demo.solver.UtilityReporterEntryPoint', 'py4j.GatewayServer', '0'] 

En passant cette commande pour Popen renvoie le port d'écoute comme prévu. Cependant, la connexion à ce port d'écoute ne permet toujours pas l'accès à mes méthodes de classe. Enfin, en passant la commande en tant que chaîne unique à Popen sans l'argument final ('0'), lance correctement une passerelle qui fonctionne à nouveau comme prévu. Ayant jeté un coup d'oeil sur le code source Java pour py4j.GatewayServer.class cela n'a aucun sens car la méthode principale semble indiquer que la classe devrait quitter avec le statut 1 si la longueur des arguments est 0.

À ce stade, je Je suis un peu perdu. Je peux me frayer un chemin dans une solution réalisable, mais comme je l'ai dit je suis sûr que cela ignore des aspects importants du comportement de la passerelle et je n'aime pas les solutions hacky. J'aimerais taguer @Barthelemy dans celui-ci, mais j'espère qu'il lit ceci. Merci d'avance pour votre aide.

EDIT

Pour l'instant, je suis en mesure de contourner ce problème avec les étapes suivantes.

  1. durée totale du forfait de projet, y compris toutes les dépendances externes dans un seul fichier jar magABM-all.jar, avec 'set' Main-Class à UtilityReporterEntryPoint.

  2. Inclure if...else bloc en ce qui concerne la présence de --die-on-exit exactement comme il est dans GatewayServer.java

  3. Utilisez subprocess.Popen pour appeler la commande pour exécuter le pot de projet.

UtilityReporterEntryPoint.java

public static void main(String[] args) throws IOException { 
    GatewayServer server = new GatewayServer(new UtilityReporterEntryPoint()); 
    System.out.println("Gateway Server Started"); 
    server.start(); 
    if (args[0].equals("--die-on-exit")) { 
    try { 
     BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in, Charset.forName("UTF-8"))); 
     stdin.readLine(); 
     System.exit(0); 
    } catch (java.io.IOException e) { 
     System.exit(1); 
    } 
    } 
} 

app.py

def setup_gateway() 
    """Launch a py4j gateway using UtilityReporterEntryPoint.""" 
    process = subprocess.Popen('java -jar magABM-all.jar --die-on-exit', shell=True) 
    time.sleep(0.5) 
    gateway = JavaGateway() 
    return gateway 

De cette façon, je peux toujours utiliser gateway.shutdown si nécessaire et si le processus de python qui commence la passerelle py4j meurt ou est fermé la passerelle sera fermée.

NB Je nullement considère cette solution finale py4j a été écrit par des individus beaucoup plus intelligents avec un objectif clair à l'esprit et je suis sûr qu'il ya un moyen de gérer ce flux de travail exact dans les limites de py4j . Ceci est juste une solution stopgap.

Répondre

1

Il y a quelques problèmes:

  1. Le paramètre CLASSPATH launch_gateway doit être un répertoire ou un fichier jar, pas un nom de classe. Par exemple, si vous souhaitez inclure des bibliothèques Java supplémentaires, vous devez les ajouter au paramètre classpath. L'erreur que vous recevez lorsque vous appelez le gateway.entry_point.someMethod() signifie que vous n'avez aucun point d'entrée. Lorsque vous appelez launch_gateway, la machine virtuelle Java est démarrée avec GatewayServer.main, qui lance un GatewayServer sans point d'entrée: GatewayServer server = new GatewayServer(null, port). Il n'est actuellement pas possible d'utiliser launch_gateway et de spécifier un point d'entrée. Lorsque vous démarrez la JVM avec java -cp /Users/grr/anaconda/share/py4j/py4j0.10.4.jar: sandbox.demo.solver.UtilityReporterEntryPoint py4j.GatewayServer, je crois que la JVM utilise UtilityReporterEntryPoint en tant que classe principale. Bien que vous n'ayez pas fourni le code, je suppose que cette classe a une méthode principale et qu'elle lance un GatewayServer avec une instance de UtilityReporterEntryPoint comme point d'entrée. Notez qu'il existe un espace entre le signe deux-points et le nom de la classe, donc UtilityReporterEntryPoint est considéré comme la classe principale et non comme faisant partie du chemin de classe.

+0

Merci pour la réponse. Je suppose que je suis un peu confus par le point 2. Voulez-vous dire qu'avec 'launch_gateway' il n'y a aucun moyen pour moi de lancer ma classe' UtilityReporterEntryPoint'? Si oui, y a-t-il un moyen pour moi de définir le point d'entrée après le lancement? – Grr

+0

Je voudrais toujours en savoir plus sur la façon d'utiliser correctement la méthode 'launch_gateway', mais pour l'instant je suis arrivé à une solution de contournement. Jetez un oeil à ma question mise à jour et laissez-moi savoir si cela a du sens. Comme je l'ai dit, je veux vraiment savoir comment le faire de la bonne façon et pas seulement une solution hacky. – Grr

+0

@Grr il n'y a aucun moyen de spécifier une classe principale avec launch_gateway, il n'est donc pas possible de spécifier un point d'entrée. L'objectif de launch_gateway est de démarrer le plus rapidement possible. Dès que vous avez besoin de plus d'options de configuration, il est préférable de lancer votre propre stratégie, telle que la fonction app.setup_gateway que vous avez créée. Vous pouvez également envisager d'ouvrir une demande de fonctionnalité pour prendre en charge la classe Main personnalisée, mais comme vous l'avez vu avec --die-on-exit, la classe Main devrait toujours suivre un certain protocole. – Barthelemy