File: process.rb

package info (click to toggle)
ruby-childprocess 4.1.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 376 kB
  • sloc: ruby: 2,541; makefile: 4
file content (184 lines) | stat: -rwxr-xr-x 5,037 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
require "java"

module ChildProcess
  module JRuby
    class Process < AbstractProcess
      def initialize(args)
        super(args)

        @pumps = []
      end

      def io
        @io ||= JRuby::IO.new
      end

      def exited?
        return true if @exit_code

        assert_started
        @exit_code = @process.exitValue
        stop_pumps

        true
      rescue java.lang.IllegalThreadStateException => ex
        log(ex.class => ex.message)
        false
      ensure
        log(:exit_code => @exit_code)
      end

      def stop(timeout = nil)
        assert_started

        @process.destroy
        wait # no way to actually use the timeout here..
      end

      def wait
        if exited?
          exit_code
        else
          @process.waitFor

          stop_pumps
          @exit_code = @process.exitValue
        end
      end

      # 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

      end

      private

      def launch_process(&blk)
        pb = java.lang.ProcessBuilder.new(@args)

        pb.directory java.io.File.new(@cwd || Dir.pwd)
        set_env pb.environment

        begin
          @process = pb.start
        rescue java.io.IOException => ex
          raise LaunchError, ex.message
        end

        setup_io
      end

      def setup_io
        if @io
          redirect(@process.getErrorStream, @io.stderr)
          redirect(@process.getInputStream, @io.stdout)
        else
          @process.getErrorStream.close
          @process.getInputStream.close
        end

        if duplex?
          io._stdin = create_stdin
        else
          @process.getOutputStream.close
        end
      end

      def redirect(input, output)
        if output.nil?
          input.close
          return
        end

        @pumps << Pump.new(input, output.to_outputstream).run
      end

      def stop_pumps
        @pumps.each { |pump| pump.stop }
      end

      def set_env(env)
        merged = ENV.to_hash

        @environment.each { |k, v| merged[k.to_s] = v }

        merged.each do |k, v|
          if v
            env.put(k, v.to_s)
          elsif env.has_key? k
            env.remove(k)
          end
        end

        removed_keys = env.key_set.to_a - merged.keys
        removed_keys.each { |k| env.remove(k) }
      end

      def create_stdin
        output_stream = @process.getOutputStream

        stdin = output_stream.to_io
        stdin.sync = true
        stdin.instance_variable_set(:@childprocess_java_stream, output_stream)

        class << stdin
          # The stream provided is a BufferedeOutputStream, so we
          # have to flush it to make the bytes flow to the process
          def __childprocess_flush__
            @childprocess_java_stream.flush
          end

          [:flush, :print, :printf, :putc, :puts, :write, :write_nonblock].each do |m|
            define_method(m) do |*args|
              super(*args)
              self.__childprocess_flush__
            end
          end
        end

        stdin
      end

    end # Process
  end # JRuby
end # ChildProcess