Skip to content

Commit

Permalink
[GR-20446] Implement Process::Status.wait
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/2736
  • Loading branch information
bjfish committed Jun 18, 2021
2 parents 9ed5f9c + eaf1a36 commit 2b88831
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Bug fixes:

Compatibility:

* Implement `Process::Status.wait` (#2378).

Performance:

Expand Down
103 changes: 103 additions & 0 deletions spec/ruby/core/process/status/wait_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
require_relative '../../../spec_helper'
require_relative '../fixtures/common'

ruby_version_is "3.0" do
describe "Process::Status.wait" do
ProcessSpecs.use_system_ruby(self)

before :all do
begin
leaked = Process.waitall
# Ruby-space should not see PIDs used by mjit
raise "subprocesses leaked before wait specs: #{leaked}" unless leaked.empty?
rescue NotImplementedError
end
end

it "returns a status with pid -1 if there are no child processes" do
Process::Status.wait.pid.should == -1
end

platform_is_not :windows do
it "returns a status with its child pid" do
pid = Process.spawn(ruby_cmd('exit'))
status = Process::Status.wait
status.should be_an_instance_of(Process::Status)
status.pid.should == pid
end

it "should not set $? to the Process::Status" do
pid = Process.spawn(ruby_cmd('exit'))
status = Process::Status.wait
$?.should_not equal(status)
end

it "should not change the value of $?" do
pid = Process.spawn(ruby_cmd('exit'))
Process.wait
status = $?
Process::Status.wait
status.should equal($?)
end

it "waits for any child process if no pid is given" do
pid = Process.spawn(ruby_cmd('exit'))
Process::Status.wait.pid.should == pid
-> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
end

it "waits for a specific child if a pid is given" do
pid1 = Process.spawn(ruby_cmd('exit'))
pid2 = Process.spawn(ruby_cmd('exit'))
Process::Status.wait(pid2).pid.should == pid2
Process::Status.wait(pid1).pid.should == pid1
-> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
-> { Process.kill(0, pid2) }.should raise_error(Errno::ESRCH)
end

it "coerces the pid to an Integer" do
pid1 = Process.spawn(ruby_cmd('exit'))
Process::Status.wait(mock_int(pid1)).pid.should == pid1
-> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
end

# This spec is probably system-dependent.
it "waits for a child whose process group ID is that of the calling process" do
pid1 = Process.spawn(ruby_cmd('exit'), pgroup: true)
pid2 = Process.spawn(ruby_cmd('exit'))

Process::Status.wait(0).pid.should == pid2
Process::Status.wait.pid.should == pid1
end

# This spec is probably system-dependent.
it "doesn't block if no child is available when WNOHANG is used" do
read, write = IO.pipe
pid = Process.fork do
read.close
Signal.trap("TERM") { Process.exit! }
write << 1
write.close
sleep
end

Process::Status.wait(pid, Process::WNOHANG).should be_nil

# wait for the child to setup its TERM handler
write.close
read.read(1)
read.close

Process.kill("TERM", pid)
Process::Status.wait.pid.should == pid
end

it "always accepts flags=0" do
pid = Process.spawn(ruby_cmd('exit'))
Process::Status.wait(-1, 0).pid.should == pid
-> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
end
end
end
end

1 change: 1 addition & 0 deletions spec/tags/core/process/status/wait_tags.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fails:Process::Status.wait doesn't block if no child is available when WNOHANG is used
1 change: 1 addition & 0 deletions spec/truffleruby.mspec
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class MSpecScript
set :next, %w[
spec/ruby/core/mutex/owned_spec.rb
spec/ruby/core/fiber/raise_spec.rb
spec/ruby/core/process/status/wait_spec.rb
]

set :tags_patterns, [
Expand Down
29 changes: 7 additions & 22 deletions src/main/ruby/truffleruby/core/process.rb
Original file line number Diff line number Diff line change
Expand Up @@ -601,26 +601,8 @@ def self.last_status
# TODO: Support other options such as WUNTRACED? --rue
#
def self.wait2(input_pid=-1, flags=nil)
input_pid = Truffle::Type.coerce_to input_pid, Integer, :to_int
flags ||= 0

FFI::MemoryPointer.new(:int, 4) do |ptr|
pid = Truffle::POSIX.truffleposix_waitpid(input_pid, flags, ptr)
if pid == 0
return nil
elsif pid == -1
Errno.handle "No child process: #{input_pid}"
else
ints = ptr.read_array_of_int(4)
exitcode, termsig, stopsig, = ints.map { |e| e == -1000 ? nil : e }
raw_status = ints.last

status = Process::Status.new(pid, exitcode, termsig, stopsig, raw_status)
Primitive.thread_set_return_code status

[pid, status]
end
end
status = Truffle::ProcessOperations.wait(input_pid, flags, true, true)
[status.pid, status]
end

#
Expand Down Expand Up @@ -650,8 +632,7 @@ def self.waitall
end

def self.wait(pid=-1, flags=nil)
pid, _status = Process.wait2(pid, flags)
pid
Truffle::ProcessOperations.wait(pid, flags, true, true)&.pid
end

class << self
Expand Down Expand Up @@ -781,6 +762,10 @@ def to_s
def inspect
"#<Process::Status: #{self}>"
end

def self.wait(pid=-1, flags=nil)
Truffle::ProcessOperations.wait(pid, flags, false, false)
end
end

module Sys
Expand Down
23 changes: 23 additions & 0 deletions src/main/ruby/truffleruby/core/truffle/process_operations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,29 @@ def self.spawn(*args)
end
end

def self.wait(input_pid, flags, set_status, raise_on_error)
input_pid = Truffle::Type.coerce_to input_pid, Integer, :to_int
flags ||= 0

FFI::MemoryPointer.new(:int, 4) do |ptr|
pid = Truffle::POSIX.truffleposix_waitpid(input_pid, flags, ptr)
if pid == 0
return nil
elsif raise_on_error && pid == -1
Errno.handle "No child process: #{input_pid}"
else
ints = ptr.read_array_of_int(4)
exitcode, termsig, stopsig, = ints.map { |e| e == -1000 ? nil : e }
raw_status = ints.last

status = Process::Status.new(pid, exitcode, termsig, stopsig, raw_status)
Primitive.thread_set_return_code status if set_status

status
end
end
end

class Execute
def initialize
@options = {}
Expand Down

0 comments on commit 2b88831

Please sign in to comment.