From 431dec44c619c23b58393aa167a855aea1f45b5c Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Sun, 20 Jan 2019 06:49:47 +0000 Subject: [PATCH 1/2] jruby: add support for Java >= 9 The JRuby implementation against Javas <= 8 relied on reflection into a package-private class `java.lang.UNIXProcess` to get the pid of a process running on UNIX/posix systems, but that class was removed in Java 9 which also adds `java.lang.Process#pid()`. At load, determine the effective major version of Java, and then define an implementation that uses modern Java's `java.lang.Process#pid()` where available, falling back to the original implementation on legacy Javas. --- lib/childprocess/jruby/process.rb | 57 ++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/lib/childprocess/jruby/process.rb b/lib/childprocess/jruby/process.rb index 3b976bb..cd80fad 100755 --- a/lib/childprocess/jruby/process.rb +++ b/lib/childprocess/jruby/process.rb @@ -46,24 +46,49 @@ def wait end end - # - # Only supported in JRuby on a Unix operating system, thanks to limitations - # in Java's classes - # - # @return [Integer] the pid of the process after it has started - # @raise [NotImplementedError] when trying to access pid on non-Unix platform - # - def pid - if @process.getClass.getName != "java.lang.UNIXProcess" - raise NotImplementedError, "pid is only supported by JRuby child processes on Unix" + # Implementation of ChildProcess::JRuby::Process#pid depends heavily on + # what Java SDK is being used; here, we look it up once at load, then + # define the method once to avoid runtime overhead. + normalised_java_version_major = java.lang.System.get_property("java.version") + .slice(/^(1\.)?([0-9]+)/, 2) + .to_i + if normalised_java_version_major >= 9 + + # On modern Javas, we can simply delegate through to `Process#pid`, + # which was introduced in Java 9. + # + # @return [Integer] the pid of the process after it has started + # @raise [NotImplementedError] when trying to access pid on platform for + # which it is unsupported in Java + def pid + @process.pid + rescue java.lang.UnsupportedOperationException => e + raise NotImplementedError, "pid is not supported on this platform: #{e.message}" + end + + else + + # On Legacy Javas, fall back to reflection. + # + # Only supported in JRuby on a Unix operating system, thanks to limitations + # in Java's classes + # + # @return [Integer] the pid of the process after it has started + # @raise [NotImplementedError] when trying to access pid on non-Unix platform + # + def pid + if @process.getClass.getName != "java.lang.UNIXProcess" + raise NotImplementedError, "pid is only supported by JRuby child processes on Unix" + end + + # About the best way we can do this is with a nasty reflection-based impl + # Thanks to Martijn Courteaux + # http://stackoverflow.com/questions/2950338/how-can-i-kill-a-linux-process-in-java-with-sigkill-process-destroy-does-sigter/2951193#2951193 + field = @process.getClass.getDeclaredField("pid") + field.accessible = true + field.get(@process) end - # About the best way we can do this is with a nasty reflection-based impl - # Thanks to Martijn Courteaux - # http://stackoverflow.com/questions/2950338/how-can-i-kill-a-linux-process-in-java-with-sigkill-process-destroy-does-sigter/2951193#2951193 - field = @process.getClass.getDeclaredField("pid") - field.accessible = true - field.get(@process) end private From c37ed888d7e63bbb3e7646e6b4b1dcab1c227069 Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Sun, 20 Jan 2019 14:48:31 +0000 Subject: [PATCH 2/2] add JRuby 9.2 and Java 11 to the ci mix The `JAVA_OPTS` environment variable is used by JRuby to launch the JVM with appropriate options; by adding specific `--add-opens` flags, we are able to enable the specific reflective access that is necessary to run in Javas >= 9. For ci, we set `JAVA_OPTS` with the contents of `JAVA_OPTS_FOR_SPECS` inside the `before_script` in order to avoid invoking all jruby JVMs with the flags. --- .travis.yml | 7 +++++++ README.md | 2 ++ 2 files changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0c38d5e..90b63aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,9 @@ before_install: - ruby -e "system('gem update --system') if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.3')" - gem install bundler --version '~> 1.17' +before_script: + - 'export JAVA_OPTS="${JAVA_OPTS_FOR_SPECS}"' + env: global: matrix: @@ -36,6 +39,10 @@ matrix: - rvm: jruby-9.1.9.0 - rvm: ruby-head - env: "CHILDPROCESS_POSIX_SPAWN=true" + include: + - rvm: jruby-9.2.5.0 + jdk: openjdk11 + env: "JAVA_OPTS_FOR_SPECS='--add-opens java.base/java.io=org.jruby.dist --add-opens java.base/sun.nio.ch=org.jruby.dist'" exclude: # Travis does not provide 1.9.3 on OSX - rvm: 1.9.3 diff --git a/README.md b/README.md index 80454d8..44bddbc 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,8 @@ ChildProcess.logger = logger ## Caveats * With JRuby on Unix, modifying `ENV["PATH"]` before using childprocess could lead to 'Command not found' errors, since JRuby is unable to modify the environment used for PATH searches in `java.lang.ProcessBuilder`. This can be avoided by setting `ChildProcess.posix_spawn = true`. +* With JRuby on Java >= 9, the JVM may need to be configured to allow JRuby to access neccessary implementations; this can be done by adding `--add-opens java.base/java.io=org.jruby.dist` and `--add-opens java.base/sun.nio.ch=org.jruby.dist` to the `JAVA_OPTS` environment variable that is used by JRuby when launching the JVM. + # Implementation