File: process_spawn_process.rb

package info (click to toggle)
ruby-childprocess 5.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 260 kB
  • sloc: ruby: 1,285; makefile: 4
file content (127 lines) | stat: -rw-r--r-- 2,848 bytes parent folder | download
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
require_relative 'abstract_process'

module ChildProcess
  class ProcessSpawnProcess < AbstractProcess
    attr_reader :pid

    def exited?
      return true if @exit_code

      assert_started
      pid, status = ::Process.waitpid2(@pid, ::Process::WNOHANG | ::Process::WUNTRACED)
      pid = nil if pid == 0 # may happen on jruby

      log(:pid => pid, :status => status)

      if pid
        set_exit_code(status)
      end

      !!pid
    rescue Errno::ECHILD
      # may be thrown for detached processes
      true
    end

    def wait
      assert_started

      if exited?
        exit_code
      else
        _, status = ::Process.waitpid2(@pid)

        set_exit_code(status)
      end
    end

    private

    def launch_process
      environment = {}
      @environment.each_pair do |key, value|
        key = key.to_s
        value = value.nil? ? nil : value.to_s

        if key.include?("\0") || key.include?("=") || value.to_s.include?("\0")
          raise InvalidEnvironmentVariable, "#{key.inspect} => #{value.to_s.inspect}"
        end
        environment[key] = value
      end

      options = {}

      options[:out] = io.stdout ? io.stdout.fileno : File::NULL
      options[:err] = io.stderr ? io.stderr.fileno : File::NULL

      if duplex?
        reader, writer = ::IO.pipe
        options[:in] = reader.fileno
        unless ChildProcess.windows?
          options[writer.fileno] = :close
        end
      end

      if leader?
        if ChildProcess.windows?
          options[:new_pgroup] = true
        else
          options[:pgroup] = true
        end
      end

      options[:chdir] = @cwd if @cwd

      if @args.size == 1
        # When given a single String, Process.spawn would think it should use the shell
        # if there is any special character in it. However,  ChildProcess should never
        # use the shell. So we use the [cmdname, argv0] form to force no shell.
        arg = @args[0]
        args = [[arg, arg]]
      else
        args = @args
      end

      begin
        @pid = ::Process.spawn(environment, *args, options)
      rescue SystemCallError => e
        raise LaunchError, e.message
      end

      if duplex?
        io._stdin = writer
        reader.close
      end

      ::Process.detach(@pid) if detach?
    end

    def set_exit_code(status)
      @exit_code = status.exitstatus || status.termsig
    end

    def send_term
      send_signal 'TERM'
    end

    def send_kill
      send_signal 'KILL'
    end

    def send_signal(sig)
      assert_started

      log "sending #{sig}"
      if leader?
        if ChildProcess.unix?
          ::Process.kill sig, -@pid # negative pid == process group
        else
          output = `taskkill /F /T /PID #{@pid}`
          log output
        end
      else
        ::Process.kill sig, @pid
      end
    end
  end
end