childprocess 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.document +6 -0
  3. data/.gitignore +28 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +44 -0
  6. data/CHANGELOG.md +49 -0
  7. data/Gemfile +15 -0
  8. data/LICENSE +20 -0
  9. data/README.md +196 -0
  10. data/Rakefile +61 -0
  11. data/appveyor.yml +60 -0
  12. data/childprocess.gemspec +30 -0
  13. data/lib/childprocess.rb +205 -0
  14. data/lib/childprocess/abstract_io.rb +36 -0
  15. data/lib/childprocess/abstract_process.rb +192 -0
  16. data/lib/childprocess/errors.rb +26 -0
  17. data/lib/childprocess/jruby.rb +56 -0
  18. data/lib/childprocess/jruby/io.rb +16 -0
  19. data/lib/childprocess/jruby/process.rb +159 -0
  20. data/lib/childprocess/jruby/pump.rb +53 -0
  21. data/lib/childprocess/tools/generator.rb +146 -0
  22. data/lib/childprocess/unix.rb +9 -0
  23. data/lib/childprocess/unix/fork_exec_process.rb +70 -0
  24. data/lib/childprocess/unix/io.rb +21 -0
  25. data/lib/childprocess/unix/lib.rb +186 -0
  26. data/lib/childprocess/unix/platform/i386-linux.rb +12 -0
  27. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -0
  28. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -0
  29. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -0
  30. data/lib/childprocess/unix/posix_spawn_process.rb +134 -0
  31. data/lib/childprocess/unix/process.rb +89 -0
  32. data/lib/childprocess/version.rb +3 -0
  33. data/lib/childprocess/windows.rb +33 -0
  34. data/lib/childprocess/windows/handle.rb +91 -0
  35. data/lib/childprocess/windows/io.rb +25 -0
  36. data/lib/childprocess/windows/lib.rb +416 -0
  37. data/lib/childprocess/windows/process.rb +130 -0
  38. data/lib/childprocess/windows/process_builder.rb +175 -0
  39. data/lib/childprocess/windows/structs.rb +149 -0
  40. data/spec/abstract_io_spec.rb +12 -0
  41. data/spec/childprocess_spec.rb +422 -0
  42. data/spec/io_spec.rb +228 -0
  43. data/spec/jruby_spec.rb +24 -0
  44. data/spec/pid_behavior.rb +12 -0
  45. data/spec/platform_detection_spec.rb +86 -0
  46. data/spec/spec_helper.rb +261 -0
  47. data/spec/unix_spec.rb +57 -0
  48. data/spec/windows_spec.rb +23 -0
  49. metadata +179 -0
@@ -0,0 +1,89 @@
1
+ module ChildProcess
2
+ module Unix
3
+ class Process < AbstractProcess
4
+ attr_reader :pid
5
+
6
+ def io
7
+ @io ||= Unix::IO.new
8
+ end
9
+
10
+ def stop(timeout = 3)
11
+ assert_started
12
+ send_term
13
+
14
+ begin
15
+ return poll_for_exit(timeout)
16
+ rescue TimeoutError
17
+ # try next
18
+ end
19
+
20
+ send_kill
21
+ wait
22
+ rescue Errno::ECHILD, Errno::ESRCH
23
+ # handle race condition where process dies between timeout
24
+ # and send_kill
25
+ true
26
+ end
27
+
28
+ def exited?
29
+ return true if @exit_code
30
+
31
+ assert_started
32
+ pid, status = ::Process.waitpid2(_pid, ::Process::WNOHANG | ::Process::WUNTRACED)
33
+ pid = nil if pid == 0 # may happen on jruby
34
+
35
+ log(:pid => pid, :status => status)
36
+
37
+ if pid
38
+ set_exit_code(status)
39
+ end
40
+
41
+ !!pid
42
+ rescue Errno::ECHILD
43
+ # may be thrown for detached processes
44
+ true
45
+ end
46
+
47
+ def wait
48
+ assert_started
49
+
50
+ if exited?
51
+ exit_code
52
+ else
53
+ _, status = ::Process.waitpid2 _pid
54
+ set_exit_code(status)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def send_term
61
+ send_signal 'TERM'
62
+ end
63
+
64
+ def send_kill
65
+ send_signal 'KILL'
66
+ end
67
+
68
+ def send_signal(sig)
69
+ assert_started
70
+
71
+ log "sending #{sig}"
72
+ ::Process.kill sig, _pid
73
+ end
74
+
75
+ def set_exit_code(status)
76
+ @exit_code = status.exitstatus || status.termsig
77
+ end
78
+
79
+ def _pid
80
+ if leader?
81
+ -@pid # negative pid == process group
82
+ else
83
+ @pid
84
+ end
85
+ end
86
+
87
+ end # Process
88
+ end # Unix
89
+ end # ChildProcess
@@ -0,0 +1,3 @@
1
+ module ChildProcess
2
+ VERSION = '0.9.0'
3
+ end
@@ -0,0 +1,33 @@
1
+ require "ffi"
2
+ require "rbconfig"
3
+
4
+ module ChildProcess
5
+ module Windows
6
+ module Lib
7
+ extend FFI::Library
8
+
9
+ def self.msvcrt_name
10
+ host_part = RbConfig::CONFIG['host_os'].split("_")[1]
11
+ manifest = File.join(RbConfig::CONFIG['bindir'], 'ruby.exe.manifest')
12
+
13
+ if host_part && host_part.to_i > 80 && File.exists?(manifest)
14
+ "msvcr#{host_part}"
15
+ else
16
+ "msvcrt"
17
+ end
18
+ end
19
+
20
+ ffi_lib "kernel32", msvcrt_name
21
+ ffi_convention :stdcall
22
+
23
+
24
+ end # Library
25
+ end # Windows
26
+ end # ChildProcess
27
+
28
+ require "childprocess/windows/lib"
29
+ require "childprocess/windows/structs"
30
+ require "childprocess/windows/handle"
31
+ require "childprocess/windows/io"
32
+ require "childprocess/windows/process_builder"
33
+ require "childprocess/windows/process"
@@ -0,0 +1,91 @@
1
+ module ChildProcess
2
+ module Windows
3
+ class Handle
4
+
5
+ class << self
6
+ private :new
7
+
8
+ def open(pid, access = PROCESS_ALL_ACCESS)
9
+ handle = Lib.open_process(access, false, pid)
10
+
11
+ if handle.null?
12
+ raise Error, Lib.last_error_message
13
+ end
14
+
15
+ h = new(handle, pid)
16
+ return h unless block_given?
17
+
18
+ begin
19
+ yield h
20
+ ensure
21
+ h.close
22
+ end
23
+ end
24
+ end
25
+
26
+ attr_reader :pointer
27
+
28
+ def initialize(pointer, pid)
29
+ unless pointer.kind_of?(FFI::Pointer)
30
+ raise TypeError, "invalid handle: #{pointer.inspect}"
31
+ end
32
+
33
+ if pointer.null?
34
+ raise ArgumentError, "handle is null: #{pointer.inspect}"
35
+ end
36
+
37
+ @pid = pid
38
+ @pointer = pointer
39
+ @closed = false
40
+ end
41
+
42
+ def exit_code
43
+ code_pointer = FFI::MemoryPointer.new :ulong
44
+ ok = Lib.get_exit_code(@pointer, code_pointer)
45
+
46
+ if ok
47
+ code_pointer.get_ulong(0)
48
+ else
49
+ close
50
+ raise Error, Lib.last_error_message
51
+ end
52
+ end
53
+
54
+ def send(signal)
55
+ case signal
56
+ when 0
57
+ exit_code == PROCESS_STILL_ALIVE
58
+ when WIN_SIGINT
59
+ Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid)
60
+ when WIN_SIGBREAK
61
+ Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
62
+ when WIN_SIGKILL
63
+ ok = Lib.terminate_process(@pointer, @pid)
64
+ Lib.check_error ok
65
+ else
66
+ thread_id = FFI::MemoryPointer.new(:ulong)
67
+ module_handle = Lib.get_module_handle("kernel32")
68
+ proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
69
+
70
+ thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id)
71
+ check_error thread
72
+
73
+ Lib.wait_for_single_object(thread, 5)
74
+ true
75
+ end
76
+ end
77
+
78
+ def close
79
+ return if @closed
80
+
81
+ Lib.close_handle(@pointer)
82
+ @closed = true
83
+ end
84
+
85
+ def wait(milliseconds = nil)
86
+ Lib.wait_for_single_object(@pointer, milliseconds || INFINITE)
87
+ end
88
+
89
+ end # Handle
90
+ end # Windows
91
+ end # ChildProcess
@@ -0,0 +1,25 @@
1
+ module ChildProcess
2
+ module Windows
3
+ class IO < AbstractIO
4
+ private
5
+
6
+ def check_type(io)
7
+ return if has_fileno?(io)
8
+ return if has_to_io?(io)
9
+
10
+ raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io"
11
+ end
12
+
13
+ def has_fileno?(io)
14
+ io.respond_to?(:fileno) && io.fileno
15
+ end
16
+
17
+ def has_to_io?(io)
18
+ io.respond_to?(:to_io) && io.to_io.kind_of?(::IO)
19
+ end
20
+
21
+ end # IO
22
+ end # Windows
23
+ end # ChildProcess
24
+
25
+
@@ -0,0 +1,416 @@
1
+ module ChildProcess
2
+ module Windows
3
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
4
+ FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
5
+
6
+ PROCESS_ALL_ACCESS = 0x1F0FFF
7
+ PROCESS_QUERY_INFORMATION = 0x0400
8
+ PROCESS_VM_READ = 0x0010
9
+ PROCESS_STILL_ACTIVE = 259
10
+
11
+ INFINITE = 0xFFFFFFFF
12
+
13
+ WIN_SIGINT = 2
14
+ WIN_SIGBREAK = 3
15
+ WIN_SIGKILL = 9
16
+
17
+ CTRL_C_EVENT = 0
18
+ CTRL_BREAK_EVENT = 1
19
+
20
+ CREATE_BREAKAWAY_FROM_JOB = 0x01000000
21
+ DETACHED_PROCESS = 0x00000008
22
+
23
+ STARTF_USESTDHANDLES = 0x00000100
24
+ INVALID_HANDLE_VALUE = -1
25
+ HANDLE_FLAG_INHERIT = 0x00000001
26
+
27
+ DUPLICATE_SAME_ACCESS = 0x00000002
28
+ CREATE_UNICODE_ENVIRONMENT = 0x00000400
29
+
30
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
31
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800
32
+ JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9
33
+ JOB_OBJECT_BASIC_LIMIT_INFORMATION = 2
34
+
35
+ module Lib
36
+ enum :wait_status, [
37
+ :wait_object_0, 0,
38
+ :wait_timeout, 0x102,
39
+ :wait_abandoned, 0x80,
40
+ :wait_failed, 0xFFFFFFFF
41
+ ]
42
+
43
+ #
44
+ # BOOL WINAPI CreateProcess(
45
+ # __in_opt LPCTSTR lpApplicationName,
46
+ # __inout_opt LPTSTR lpCommandLine,
47
+ # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
48
+ # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
49
+ # __in BOOL bInheritHandles,
50
+ # __in DWORD dwCreationFlags,
51
+ # __in_opt LPVOID lpEnvironment,
52
+ # __in_opt LPCTSTR lpCurrentDirectory,
53
+ # __in LPSTARTUPINFO lpStartupInfo,
54
+ # __out LPPROCESS_INFORMATION lpProcessInformation
55
+ # );
56
+ #
57
+
58
+ attach_function :create_process, :CreateProcessA, [
59
+ :pointer,
60
+ :pointer,
61
+ :pointer,
62
+ :pointer,
63
+ :bool,
64
+ :ulong,
65
+ :pointer,
66
+ :pointer,
67
+ :pointer,
68
+ :pointer], :bool
69
+
70
+ #
71
+ # DWORD WINAPI FormatMessage(
72
+ # __in DWORD dwFlags,
73
+ # __in_opt LPCVOID lpSource,
74
+ # __in DWORD dwMessageId,
75
+ # __in DWORD dwLanguageId,
76
+ # __out LPTSTR lpBuffer,
77
+ # __in DWORD nSize,
78
+ # __in_opt va_list *Arguments
79
+ # );
80
+ #
81
+
82
+ attach_function :format_message, :FormatMessageA, [
83
+ :ulong,
84
+ :pointer,
85
+ :ulong,
86
+ :ulong,
87
+ :pointer,
88
+ :ulong,
89
+ :pointer], :ulong
90
+
91
+
92
+ attach_function :close_handle, :CloseHandle, [:pointer], :bool
93
+
94
+ #
95
+ # HANDLE WINAPI OpenProcess(
96
+ # __in DWORD dwDesiredAccess,
97
+ # __in BOOL bInheritHandle,
98
+ # __in DWORD dwProcessId
99
+ # );
100
+ #
101
+
102
+ attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer
103
+
104
+ #
105
+ # HANDLE WINAPI CreateJobObject(
106
+ # _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes,
107
+ # _In_opt_ LPCTSTR lpName
108
+ # );
109
+ #
110
+
111
+ attach_function :create_job_object, :CreateJobObjectA, [:pointer, :pointer], :pointer
112
+
113
+ #
114
+ # BOOL WINAPI AssignProcessToJobObject(
115
+ # _In_ HANDLE hJob,
116
+ # _In_ HANDLE hProcess
117
+ # );
118
+
119
+ attach_function :assign_process_to_job_object, :AssignProcessToJobObject, [:pointer, :pointer], :bool
120
+
121
+ #
122
+ # BOOL WINAPI SetInformationJobObject(
123
+ # _In_ HANDLE hJob,
124
+ # _In_ JOBOBJECTINFOCLASS JobObjectInfoClass,
125
+ # _In_ LPVOID lpJobObjectInfo,
126
+ # _In_ DWORD cbJobObjectInfoLength
127
+ # );
128
+ #
129
+
130
+ attach_function :set_information_job_object, :SetInformationJobObject, [:pointer, :int, :pointer, :ulong], :bool
131
+
132
+ #
133
+ #
134
+ # DWORD WINAPI WaitForSingleObject(
135
+ # __in HANDLE hHandle,
136
+ # __in DWORD dwMilliseconds
137
+ # );
138
+ #
139
+
140
+ attach_function :wait_for_single_object, :WaitForSingleObject, [:pointer, :ulong], :wait_status, :blocking => true
141
+
142
+ #
143
+ # BOOL WINAPI GetExitCodeProcess(
144
+ # __in HANDLE hProcess,
145
+ # __out LPDWORD lpExitCode
146
+ # );
147
+ #
148
+
149
+ attach_function :get_exit_code, :GetExitCodeProcess, [:pointer, :pointer], :bool
150
+
151
+ #
152
+ # BOOL WINAPI GenerateConsoleCtrlEvent(
153
+ # __in DWORD dwCtrlEvent,
154
+ # __in DWORD dwProcessGroupId
155
+ # );
156
+ #
157
+
158
+ attach_function :generate_console_ctrl_event, :GenerateConsoleCtrlEvent, [:ulong, :ulong], :bool
159
+
160
+ #
161
+ # BOOL WINAPI TerminateProcess(
162
+ # __in HANDLE hProcess,
163
+ # __in UINT uExitCode
164
+ # );
165
+ #
166
+
167
+ attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool
168
+
169
+ #
170
+ # intptr_t _get_osfhandle(
171
+ # int fd
172
+ # );
173
+ #
174
+
175
+ attach_function :get_osfhandle, :_get_osfhandle, [:int], :intptr_t
176
+
177
+ #
178
+ # int _open_osfhandle (
179
+ # intptr_t osfhandle,
180
+ # int flags
181
+ # );
182
+ #
183
+
184
+ attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int
185
+
186
+ # BOOL WINAPI SetHandleInformation(
187
+ # __in HANDLE hObject,
188
+ # __in DWORD dwMask,
189
+ # __in DWORD dwFlags
190
+ # );
191
+
192
+ attach_function :set_handle_information, :SetHandleInformation, [:pointer, :ulong, :ulong], :bool
193
+
194
+ # BOOL WINAPI GetHandleInformation(
195
+ # __in HANDLE hObject,
196
+ # __out LPDWORD lpdwFlags
197
+ # );
198
+
199
+ attach_function :get_handle_information, :GetHandleInformation, [:pointer, :pointer], :bool
200
+
201
+ # BOOL WINAPI CreatePipe(
202
+ # __out PHANDLE hReadPipe,
203
+ # __out PHANDLE hWritePipe,
204
+ # __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
205
+ # __in DWORD nSize
206
+ # );
207
+
208
+ attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool
209
+
210
+ #
211
+ # HANDLE WINAPI GetCurrentProcess(void);
212
+ #
213
+
214
+ attach_function :current_process, :GetCurrentProcess, [], :pointer
215
+
216
+ #
217
+ # BOOL WINAPI DuplicateHandle(
218
+ # __in HANDLE hSourceProcessHandle,
219
+ # __in HANDLE hSourceHandle,
220
+ # __in HANDLE hTargetProcessHandle,
221
+ # __out LPHANDLE lpTargetHandle,
222
+ # __in DWORD dwDesiredAccess,
223
+ # __in BOOL bInheritHandle,
224
+ # __in DWORD dwOptions
225
+ # );
226
+ #
227
+
228
+ attach_function :_duplicate_handle, :DuplicateHandle, [
229
+ :pointer,
230
+ :pointer,
231
+ :pointer,
232
+ :pointer,
233
+ :ulong,
234
+ :bool,
235
+ :ulong
236
+ ], :bool
237
+
238
+ class << self
239
+ def kill(signal, *pids)
240
+ case signal
241
+ when 'SIGINT', 'INT', :SIGINT, :INT
242
+ signal = WIN_SIGINT
243
+ when 'SIGBRK', 'BRK', :SIGBREAK, :BRK
244
+ signal = WIN_SIGBREAK
245
+ when 'SIGKILL', 'KILL', :SIGKILL, :KILL
246
+ signal = WIN_SIGKILL
247
+ when 0..9
248
+ # Do nothing
249
+ else
250
+ raise Error, "invalid signal #{signal.inspect}"
251
+ end
252
+
253
+ pids.map { |pid| pid if Lib.send_signal(signal, pid) }.compact
254
+ end
255
+
256
+ def waitpid(pid, flags = 0)
257
+ wait_for_pid(pid, no_hang?(flags))
258
+ end
259
+
260
+ def waitpid2(pid, flags = 0)
261
+ code = wait_for_pid(pid, no_hang?(flags))
262
+
263
+ [pid, code] if code
264
+ end
265
+
266
+ def dont_inherit(file)
267
+ unless file.respond_to?(:fileno)
268
+ raise ArgumentError, "expected #{file.inspect} to respond to :fileno"
269
+ end
270
+
271
+ set_handle_inheritance(handle_for(file.fileno), false)
272
+ end
273
+
274
+ def last_error_message
275
+ errnum = FFI.errno
276
+
277
+ buf = FFI::MemoryPointer.new :char, 512
278
+
279
+ size = format_message(
280
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
281
+ nil, errnum, 0, buf, buf.size, nil
282
+ )
283
+
284
+ str = buf.read_string(size).strip
285
+ if errnum == 0
286
+ "Unknown error (Windows says #{str.inspect}, but it did not.)"
287
+ else
288
+ "#{str} (#{errnum})"
289
+ end
290
+ end
291
+
292
+ def each_child_of(pid, &blk)
293
+ raise NotImplementedError
294
+
295
+ # http://stackoverflow.com/questions/1173342/terminate-a-process-tree-c-for-windows?rq=1
296
+
297
+ # for each process entry
298
+ # if pe.th32ParentProcessID == pid
299
+ # Handle.open(pe.pe.th32ProcessId, &blk)
300
+ # end
301
+ #
302
+ end
303
+
304
+ def handle_for(fd_or_io)
305
+ if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno)
306
+ if ChildProcess.jruby?
307
+ handle = ChildProcess::JRuby.windows_handle_for(fd_or_io)
308
+ else
309
+ handle = get_osfhandle(fd_or_io.fileno)
310
+ end
311
+ elsif fd_or_io.kind_of?(Integer)
312
+ handle = get_osfhandle(fd_or_io)
313
+ elsif fd_or_io.respond_to?(:to_io)
314
+ io = fd_or_io.to_io
315
+
316
+ unless io.kind_of?(IO)
317
+ raise TypeError, "expected #to_io to return an instance of IO"
318
+ end
319
+
320
+ handle = get_osfhandle(io.fileno)
321
+ else
322
+ raise TypeError, "invalid type: #{fd_or_io.inspect}"
323
+ end
324
+
325
+ if handle == INVALID_HANDLE_VALUE
326
+ raise Error, last_error_message
327
+ end
328
+
329
+ FFI::Pointer.new handle
330
+ end
331
+
332
+ def io_for(handle, flags = File::RDONLY)
333
+ fd = open_osfhandle(handle, flags)
334
+ if fd == -1
335
+ raise Error, last_error_message
336
+ end
337
+
338
+ FFI::IO.for_fd fd, flags
339
+ end
340
+
341
+ def duplicate_handle(handle)
342
+ dup = FFI::MemoryPointer.new(:pointer)
343
+ proc = current_process
344
+
345
+ ok = Lib._duplicate_handle(
346
+ proc,
347
+ handle,
348
+ proc,
349
+ dup,
350
+ 0,
351
+ false,
352
+ DUPLICATE_SAME_ACCESS
353
+ )
354
+
355
+ check_error ok
356
+
357
+ dup.read_pointer
358
+ ensure
359
+ close_handle proc
360
+ end
361
+
362
+ def set_handle_inheritance(handle, bool)
363
+ status = set_handle_information(
364
+ handle,
365
+ HANDLE_FLAG_INHERIT,
366
+ bool ? HANDLE_FLAG_INHERIT : 0
367
+ )
368
+
369
+ check_error status
370
+ end
371
+
372
+ def get_handle_inheritance(handle)
373
+ flags = FFI::MemoryPointer.new(:uint)
374
+
375
+ status = get_handle_information(
376
+ handle,
377
+ flags
378
+ )
379
+
380
+ check_error status
381
+
382
+ flags.read_uint
383
+ end
384
+
385
+ def check_error(bool)
386
+ bool or raise Error, last_error_message
387
+ end
388
+
389
+ def alive?(pid)
390
+ handle = Lib.open_process(PROCESS_ALL_ACCESS, false, pid)
391
+ if handle.null?
392
+ false
393
+ else
394
+ ptr = FFI::MemoryPointer.new :ulong
395
+ Lib.check_error Lib.get_exit_code(handle, ptr)
396
+ ptr.read_ulong == PROCESS_STILL_ACTIVE
397
+ end
398
+ end
399
+
400
+ def no_hang?(flags)
401
+ (flags & Process::WNOHANG) == Process::WNOHANG
402
+ end
403
+
404
+ def wait_for_pid(pid, no_hang)
405
+ code = Handle.open(pid) { |handle|
406
+ handle.wait unless no_hang
407
+ handle.exit_code
408
+ }
409
+
410
+ code if code != PROCESS_STILL_ACTIVE
411
+ end
412
+ end
413
+
414
+ end # Lib
415
+ end # Windows
416
+ end # ChildProcess