Py4j launch_gateway 连接不正确

Py4j launch_gateway not connecting properly

我正在尝试使用 py4j 打开一个网关,我可以使用该网关将对象从 java 传递到 python。当我尝试使用 py4j 函数 launch_gateway it does not seem to properly connect to my Java class. However, when I launch my java class in the command line and then connect to it in python using JavaGateway 打开网关时,一切都按预期工作。我希望能够使用内置方法,因为我确信我没有考虑 py4j 设计中已经考虑过的事情,但我只是不确定我做错了什么。

假设我想创建一个通往 class sandbox.demo.solver.UtilityReporterEntryPoint.class 的网关。在命令行中,我可以通过执行以下命令来做到这一点:

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

这按预期启动,我可以在连接到网关后从 python 中使用我的 class 中的方法。到目前为止一切顺利。

我对 py4j 文档的理解使我相信我应该执行以下操作以在 python 中启动网关:

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

执行这三行时我没有收到任何错误,但是当我尝试使用 gateway.entry_point.someMethod() 访问我的 java class 方法时,它失败并出现以下错误:

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)

显然 launch_gateway 中有些东西没有被正确调用,或者我给它提供了错误的信息。

launch_gateway you can see that given the inputs you provide and those constructed by the function, a command is constructed that eventually gets called by subprocess.Popen 的 py4j 源代码中。因此,给定传递给 launch_gateway 以上的输入,传递给 Popen 的命令将是:

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

按预期将此命令传递到 Popen returns 侦听端口。但是,连接到这个监听端口仍然不允许访问我的 class 方法。

最后,将命令作为单个字符串传递给没有最后一个参数(“0”)的 Popen,正确启动网关,该网关再次按预期运行。浏览了 py4j.GatewayServer.class 的 Java 源代码后,这没有任何意义,因为主要方法似乎表明如果参数长度为 0,class 应该以状态 1 退出。

此时我有点不知所措。我可以找到一个可行的解决方案,但正如我所说,我确信这会忽略网关行为的重要方面,而且我不喜欢 hacky 解决方案。我很想在这一篇中标记@Barthelemy,但希望他能读到这篇文章。在此先感谢您的帮助。

编辑

目前我已经能够通过以下步骤解决这个问题。

  1. 将包括所有外部依赖项在内的整个项目打包到一个 jar 文件中 magABM-all.jar,'Main-Class' 设置为 UtilityReporterEntryPoint.

  2. 包含关于 --die-on-exitif...else 块,就像它在 GatewayServer.java

  3. 中一样
  4. 使用subprocess.Popen调用命令到运行项目jar。

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

这样我仍然可以在必要时使用 gateway.shutdown,如果启动 py4j 网关的 python 进程终止或关闭,网关将关闭。

N.B 我绝不认为这是最终解决方案,因为 py4j 是由更聪明的人编写的,目的明确,我相信是一种在 py4j 的范围内管理这个确切工作流的方法。这只是权宜之计。

有几个问题:

  1. launch_gateway中的classpath参数应该是目录或jar文件,而不是class名称。例如,如果你想包含额外的 Java 库,你可以将它们添加到 classpath 参数。

  2. 调用 gateway.entry_point.someMethod() 时收到的错误意味着您没有入口点。当您调用 launch_gateway 时,JVM 以 GatewayServer.main 启动,它启动一个没有入口点的 GatewayServer:GatewayServer server = new GatewayServer(null, port)。目前无法使用 launch_gateway 并指定入口点。

  3. 当您使用 java -cp /Users/grr/anaconda/share/py4j/py4j0.10.4.jar: sandbox.demo.solver.UtilityReporterEntryPoint py4j.GatewayServer 启动 JVM 时,我相信 JVM 使用 UtilityReporterEntryPoint 作为主要 class。尽管您没有提供代码,但我假设此 class 有一个 main 方法,并且它启动一个 GatewayServer 并以 UtilityReporterEntryPoint 实例作为入口点。请注意,冒号和 class 名称之间有一个空格,因此 UtilityReporterEntryPoint 被视为主要 class 而不是 class 路径的一部分。