childprocess 0.4.0 → 0.4.1.rc1
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +0 -2
- data/lib/childprocess/unix/fork_exec_process.rb +2 -0
- data/lib/childprocess/unix/posix_spawn_process.rb +2 -6
- data/lib/childprocess/unix/process.rb +1 -1
- data/lib/childprocess/version.rb +1 -1
- data/lib/childprocess/windows/handle.rb +15 -13
- data/lib/childprocess/windows/lib.rb +76 -25
- data/lib/childprocess/windows/process.rb +38 -1
- data/lib/childprocess/windows/process_builder.rb +1 -0
- data/lib/childprocess/windows/structs.rb +66 -0
- data/spec/childprocess_spec.rb +21 -16
- data/spec/io_spec.rb +6 -13
- data/spec/pid_behavior.rb +1 -2
- data/spec/spec_helper.rb +23 -0
- metadata +22 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f15d02987e0f6fb61ee1de1d2a7a58463ed533e
|
4
|
+
data.tar.gz: 00bae3b277c92838151774688d3d69c691dc74f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dccbf2db7db79bb2dcaed2726ddb1b71e9c6620bc80dd22bf4b45c6c1d9142e56731961294df701296972efada62bed9db15a12bf2af0b35073f4469fef80b47
|
7
|
+
data.tar.gz: ba1b3c25f5a7141b5632960054d9617af5fbf40b360dba30d0c73a34aac7f0f42cfdfe909066cc7e4408a18c6340901aaa433a5b3d729e469d465e3e6162fd23
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -12,7 +12,6 @@ module ChildProcess
|
|
12
12
|
pid_ptr = FFI::MemoryPointer.new(:pid_t)
|
13
13
|
actions = Lib::FileActions.new
|
14
14
|
attrs = Lib::Attrs.new
|
15
|
-
flags = 0
|
16
15
|
|
17
16
|
if @io
|
18
17
|
if @io.stdout
|
@@ -34,11 +33,8 @@ module ChildProcess
|
|
34
33
|
actions.add_close fileno_for(writer)
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
attrs.flags = flags
|
36
|
+
attrs.flags |= Platform::POSIX_SPAWN_SETPGROUP
|
37
|
+
attrs.flags |= Platform::POSIX_SPAWN_USEVFORK if defined? Platform::POSIX_SPAWN_USEVFORK
|
42
38
|
|
43
39
|
# wrap in helper classes in order to avoid GC'ed pointers
|
44
40
|
argv = Argv.new(@args)
|
data/lib/childprocess/version.rb
CHANGED
@@ -23,23 +23,25 @@ module ChildProcess
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
attr_reader :pointer
|
27
|
+
|
28
|
+
def initialize(pointer, pid)
|
29
|
+
unless pointer.kind_of?(FFI::Pointer)
|
30
|
+
raise TypeError, "invalid handle: #{pointer.inspect}"
|
29
31
|
end
|
30
32
|
|
31
|
-
if
|
32
|
-
raise ArgumentError, "handle is null: #{
|
33
|
+
if pointer.null?
|
34
|
+
raise ArgumentError, "handle is null: #{pointer.inspect}"
|
33
35
|
end
|
34
36
|
|
35
|
-
@pid
|
36
|
-
@
|
37
|
-
@closed
|
37
|
+
@pid = pid
|
38
|
+
@pointer = pointer
|
39
|
+
@closed = false
|
38
40
|
end
|
39
41
|
|
40
42
|
def exit_code
|
41
43
|
code_pointer = FFI::MemoryPointer.new :ulong
|
42
|
-
ok = Lib.get_exit_code(@
|
44
|
+
ok = Lib.get_exit_code(@pointer, code_pointer)
|
43
45
|
|
44
46
|
if ok
|
45
47
|
code_pointer.get_ulong(0)
|
@@ -58,14 +60,14 @@ module ChildProcess
|
|
58
60
|
when WIN_SIGBREAK
|
59
61
|
Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
|
60
62
|
when WIN_SIGKILL
|
61
|
-
ok = Lib.terminate_process(@
|
63
|
+
ok = Lib.terminate_process(@pointer, @pid)
|
62
64
|
Lib.check_error ok
|
63
65
|
else
|
64
66
|
thread_id = FFI::MemoryPointer.new(:ulong)
|
65
67
|
module_handle = Lib.get_module_handle("kernel32")
|
66
68
|
proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
|
67
69
|
|
68
|
-
thread = Lib.create_remote_thread(@
|
70
|
+
thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id)
|
69
71
|
check_error thread
|
70
72
|
|
71
73
|
Lib.wait_for_single_object(thread, 5)
|
@@ -76,12 +78,12 @@ module ChildProcess
|
|
76
78
|
def close
|
77
79
|
return if @closed
|
78
80
|
|
79
|
-
Lib.close_handle(@
|
81
|
+
Lib.close_handle(@pointer)
|
80
82
|
@closed = true
|
81
83
|
end
|
82
84
|
|
83
85
|
def wait(milliseconds = nil)
|
84
|
-
Lib.wait_for_single_object(@
|
86
|
+
Lib.wait_for_single_object(@pointer, milliseconds || INFINITE)
|
85
87
|
end
|
86
88
|
|
87
89
|
end # Handle
|
@@ -1,30 +1,34 @@
|
|
1
1
|
module ChildProcess
|
2
2
|
module Windows
|
3
|
-
FORMAT_MESSAGE_FROM_SYSTEM
|
4
|
-
FORMAT_MESSAGE_ARGUMENT_ARRAY
|
3
|
+
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
|
4
|
+
FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
|
5
5
|
|
6
|
-
PROCESS_ALL_ACCESS
|
7
|
-
PROCESS_QUERY_INFORMATION
|
8
|
-
PROCESS_VM_READ
|
9
|
-
PROCESS_STILL_ACTIVE
|
6
|
+
PROCESS_ALL_ACCESS = 0x1F0FFF
|
7
|
+
PROCESS_QUERY_INFORMATION = 0x0400
|
8
|
+
PROCESS_VM_READ = 0x0010
|
9
|
+
PROCESS_STILL_ACTIVE = 259
|
10
10
|
|
11
|
-
INFINITE
|
11
|
+
INFINITE = 0xFFFFFFFF
|
12
12
|
|
13
|
-
WIN_SIGINT
|
14
|
-
WIN_SIGBREAK
|
15
|
-
WIN_SIGKILL
|
13
|
+
WIN_SIGINT = 2
|
14
|
+
WIN_SIGBREAK = 3
|
15
|
+
WIN_SIGKILL = 9
|
16
16
|
|
17
|
-
CTRL_C_EVENT
|
18
|
-
CTRL_BREAK_EVENT
|
17
|
+
CTRL_C_EVENT = 0
|
18
|
+
CTRL_BREAK_EVENT = 1
|
19
19
|
|
20
|
-
DETACHED_PROCESS
|
20
|
+
DETACHED_PROCESS = 0x00000008
|
21
21
|
|
22
|
-
STARTF_USESTDHANDLES
|
23
|
-
INVALID_HANDLE_VALUE
|
24
|
-
HANDLE_FLAG_INHERIT
|
22
|
+
STARTF_USESTDHANDLES = 0x00000100
|
23
|
+
INVALID_HANDLE_VALUE = -1
|
24
|
+
HANDLE_FLAG_INHERIT = 0x00000001
|
25
25
|
|
26
|
-
DUPLICATE_SAME_ACCESS
|
27
|
-
CREATE_UNICODE_ENVIRONMENT
|
26
|
+
DUPLICATE_SAME_ACCESS = 0x00000002
|
27
|
+
CREATE_UNICODE_ENVIRONMENT = 0x00000400
|
28
|
+
|
29
|
+
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
|
30
|
+
JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9
|
31
|
+
JOB_OBJECT_BASIC_LIMIT_INFORMATION = 2
|
28
32
|
|
29
33
|
module Lib
|
30
34
|
enum :wait_status, [
|
@@ -61,12 +65,6 @@ module ChildProcess
|
|
61
65
|
:pointer,
|
62
66
|
:pointer], :bool
|
63
67
|
|
64
|
-
#
|
65
|
-
# DWORD WINAPI GetLastError(void);
|
66
|
-
#
|
67
|
-
|
68
|
-
attach_function :get_last_error, :GetLastError, [], :ulong
|
69
|
-
|
70
68
|
#
|
71
69
|
# DWORD WINAPI FormatMessage(
|
72
70
|
# __in DWORD dwFlags,
|
@@ -101,6 +99,35 @@ module ChildProcess
|
|
101
99
|
|
102
100
|
attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer
|
103
101
|
|
102
|
+
#
|
103
|
+
# HANDLE WINAPI CreateJobObject(
|
104
|
+
# _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes,
|
105
|
+
# _In_opt_ LPCTSTR lpName
|
106
|
+
# );
|
107
|
+
#
|
108
|
+
|
109
|
+
attach_function :create_job_object, :CreateJobObjectA, [:pointer, :pointer], :pointer
|
110
|
+
|
111
|
+
#
|
112
|
+
# BOOL WINAPI AssignProcessToJobObject(
|
113
|
+
# _In_ HANDLE hJob,
|
114
|
+
# _In_ HANDLE hProcess
|
115
|
+
# );
|
116
|
+
|
117
|
+
attach_function :assign_process_to_job_object, :AssignProcessToJobObject, [:pointer, :pointer], :bool
|
118
|
+
|
119
|
+
#
|
120
|
+
# BOOL WINAPI SetInformationJobObject(
|
121
|
+
# _In_ HANDLE hJob,
|
122
|
+
# _In_ JOBOBJECTINFOCLASS JobObjectInfoClass,
|
123
|
+
# _In_ LPVOID lpJobObjectInfo,
|
124
|
+
# _In_ DWORD cbJobObjectInfoLength
|
125
|
+
# );
|
126
|
+
#
|
127
|
+
|
128
|
+
attach_function :set_information_job_object, :SetInformationJobObject, [:pointer, :int, :pointer, :ulong], :bool
|
129
|
+
|
130
|
+
#
|
104
131
|
#
|
105
132
|
# DWORD WINAPI WaitForSingleObject(
|
106
133
|
# __in HANDLE hHandle,
|
@@ -243,7 +270,8 @@ module ChildProcess
|
|
243
270
|
end
|
244
271
|
|
245
272
|
def last_error_message
|
246
|
-
errnum =
|
273
|
+
errnum = FFI.errno
|
274
|
+
|
247
275
|
buf = FFI::MemoryPointer.new :char, 512
|
248
276
|
|
249
277
|
size = format_message(
|
@@ -259,6 +287,18 @@ module ChildProcess
|
|
259
287
|
end
|
260
288
|
end
|
261
289
|
|
290
|
+
def each_child_of(pid, &blk)
|
291
|
+
raise NotImplementedError
|
292
|
+
|
293
|
+
# http://stackoverflow.com/questions/1173342/terminate-a-process-tree-c-for-windows?rq=1
|
294
|
+
|
295
|
+
# for each process entry
|
296
|
+
# if pe.th32ParentProcessID == pid
|
297
|
+
# Handle.open(pe.pe.th32ProcessId, &blk)
|
298
|
+
# end
|
299
|
+
#
|
300
|
+
end
|
301
|
+
|
262
302
|
def handle_for(fd_or_io)
|
263
303
|
if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno)
|
264
304
|
if ChildProcess.jruby?
|
@@ -344,6 +384,17 @@ module ChildProcess
|
|
344
384
|
bool or raise Error, last_error_message
|
345
385
|
end
|
346
386
|
|
387
|
+
def alive?(pid)
|
388
|
+
handle = Lib.open_process(PROCESS_ALL_ACCESS, false, pid)
|
389
|
+
if handle.null?
|
390
|
+
false
|
391
|
+
else
|
392
|
+
ptr = FFI::MemoryPointer.new :ulong
|
393
|
+
Lib.check_error Lib.get_exit_code(handle, ptr)
|
394
|
+
ptr.read_ulong == PROCESS_STILL_ACTIVE
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
347
398
|
def no_hang?(flags)
|
348
399
|
(flags & Process::WNOHANG) == Process::WNOHANG
|
349
400
|
end
|
@@ -11,13 +11,13 @@ module ChildProcess
|
|
11
11
|
def stop(timeout = 3)
|
12
12
|
assert_started
|
13
13
|
|
14
|
-
# just kill right away on windows.
|
15
14
|
log "sending KILL"
|
16
15
|
@handle.send(WIN_SIGKILL)
|
17
16
|
|
18
17
|
poll_for_exit(timeout)
|
19
18
|
ensure
|
20
19
|
@handle.close
|
20
|
+
@job.close
|
21
21
|
end
|
22
22
|
|
23
23
|
def wait
|
@@ -27,6 +27,7 @@ module ChildProcess
|
|
27
27
|
@handle.wait
|
28
28
|
@exit_code = @handle.exit_code
|
29
29
|
@handle.close
|
30
|
+
@job.close
|
30
31
|
|
31
32
|
@exit_code
|
32
33
|
end
|
@@ -63,9 +64,12 @@ module ChildProcess
|
|
63
64
|
builder.stderr = @io.stderr
|
64
65
|
end
|
65
66
|
|
67
|
+
@job = Job.new
|
66
68
|
@pid = builder.start
|
67
69
|
@handle = Handle.open @pid
|
68
70
|
|
71
|
+
@job << @handle
|
72
|
+
|
69
73
|
if duplex?
|
70
74
|
raise Error, "no stdin stream" unless builder.stdin
|
71
75
|
io._stdin = builder.stdin
|
@@ -74,6 +78,39 @@ module ChildProcess
|
|
74
78
|
self
|
75
79
|
end
|
76
80
|
|
81
|
+
class Job
|
82
|
+
def initialize
|
83
|
+
@pointer = Lib.create_job_object(nil, nil)
|
84
|
+
|
85
|
+
if @pointer.nil? || @pointer.null?
|
86
|
+
raise Error, "unable to create job object"
|
87
|
+
end
|
88
|
+
|
89
|
+
basic = JobObjectBasicLimitInformation.new
|
90
|
+
basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
91
|
+
|
92
|
+
extended = JobObjectExtendedLimitInformation.new
|
93
|
+
extended[:BasicLimitInformation] = basic
|
94
|
+
|
95
|
+
ret = Lib.set_information_job_object(
|
96
|
+
@pointer,
|
97
|
+
JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
|
98
|
+
extended,
|
99
|
+
extended.size
|
100
|
+
)
|
101
|
+
|
102
|
+
Lib.check_error ret
|
103
|
+
end
|
104
|
+
|
105
|
+
def <<(handle)
|
106
|
+
Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
|
107
|
+
end
|
108
|
+
|
109
|
+
def close
|
110
|
+
Lib.close_handle @pointer
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
77
114
|
end # Process
|
78
115
|
end # Windows
|
79
116
|
end # ChildProcess
|
@@ -79,5 +79,71 @@ module ChildProcess
|
|
79
79
|
self[:bInheritHandle] = opts[:inherit] ? 1 : 0
|
80
80
|
end
|
81
81
|
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION {
|
85
|
+
# LARGE_INTEGER PerProcessUserTimeLimit;
|
86
|
+
# LARGE_INTEGER PerJobUserTimeLimit;
|
87
|
+
# DWORD LimitFlags;
|
88
|
+
# SIZE_T MinimumWorkingSetSize;
|
89
|
+
# SIZE_T MaximumWorkingSetSize;
|
90
|
+
# DWORD ActiveProcessLimit;
|
91
|
+
# ULONG_PTR Affinity;
|
92
|
+
# DWORD PriorityClass;
|
93
|
+
# DWORD SchedulingClass;
|
94
|
+
# } JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;
|
95
|
+
#
|
96
|
+
class JobObjectBasicLimitInformation < FFI::Struct
|
97
|
+
layout :PerProcessUserTimeLimit, :int64,
|
98
|
+
:PerJobUserTimeLimit, :int64,
|
99
|
+
:LimitFlags, :ulong,
|
100
|
+
:MinimumWorkingSetSize, :size_t,
|
101
|
+
:MaximumWorkingSetSize, :size_t,
|
102
|
+
:ActiveProcessLimit, :ulong,
|
103
|
+
:Affinity, :pointer,
|
104
|
+
:PriorityClass, :ulong,
|
105
|
+
:SchedulingClass, :ulong
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# typedef struct _IO_COUNTERS {
|
110
|
+
# ULONGLONG ReadOperationCount;
|
111
|
+
# ULONGLONG WriteOperationCount;
|
112
|
+
# ULONGLONG OtherOperationCount;
|
113
|
+
# ULONGLONG ReadTransferCount;
|
114
|
+
# ULONGLONG WriteTransferCount;
|
115
|
+
# ULONGLONG OtherTransferCount;
|
116
|
+
# } IO_COUNTERS, *PIO_COUNTERS;
|
117
|
+
#
|
118
|
+
|
119
|
+
class IoCounters < FFI::Struct
|
120
|
+
layout :ReadOperationCount, :ulong_long,
|
121
|
+
:WriteOperationCount, :ulong_long,
|
122
|
+
:OtherOperationCount, :ulong_long,
|
123
|
+
:ReadTransferCount, :ulong_long,
|
124
|
+
:WriteTransferCount, :ulong_long,
|
125
|
+
:OtherTransferCount, :ulong_long
|
126
|
+
end
|
127
|
+
#
|
128
|
+
# typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
|
129
|
+
# JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
130
|
+
# IO_COUNTERS IoInfo;
|
131
|
+
# SIZE_T ProcessMemoryLimit;
|
132
|
+
# SIZE_T JobMemoryLimit;
|
133
|
+
# SIZE_T PeakProcessMemoryUsed;
|
134
|
+
# SIZE_T PeakJobMemoryUsed;
|
135
|
+
# } JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;
|
136
|
+
#
|
137
|
+
|
138
|
+
class JobObjectExtendedLimitInformation < FFI::Struct
|
139
|
+
layout :BasicLimitInformation, JobObjectBasicLimitInformation,
|
140
|
+
:IoInfo, IoCounters,
|
141
|
+
:ProcessMemoryLimit, :size_t,
|
142
|
+
:JobMemoryLimit, :size_t,
|
143
|
+
:PeakProcessMemoryUsed, :size_t,
|
144
|
+
:PeakJobMemoryUsed, :size_t
|
145
|
+
end
|
146
|
+
|
147
|
+
|
82
148
|
end # Windows
|
83
149
|
end # ChildProcess
|
data/spec/childprocess_spec.rb
CHANGED
@@ -73,8 +73,7 @@ describe ChildProcess do
|
|
73
73
|
process.wait
|
74
74
|
end
|
75
75
|
|
76
|
-
file
|
77
|
-
child_env = eval(file.read)
|
76
|
+
child_env = eval rewind_and_read(file)
|
78
77
|
child_env['INHERITED'].should == 'yes'
|
79
78
|
end
|
80
79
|
end
|
@@ -88,9 +87,8 @@ describe ChildProcess do
|
|
88
87
|
ENV['CHILD_ONLY'].should be_nil
|
89
88
|
|
90
89
|
process.wait
|
91
|
-
file.rewind
|
92
90
|
|
93
|
-
child_env = eval(file
|
91
|
+
child_env = eval rewind_and_read(file)
|
94
92
|
child_env['CHILD_ONLY'].should == '1'
|
95
93
|
end
|
96
94
|
end
|
@@ -104,8 +102,7 @@ describe ChildProcess do
|
|
104
102
|
process.start
|
105
103
|
process.wait
|
106
104
|
|
107
|
-
file
|
108
|
-
child_env = eval(file.read)
|
105
|
+
child_env = eval rewind_and_read(file)
|
109
106
|
|
110
107
|
child_env['INHERITED'].should eq 'yes'
|
111
108
|
child_env['CHILD_ONLY'].should eq 'yes'
|
@@ -121,9 +118,8 @@ describe ChildProcess do
|
|
121
118
|
process.start
|
122
119
|
|
123
120
|
process.wait
|
124
|
-
file.rewind
|
125
121
|
|
126
|
-
child_env = eval(file
|
122
|
+
child_env = eval rewind_and_read(file)
|
127
123
|
child_env.should_not have_key('CHILDPROCESS_UNSET')
|
128
124
|
end
|
129
125
|
end
|
@@ -136,8 +132,7 @@ describe ChildProcess do
|
|
136
132
|
process = write_argv(file.path, *args).start
|
137
133
|
process.wait
|
138
134
|
|
139
|
-
file.
|
140
|
-
file.read.should == args.inspect
|
135
|
+
rewind_and_read(file).should == args.inspect
|
141
136
|
end
|
142
137
|
end
|
143
138
|
|
@@ -158,8 +153,7 @@ describe ChildProcess do
|
|
158
153
|
|
159
154
|
process.wait
|
160
155
|
|
161
|
-
file.
|
162
|
-
file.read.should == expected_dir
|
156
|
+
rewind_and_read(file).should == expected_dir
|
163
157
|
end
|
164
158
|
end
|
165
159
|
|
@@ -170,8 +164,7 @@ describe ChildProcess do
|
|
170
164
|
process = write_argv(file.path, *args).start
|
171
165
|
process.wait
|
172
166
|
|
173
|
-
file.
|
174
|
-
file.read.should == args.inspect
|
167
|
+
rewind_and_read(file).should == args.inspect
|
175
168
|
end
|
176
169
|
end
|
177
170
|
|
@@ -203,11 +196,23 @@ describe ChildProcess do
|
|
203
196
|
process.start
|
204
197
|
process.wait
|
205
198
|
|
206
|
-
file.
|
207
|
-
file.read.should == dir
|
199
|
+
rewind_and_read(file).should == dir
|
208
200
|
end
|
209
201
|
|
210
202
|
Dir.pwd.should == orig_pwd
|
211
203
|
}
|
212
204
|
end
|
205
|
+
|
206
|
+
it 'kills the full process tree' do
|
207
|
+
Tempfile.open('kill-process-tree') do |file|
|
208
|
+
process = write_pid_in_sleepy_grand_child(file.path).start
|
209
|
+
|
210
|
+
pid = within(5) do
|
211
|
+
Integer(rewind_and_read(file)) rescue nil
|
212
|
+
end
|
213
|
+
|
214
|
+
process.stop
|
215
|
+
within(3) { alive?(pid).should be_false }
|
216
|
+
end
|
217
|
+
end
|
213
218
|
end
|
data/spec/io_spec.rb
CHANGED
@@ -20,11 +20,8 @@ describe ChildProcess do
|
|
20
20
|
process.io.stdin.should be_nil
|
21
21
|
process.wait
|
22
22
|
|
23
|
-
out.
|
24
|
-
err.
|
25
|
-
|
26
|
-
out.read.should eq "0\n"
|
27
|
-
err.read.should eq "1\n"
|
23
|
+
rewind_and_read(out).should eq "0\n"
|
24
|
+
rewind_and_read(err).should eq "1\n"
|
28
25
|
ensure
|
29
26
|
out.close
|
30
27
|
err.close
|
@@ -47,8 +44,7 @@ describe ChildProcess do
|
|
47
44
|
process.start
|
48
45
|
process.wait
|
49
46
|
|
50
|
-
out.
|
51
|
-
out.read.should == "0\n"
|
47
|
+
rewind_and_read(out).should == "0\n"
|
52
48
|
ensure
|
53
49
|
out.close
|
54
50
|
end
|
@@ -66,8 +62,7 @@ describe ChildProcess do
|
|
66
62
|
process.start
|
67
63
|
process.poll_for_exit(exit_timeout)
|
68
64
|
|
69
|
-
out.
|
70
|
-
out.read.should == "hello\n"
|
65
|
+
rewind_and_read(out).should == "hello\n"
|
71
66
|
ensure
|
72
67
|
out.close
|
73
68
|
end
|
@@ -91,8 +86,7 @@ describe ChildProcess do
|
|
91
86
|
|
92
87
|
process.poll_for_exit(exit_timeout)
|
93
88
|
|
94
|
-
out.
|
95
|
-
out.read.should == "hello world\n"
|
89
|
+
rewind_and_read(out).should == "hello world\n"
|
96
90
|
ensure
|
97
91
|
out.close
|
98
92
|
end
|
@@ -207,8 +201,7 @@ describe ChildProcess do
|
|
207
201
|
process.start
|
208
202
|
process.wait
|
209
203
|
|
210
|
-
out.
|
211
|
-
out.read.size.should == 3000
|
204
|
+
rewind_and_read(out).size.should == 3000
|
212
205
|
ensure
|
213
206
|
out.close
|
214
207
|
end
|
data/spec/pid_behavior.rb
CHANGED
@@ -5,9 +5,8 @@ shared_examples_for "a platform that provides the child's pid" do
|
|
5
5
|
Tempfile.open("pid-spec") do |file|
|
6
6
|
process = write_pid(file.path).start
|
7
7
|
process.wait
|
8
|
-
file.rewind
|
9
8
|
|
10
|
-
process.pid.should == file.
|
9
|
+
process.pid.should == rewind_and_read(file).chomp.to_i
|
11
10
|
end
|
12
11
|
end
|
13
12
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -60,6 +60,14 @@ module ChildProcessSpecHelper
|
|
60
60
|
ruby_process tmp_script(code)
|
61
61
|
end
|
62
62
|
|
63
|
+
def write_pid_in_sleepy_grand_child(path)
|
64
|
+
code = <<-RUBY
|
65
|
+
system "ruby", "-e", 'File.open(#{path.inspect}, "w") { |f| f << Process.pid }; sleep'
|
66
|
+
RUBY
|
67
|
+
|
68
|
+
ruby_process tmp_script(code)
|
69
|
+
end
|
70
|
+
|
63
71
|
def exit_with(exit_code)
|
64
72
|
ruby_process(tmp_script("exit(#{exit_code})"))
|
65
73
|
end
|
@@ -97,6 +105,8 @@ module ChildProcessSpecHelper
|
|
97
105
|
end
|
98
106
|
|
99
107
|
raise last_error unless ok
|
108
|
+
|
109
|
+
ok
|
100
110
|
end
|
101
111
|
|
102
112
|
def cat
|
@@ -201,6 +211,19 @@ module ChildProcessSpecHelper
|
|
201
211
|
io.read
|
202
212
|
end
|
203
213
|
|
214
|
+
def alive?(pid)
|
215
|
+
if ChildProcess.windows?
|
216
|
+
ChildProcess::Windows::Lib.alive?(pid)
|
217
|
+
else
|
218
|
+
begin
|
219
|
+
Process.getpgid pid
|
220
|
+
true
|
221
|
+
rescue Errno::ESRCH
|
222
|
+
false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
204
227
|
end # ChildProcessSpecHelper
|
205
228
|
|
206
229
|
Thread.abort_on_exception = true
|
metadata
CHANGED
@@ -1,89 +1,89 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: childprocess
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jari Bakken
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 2.0.0
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 2.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: yard
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 0.9.2
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.9.2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: coveralls
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: ffi
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '1.0'
|
76
|
-
- -
|
76
|
+
- - ">="
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: 1.0.11
|
79
79
|
type: :runtime
|
80
80
|
prerelease: false
|
81
81
|
version_requirements: !ruby/object:Gem::Requirement
|
82
82
|
requirements:
|
83
|
-
- - ~>
|
83
|
+
- - "~>"
|
84
84
|
- !ruby/object:Gem::Version
|
85
85
|
version: '1.0'
|
86
|
-
- -
|
86
|
+
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: 1.0.11
|
89
89
|
description: This gem aims at being a simple and reliable solution for controlling
|
@@ -94,10 +94,10 @@ executables: []
|
|
94
94
|
extensions: []
|
95
95
|
extra_rdoc_files: []
|
96
96
|
files:
|
97
|
-
- .document
|
98
|
-
- .gitignore
|
99
|
-
- .rspec
|
100
|
-
- .travis.yml
|
97
|
+
- ".document"
|
98
|
+
- ".gitignore"
|
99
|
+
- ".rspec"
|
100
|
+
- ".travis.yml"
|
101
101
|
- Gemfile
|
102
102
|
- LICENSE
|
103
103
|
- README.md
|
@@ -148,17 +148,17 @@ require_paths:
|
|
148
148
|
- lib
|
149
149
|
required_ruby_version: !ruby/object:Gem::Requirement
|
150
150
|
requirements:
|
151
|
-
- -
|
151
|
+
- - ">="
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
155
|
requirements:
|
156
|
-
- -
|
156
|
+
- - ">"
|
157
157
|
- !ruby/object:Gem::Version
|
158
|
-
version:
|
158
|
+
version: 1.3.1
|
159
159
|
requirements: []
|
160
160
|
rubyforge_project: childprocess
|
161
|
-
rubygems_version: 2.0
|
161
|
+
rubygems_version: 2.2.0
|
162
162
|
signing_key:
|
163
163
|
specification_version: 4
|
164
164
|
summary: This gem aims at being a simple and reliable solution for controlling external
|