Skip to content

Commit

Permalink
Merge pull request #105 from JoshRosen/gateway-launcher
Browse files Browse the repository at this point in the history
Add method to launch Java GatewayServer from Python
  • Loading branch information
bartdag committed Aug 4, 2012
2 parents 9eb8663 + 9baf755 commit 60efd39
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 1 deletion.
44 changes: 44 additions & 0 deletions py4j-java/src/py4j/GatewayServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
package py4j;

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
Expand Down Expand Up @@ -582,4 +584,46 @@ protected void startSocket() throws Py4JNetworkException {
throw new Py4JNetworkException(e);
}
}

/**
* <p>
* Main method to start a local GatewayServer on a given port.
* The listening port is printed to stdout so that clients can start
* servers on ephemeral ports.
* </p>
*/
public static void main(String[] args) {
int port;
boolean dieOnBrokenPipe = false;
String usage = "usage: [--die-on-broken-pipe] port";
if (args.length == 0) {
System.err.println(usage);
System.exit(1);
} else if (args.length == 2) {
if (!args[0].equals("--die-on-broken-pipe")) {
System.err.println(usage);
System.exit(1);
}
dieOnBrokenPipe = true;
}
port = Integer.parseInt(args[args.length - 1]);
GatewayServer gatewayServer = new GatewayServer(null, port);
gatewayServer.start();
/* Print out the listening port so that clients can discover it. */
int listening_port = gatewayServer.getListeningPort();
System.out.println("" + listening_port);

if (dieOnBrokenPipe) {
/* Exit on EOF or broken pipe. This ensures that the server dies
* if its parent program dies. */
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
try {
stdin.readLine();
System.exit(0);
} catch (java.io.IOException e) {
System.exit(1);
}
}
}
}
3 changes: 2 additions & 1 deletion py4j-python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

DOC_DIR = 'doc'
DIST_DIR = 'dist'
VERSION = '0.7'
execfile("src/py4j/version.py")
VERSION = __version__
RELEASE = 'py4j-' + VERSION
JAR_FILE = 'py4j' + VERSION + '.jar'
JAR_FILE_PATH = os.path.join('py4j-java',JAR_FILE)
Expand Down
79 changes: 79 additions & 0 deletions py4j-python/src/py4j/java_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@
"""
from __future__ import unicode_literals, absolute_import

import atexit
from collections import deque
import logging
import os
from pydoc import ttypager
import socket
from subprocess import Popen, PIPE
import sys
from threading import Thread, RLock
import weakref

from py4j.compat import range, hasattr2
from py4j.finalizer import ThreadSafeFinalizer
from py4j.protocol import *
from py4j.version import __version__


class NullHandler(logging.Handler):
Expand Down Expand Up @@ -59,6 +63,55 @@ def java_import(jvm_view, import_str):
return return_value


def launch_gateway(port=0, jarpath="", classpath="", javaopts=[],
die_on_exit=False):
"""Launch a `Gateway` in a new Java process.
:param port: the port to launch the Java Gateway on. If no port is
specified then an ephemeral port is used.
:param jarpath: the path to the Py4J jar. Only necessary if the jar
was installed at a non-standard location or if Python is using
a different `sys.prefix` than the one that Py4J was installed
under.
:param classpath: the classpath used to launch the Java Gateway.
:param javaopts: an array of extra options to pass to Java (the classpath
should be specified using the `classpath` parameter, not `javaopts`.)
:param die_on_exit: if `True`, the Java gateway process will die when
this Python process exits or is killed.
:rtype: the port number of the `Gateway` server.
"""
if not jarpath:
# Try to locate the jar relative to the Py4J source directory, in case
# we're testing a source checkout.
jarpath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'../../../py4j-java/py4j' + __version__ + '.jar')
try:
with open(jarpath) as f: pass
except IOError:
# Otherwise, try the default location.
jarpath = os.path.join(sys.prefix, "share/py4j/py4j" +
__version__ + ".jar")

# Fail if the jar does not exist.
with open(jarpath) as f: pass

# Launch the server in a subprocess.
classpath = ":".join((jarpath, classpath))
command = ["java", "-classpath", classpath] + javaopts + \
["py4j.GatewayServer"]
if die_on_exit:
command.append("--die-on-broken-pipe")
command.append(str(port))
logger.debug("Lauching gateway with command {0}".format(command))
proc = Popen(command, stdout=PIPE, stdin=PIPE)

# Determine which port the server started on (needed to support
# ephemeral ports)
_port = int(proc.stdout.readline())
return _port


def get_field(java_object, field_name):
"""Retrieves the field named `field_name` from the `java_object`.
Expand Down Expand Up @@ -853,6 +906,32 @@ def help(self, var, pattern=None, short_name=True, display=True):
else:
return help_page

@classmethod
def launch_gateway(cls, port=0, jarpath="", classpath="", javaopts=[],
die_on_exit=False):
"""Launch a `Gateway` in a new Java process and create a default
:class:`JavaGateway <py4j.java_gateway.JavaGateway>` to connect to
it.
:param port: the port to launch the Java Gateway on. If no port is
specified then an ephemeral port is used.
:param jarpath: the path to the Py4J jar. Only necessary if the jar
was installed at a non-standard location or if Python is using
a different `sys.prefix` than the one that Py4J was installed
under.
:param classpath: the classpath used to launch the Java Gateway.
:param javaopts: an array of extra options to pass to Java (the classpath
should be specified using the `classpath` parameter, not `javaopts`.)
:param die_on_exit: if `True`, the Java gateway process will die when
this Python process exits or is killed.
:rtype: a :class:`JavaGateway <py4j.java_gateway.JavaGateway>`
connected to the `Gateway` server.
"""
_port = launch_gateway(port, jarpath, classpath, javaopts, die_on_exit)
gateway = JavaGateway(GatewayClient(port=_port))
return gateway


# CALLBACK SPECIFIC

Expand Down
13 changes: 13 additions & 0 deletions py4j-python/src/py4j/tests/java_gateway_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,5 +727,18 @@ def testStress(self):
self.assertTrue(runner3.ok)


class GatewayLauncherTest(unittest.TestCase):
def tearDown(self):
safe_shutdown(self)

def testDefaults(self):
self.gateway = JavaGateway.launch_gateway()
self.assertTrue(self.gateway.jvm)

def testJavaopts(self):
self.gateway = JavaGateway.launch_gateway(javaopts=["-Xmx64m"])
self.assertTrue(self.gateway.jvm)


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions py4j-python/src/py4j/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.7'
2 changes: 2 additions & 0 deletions py4j-web/py4j_java_gateway.rst
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ The following functions get be used to import packages or to get a particular fi

.. autofunction:: py4j.java_gateway.java_import

.. autofunction:: py4j.java_gateway.launch_gateway

.. autofunction:: py4j.java_gateway.get_field

.. autofunction:: py4j.java_gateway.set_field
Expand Down

0 comments on commit 60efd39

Please sign in to comment.