Skip to content

Commit

Permalink
Super-fancy refactor of Process::Status
Browse files Browse the repository at this point in the history
Backwards-compatible!

Based on crystal-lang#8381 (comment)
  • Loading branch information
oprypin committed Apr 13, 2020
1 parent cef5b0a commit d6fef93
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 53 deletions.
2 changes: 1 addition & 1 deletion src/crystal/system/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct Crystal::System::Process
# def pid : Int

# Waits until the process finishes and returns its status code
# def wait : Int
# def wait : ::Process::Status

# Whether the process is still registered in the system.
# def exists? : Bool
Expand Down
18 changes: 17 additions & 1 deletion src/crystal/system/unix/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,23 @@ struct Crystal::System::Process
end

def wait
@channel.receive
status = @channel.receive
case status & 0xff
when 0
::Process::Status::Exited.new((status & 0xff00) >> 8)
when 0x01..0x7e
::Process::Status::Signaled.new(status & 0x7f, core_dumped: false)
when 0x7f
::Process::Status::Stopped.new((status & 0xff00) >> 8)
when 0x81..0xfe
::Process::Status::Signaled.new(status & 0x7f, core_dumped: true)
else
if status == 0xffff
::Process::Status::Continued.new
else
raise ::Process::Status::UnexpectedStatusError.new(status)
end
end
end

def exists?
Expand Down
4 changes: 2 additions & 2 deletions src/crystal/system/win32/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ struct Crystal::System::Process
raise RuntimeError.from_winerror("GetExitCodeProcess")
end
if exit_code == LibC::STILL_ACTIVE
raise "BUG: process still active"
raise ::Process::Status::UnexpectedStatusError.new(exit_code, "The process might still be active")
end
exit_code
::Process::Status::Exited.new(exit_code.to_i32!)
end

def exists?
Expand Down
3 changes: 2 additions & 1 deletion src/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Process
record Tms, utime : Float64, stime : Float64, cutime : Float64, cstime : Float64
end

require "process/status"
require "crystal/system/process"

class Process
Expand Down Expand Up @@ -303,7 +304,7 @@ class Process
end
@wait_count = 0

Process::Status.new(@process_info.wait)
@process_info.wait
ensure
close
end
Expand Down
125 changes: 77 additions & 48 deletions src/process/status.cr
Original file line number Diff line number Diff line change
@@ -1,77 +1,106 @@
# The status of a terminated process. Returned by `Process#wait`.
class Process::Status
{% if flag?(:win32) %}
# :nodoc:
def initialize(@exit_status : UInt32)
end
{% else %}
# :nodoc:
def initialize(@exit_status : Int32)
end
{% end %}

# Returns `true` if the process was terminated by a signal.
abstract struct Process::Status
# Returns `Signaled` if the process was terminated by a signal, otherwise `nil`.
def signal_exit? : Bool
{% if flag?(:unix) %}
# define __WIFSIGNALED(status) (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
((LibC::SChar.new(@exit_status & 0x7f) + 1) >> 1) > 0
{% else %}
false
{% end %}
self.as? Signaled
end

# Returns `true` if the process terminated normally.
def normal_exit? : Bool
{% if flag?(:unix) %}
# define __WIFEXITED(status) (__WTERMSIG(status) == 0)
signal_code == 0
{% else %}
true
{% end %}
# Returns `Exited` if the process terminated normally, otherwise `nil`.
def normal_exit? : Exited?
self.as? Exited
end

# If `signal_exit?` is `true`, returns the *Signal* the process
# received and didn't handle. Will raise if `signal_exit?` is `false`.
#
# Available only on Unix-like operating systems.
def exit_signal : Signal
{% if flag?(:unix) %}
Signal.from_value(signal_code)
{% else %}
raise NotImplementedError.new("Process::Status#exit_signal")
{% end %}
self.as(Signaled).signal
end

# If `normal_exit?` is `true`, returns the exit code of the process.
#
# * POSIX: Otherwise, returns a placeholder negative value related to the exit signal.
# * Windows: The exit is always "normal" but the exit codes can be negative
# (wrapped around after `Int32::MAX`; take them modulo 2**32 if the actual value is needed)
def exit_code : Int32
if !normal_exit?
return -signal_code
end
{% if flag?(:unix) %}
# define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)
(@exit_status & 0xff00) >> 8
{% else %}
exit_status
{% end %}
end
abstract def exit_code : Int32

# Returns `true` if the process exited normally with an exit code of `0`.
def success? : Bool
normal_exit? && exit_code == 0
false
end

struct Exited < Status
# :nodoc:
def initialize(@exit_code : Int32)
end

getter exit_code : Int32

def success? : Bool
exit_code == 0
end
end

struct Signaled < Status
# :nodoc:
def initialize(signal_code : Int32, @core_dumped : Bool)
@signal_code = UInt8.new(signal_code)
end

def signal : Signal
Signal.new(signal_code)
end

def signal_code : Int32
@signal_code.to_i32
end

def exit_code : Int32
-signal_code
end

getter? core_dumped : Bool
end

private def signal_code
# define __WTERMSIG(status) ((status) & 0x7f)
@exit_status & 0x7f
struct Stopped < Status
# :nodoc:
def initialize(@stop_signal : Int32)
end

getter stop_signal : Int32

def exit_code : Int32
-0x7F
end
end

# Platform-specific exit status code, which usually contains either the exit code or a termination signal.
struct Continued < Status
def exit_code : Int32
-0x7F
end
end

# POSIX-specific exit status code, a complex bitmask of an exit code or termination signal.
@[Deprecated("Use `Process::Status#exit_code`")]
def exit_status : Int32
@exit_status.to_i32!
case self
when Exited
self.exit_code << 8
when Signaled
self.signal_code + (core_dumped? ? 0x80 : 0)
when Stopped
(self.stop_signal << 8) + 0x7f
when Continued
0xffff
end
end

class UnexpectedStatusError < RuntimeError
def initialize(@exit_status : Int32, msg = "The process exited with an unknown status")
super("#{msg} (#{@exit_status})")
end

getter exit_status : Int32
end
end

0 comments on commit d6fef93

Please sign in to comment.