Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Process.ppid on Windows #13140

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -358,21 +358,31 @@ describe Process do

typeof(Process.new(*standing_command).terminate(graceful: false))

pending_win32 ".exists?" do
# We can't reliably check whether it ever returns false, since we can't predict
# how PIDs are used by the system, a new process might be spawned in between
# reaping the one we would spawn and checking for it, using the now available
# pid.
Process.exists?(Process.ppid).should be_true
it ".exists?" do
# On Windows killing a parent process does not reparent its children to
# another existing process, so the following isn't guaranteed to work
{% unless flag?(:win32) %}
# We can't reliably check whether it ever returns false, since we can't predict
# how PIDs are used by the system, a new process might be spawned in between
# reaping the one we would spawn and checking for it, using the now available
# pid.
Process.exists?(Process.ppid).should be_true
{% end %}

process = Process.new(*standing_command)
process.exists?.should be_true
process.terminated?.should be_false

# Kill, zombie now
process.terminate
process.exists?.should be_true
process.terminated?.should be_false
{% if flag?(:win32) %}
# Windows has no concept of zombie processes
process.exists?.should be_false
process.terminated?.should be_true
{% else %}
process.exists?.should be_true
process.terminated?.should be_false
{% end %}

# Reap, gone now
process.wait
Expand Down
26 changes: 24 additions & 2 deletions src/crystal/system/win32/process.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "c/processthreadsapi"
require "c/handleapi"
require "c/synchapi"
require "c/tlhelp32"
require "process/shell"
require "crystal/atomic_semaphore"

Expand Down Expand Up @@ -71,7 +72,28 @@ struct Crystal::System::Process
end

def self.ppid
raise NotImplementedError.new("Process.ppid")
pid = self.pid
each_entry do |pe|
return pe.th32ParentProcessID if pe.th32ProcessID == pid
end
raise RuntimeError.new("Cannot locate current process")
end

private def self.each_entry(&)
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
h = LibC.CreateToolhelp32Snapshot(LibC::TH32CS_SNAPPROCESS, 0)
raise RuntimeError.from_winerror("CreateToolhelp32Snapshot") if h == LibC::INVALID_HANDLE_VALUE

begin
pe = LibC::PROCESSENTRY32W.new(dwSize: sizeof(LibC::PROCESSENTRY32W))
if LibC.Process32FirstW(h, pointerof(pe)) != 0
while true
yield pe
break if LibC.Process32NextW(h, pointerof(pe)) == 0
end
end
ensure
LibC.CloseHandle(h)
end
end

def self.signal(pid, signal)
Expand Down Expand Up @@ -129,7 +151,7 @@ struct Crystal::System::Process

def self.exists?(pid)
handle = LibC.OpenProcess(LibC::PROCESS_QUERY_INFORMATION, 0, pid)
return false if handle.nil?
return false unless handle
begin
if LibC.GetExitCodeProcess(handle, out exit_code) == 0
raise RuntimeError.from_winerror("GetExitCodeProcess")
Expand Down
20 changes: 20 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/tlhelp32.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
lib LibC
TH32CS_SNAPPROCESS = 0x00000002

struct PROCESSENTRY32W
dwSize : DWORD
cntUsage : DWORD
th32ProcessID : DWORD
th32DefaultHeapID : ULONG_PTR
th32ModuleID : DWORD
cntThreads : DWORD
th32ParentProcessID : DWORD
pcPriClassBase : LONG
dwFlags : DWORD
szExeFile : WCHAR[MAX_PATH]
end

fun CreateToolhelp32Snapshot(dwFlags : DWORD, th32ProcessID : DWORD) : HANDLE
fun Process32FirstW(hSnapshot : HANDLE, lppe : PROCESSENTRY32W*) : BOOL
fun Process32NextW(hSnapshot : HANDLE, lppe : PROCESSENTRY32W*) : BOOL
end
4 changes: 4 additions & 0 deletions src/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class Process
end

# Returns the process identifier of the parent process of the current process.
#
# On Windows, the parent is associated only at process creation time, and the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# On Windows, the parent is associated only at process creation time, and the
# NOTE: On Windows, the parent is associated only at process creation time, and the

# system does not re-parent the current process if the parent terminates; thus
# `Process.exists?(Process.ppid)` is not guaranteed to be true.
def self.ppid : Int64
Crystal::System::Process.ppid.to_i64
end
Expand Down