Class: RightScale::RightPopen::Process

Inherits:
ProcessBase show all
Defined in:
lib/right_popen/linux/process.rb

Instance Attribute Summary

Attributes inherited from ProcessBase

#channels_to_finish, #pid, #start_time, #status, #status_fd, #stderr, #stdin, #stdout, #stop_time

Instance Method Summary collapse

Methods inherited from ProcessBase

#interrupt, #interrupted?, #needs_watching?, #safe_close_io, #size_limit_exceeded?, #sync_all, #sync_exit_with_target, #sync_pid_with_target, #timer_expired?

Constructor Details

#initialize(options = {}) ⇒ Process

Returns a new instance of Process.



35
36
37
# File 'lib/right_popen/linux/process.rb', line 35

def initialize(options={})
  super(options)
end

Instance Method Details

#alive?TrueClass|FalseClass

Determines if the process is still running.

Return

Returns:

  • (TrueClass|FalseClass)

    true if running



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/right_popen/linux/process.rb', line 43

def alive?
  unless @pid
    raise ::RightScale::RightPopen::ProcessError, 'Process not started'
  end
  unless @status
    begin
      ignored, status = ::Process.waitpid2(@pid, ::Process::WNOHANG)
      @status = status
    rescue
      wait_for_exit_status
    end
  end
  @status.nil?
end

#drain_all_upon_death?TrueClass|FalseClass

Linux must only read streams that are selected for read, even on child death. the issue is that a child process can (inexplicably) close one of the streams but continue writing to the other and this will cause the parent to hang reading the stream until the child goes away.

Return

Returns:

  • (TrueClass|FalseClass)

    true if draining all



65
66
67
# File 'lib/right_popen/linux/process.rb', line 65

def drain_all_upon_death?
  false
end

#signals_for_interruptArray

Returns escalating termination signals for this platform.

Returns:

  • (Array)

    escalating termination signals for this platform



70
71
72
# File 'lib/right_popen/linux/process.rb', line 70

def signals_for_interrupt
  ['INT', 'TERM', 'KILL']
end

#spawn(cmd, target) ⇒ TrueClass

spawns (forks) a child process using given command and handler target in linux-specific manner.

must be overridden and override must call super.

Parameters

Return

Parameters:

  • cmd (String|Array)

    as shell command or binary to execute

  • target (Object)

    that implements all handlers (see TargetProxy)

Returns:

  • (TrueClass)

    always true



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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/right_popen/linux/process.rb', line 104

def spawn(cmd, target)
  super(cmd, target)

  # garbage collect any open file descriptors from past executions before
  # forking to prevent them being inherited. also reduces memory footprint
  # since forking will duplicate everything in memory for child process.
  ::GC.start

  # create pipes.
  stdin_r, stdin_w = IO.pipe
  stdout_r, stdout_w = IO.pipe
  stderr_r, stderr_w = IO.pipe
  status_r, status_w = IO.pipe

  [stdin_r, stdin_w, stdout_r, stdout_w,
   stderr_r, stderr_w, status_r, status_w].each {|fdes| fdes.sync = true}

  @pid = ::Kernel::fork do
    begin
      stdin_w.close
      ::STDIN.reopen stdin_r

      stdout_r.close
      ::STDOUT.reopen stdout_w

      stderr_r.close
      ::STDERR.reopen stderr_w

      status_r.close
      status_w.fcntl(::Fcntl::F_SETFD, ::Fcntl::FD_CLOEXEC)

      unless @options[:inherit_io]
        ::ObjectSpace.each_object(IO) do |io|
          if ![::STDIN, ::STDOUT, ::STDERR, status_w].include?(io)
            # be careful to not allow streams in a bad state from the
            # parent process to prevent child process running.
            (io.close rescue nil) unless (io.closed? rescue true)
          end
        end
      end

      if group = get_group
        ::Process.egid = group
        ::Process.gid = group
      end

      if user = get_user
        ::Process.euid = user
        ::Process.uid = user
      end

      if umask = get_umask
        ::File.umask(umask)
      end

      # avoid chdir when pwd is already correct due to asinine printed
      # warning from chdir block for what is basically a no-op.
      working_directory = @options[:directory]
      if working_directory &&
         ::File.expand_path(working_directory) != ::File.expand_path(::Dir.pwd)
        ::Dir.chdir(working_directory)
      end

      environment_hash = {}
      environment_hash['LC_ALL'] = 'C' if @options[:locale]
      environment_hash.merge!(@options[:environment]) if @options[:environment]
      environment_hash.each do |key, value|
        ::ENV[key.to_s] = value.nil? ? nil: value.to_s
      end

      if cmd.kind_of?(Array)
        cmd = cmd.map { |c| c.to_s } #exec only likes string arguments
        exec(*cmd)
      else
        exec('sh', '-c', cmd.to_s)  # allows shell commands for cmd string
      end
      raise 'Unreachable code'
    rescue ::Exception => e
      # note that Marshal.dump/load isn't reliable for all kinds of
      # exceptions or else can be truncated by I/O buffering.
      error_data = {
        'class' => e.class.name,
        'message' => e.message,
        'backtrace' => e.backtrace
      }
      status_w.puts(::YAML.dump(error_data))
    end
    status_w.close
    exit!
  end

  stdin_r.close
  stdout_w.close
  stderr_w.close
  status_w.close
  @stdin = stdin_w
  @stdout = stdout_r
  @stderr = stderr_r
  @status_fd = status_r
  start_timer
  true
rescue
  # catch-all for failure to spawn process ensuring a non-nil status. the
  # PID most likely is nil but the exit handler can be invoked for async.
  safe_close_io
  @status = ::RightScale::RightPopen::ProcessStatus.new(@pid, 1)
  raise
end

#wait_for_exit_statusProcessStatus

blocks waiting for process exit status.

Return

Returns:



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/right_popen/linux/process.rb', line 78

def wait_for_exit_status
  unless @pid
    raise ::RightScale::RightPopen::ProcessError, 'Process not started'
  end
  unless @status
    begin
      ignored, status = ::Process.waitpid2(@pid)
      @status = status
    rescue
      # ignored
    end
  end
  @status
end