win32-job 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af2229e6e9b4045eeee68046c86d250744c3e92c
4
- data.tar.gz: a3f7373165c4826a3762424aacd85665bcd7da19
3
+ metadata.gz: 30a60b887397b24322804e276cf42a9809f709e9
4
+ data.tar.gz: cad1ab937502a18749ebb235b229e80ec7cdc86b
5
5
  SHA512:
6
- metadata.gz: 843e8e7e12bb1f46957613139173307a922ed628ec41a6174c3171d6ff2b68052627c25d5d1471a0b83c4f338269564c04a6de0b716afb5df083de40f42d0903
7
- data.tar.gz: 9e72222e7fa9327d642cbb31539d21e5bab9bad17ee4c77e50c7a02c9524cbaab7c7037ed9d73b0739aa66f785a53d6f1dd332c27477e3fd4d2ab4c9605f309e
6
+ metadata.gz: a029f9e719ac44d0452169b189610173e9b92f0d99888687ad07019e529891eef3133be3133264ac6676d0b3af3c692bfef6aa3faf5ea27278718cec7b3300fc
7
+ data.tar.gz: b00089d6fcffd6019c30bd8ed21aaa233006f5dc61b5c2d8af85dd65585e56f4fef18ddced3e8ddaa95347f7923ed6b1aae5b888a7a22f994928043ba426eada
@@ -0,0 +1 @@
1
+ �P�5 �hђ��4,���|�.��& ��w�2(y¸$�����Yc���O�}V"��џ����{�f�p&>�z��m;�����Q{\{�baR�OЊ�i9�e1�޳�׃����s����_.;��.K^�W��'FF���Z�_UY{P-�_��R�~ uu�.���)}� ��VZ�E�B#v\dy���C�W�+�iɚ!;��RM�_�I0��x� wk6��}~���2��*7
@@ -0,0 +1,2 @@
1
+ O�����< �R�S7��͗�^ $(��A�-�+��U��X�����R^��K��<���XU�9}'}��*��jr�=s�Ǐ�*\�B��6m��jO��wށ��SV��G�-����5�O�9�� _�F������)�S�U.��˹]c�f���R�S
2
+ �poPb�T��c
data/CHANGES CHANGED
@@ -1,16 +1,22 @@
1
- = 0.1.3 - 20-Oct-2014
2
- * Updates to the gem:create and gem:install tasks.
3
- * Removed references to Rubyforge in the gemspec.
4
- * Use relative_require where appropriate.
5
-
6
- = 0.1.2 - 6-Feb-2014
7
- * Altered the constructor. The second argument now controls whether or not
8
- to raise an error if the job already exists. By default it will open the job
9
- if it already exists.
10
- * Fixed a bug in the process_list method where it could return incorrect values.
11
-
12
- = 0.1.1 - 5-Feb-2014
13
- * Added a wait method.
14
-
15
- = 0.1.0 - 4-Feb-2014
16
- * Initial release.
1
+ = 0.1.4 - 10-Nov-2015
2
+ * This gem is now signed.
3
+ * The gem related tasks in the Rakefile now assume Rubygems 2.x.
4
+ * Added a win32-job.rb file for convenience.
5
+ * Added an appveyor.yml file for the MS continuous integration service.
6
+
7
+ = 0.1.3 - 20-Oct-2014
8
+ * Updates to the gem:create and gem:install tasks.
9
+ * Removed references to Rubyforge in the gemspec.
10
+ * Use relative_require where appropriate.
11
+
12
+ = 0.1.2 - 6-Feb-2014
13
+ * Altered the constructor. The second argument now controls whether or not
14
+ to raise an error if the job already exists. By default it will open the job
15
+ if it already exists.
16
+ * Fixed a bug in the process_list method where it could return incorrect values.
17
+
18
+ = 0.1.1 - 5-Feb-2014
19
+ * Added a wait method.
20
+
21
+ = 0.1.0 - 4-Feb-2014
22
+ * Initial release.
data/MANIFEST CHANGED
@@ -4,6 +4,8 @@
4
4
  * README
5
5
  * win32-job.gemspec
6
6
  * examples\example_job.rb
7
+ * certs\djberg96_pub.pem
8
+ * lib\win32-job.rb
7
9
  * lib\win32\job.rb
8
10
  * lib\win32\job\constants.rb
9
11
  * lib\win32\job\functions.rb
data/README CHANGED
@@ -1,55 +1,55 @@
1
- == Description
2
- A Ruby interface to Windows' jobs, process groups for Windows.
3
-
4
- == Installation
5
- gem install win32-job
6
-
7
- == Prerequisites
8
- * ffi
9
- * Ruby 1.9.3 or later
10
-
11
- == Synopsis
12
- require 'win32/job'
13
- include Win32
14
-
15
- job = Job.create('test')
16
-
17
- j.configure_limit(
18
- :breakaway_ok => true,
19
- :kill_on_job_close => true,
20
- :process_memory => 1024 * 8,
21
- :process_time => 1000
22
- )
23
-
24
- job.add_process(1234)
25
- job.add_process(1235)
26
-
27
- job.close
28
-
29
- == Known Issues
30
- None known.
31
-
32
- Please file any bug reports on the project page at
33
- http://github.com/djberg96/win32-job
34
-
35
- == License
36
- Artistic 2.0
37
-
38
- == Contributions
39
- Although this library is free, please consider having your company
40
- setup a gittip if used by your company professionally.
41
-
42
- http://www.gittip.com/djberg96/
43
-
44
- == Copyright
45
- (C) 2003-2014 Daniel J. Berger
46
- All Rights Reserved
47
-
48
- == Warranty
49
- This package is provided "as is" and without any express or
50
- implied warranties, including, without limitation, the implied
51
- warranties of merchantability and fitness for a particular purpose.
52
-
53
- == Authors
54
- Daniel J. Berger
55
- Park Heesob
1
+ == Description
2
+ A Ruby interface to Windows' jobs, process groups for Windows.
3
+
4
+ == Installation
5
+ gem install win32-job
6
+
7
+ == Prerequisites
8
+ * ffi
9
+ * Ruby 1.9.3 or later
10
+
11
+ == Synopsis
12
+ require 'win32/job'
13
+ include Win32
14
+
15
+ job = Job.create('test')
16
+
17
+ j.configure_limit(
18
+ :breakaway_ok => true,
19
+ :kill_on_job_close => true,
20
+ :process_memory => 1024 * 8,
21
+ :process_time => 1000
22
+ )
23
+
24
+ job.add_process(1234)
25
+ job.add_process(1235)
26
+
27
+ job.close
28
+
29
+ == Known Issues
30
+ None known.
31
+
32
+ Please file any bug reports on the project page at
33
+ http://github.com/djberg96/win32-job
34
+
35
+ == License
36
+ Artistic 2.0
37
+
38
+ == Contributions
39
+ Although this library is free, please consider having your company
40
+ setup a gittip if used by your company professionally.
41
+
42
+ http://www.gittip.com/djberg96/
43
+
44
+ == Copyright
45
+ (C) 2003-2015 Daniel J. Berger
46
+ All Rights Reserved
47
+
48
+ == Warranty
49
+ This package is provided "as is" and without any express or
50
+ implied warranties, including, without limitation, the implied
51
+ warranties of merchantability and fitness for a particular purpose.
52
+
53
+ == Authors
54
+ Daniel J. Berger
55
+ Park Heesob
data/Rakefile CHANGED
@@ -7,13 +7,10 @@ CLEAN.include("**/*.gem", "**/*.rbc", "**/*.rbx")
7
7
  namespace :gem do
8
8
  desc 'Create the win32-job gem'
9
9
  task :create => [:clean] do
10
+ require 'rubygems/package'
10
11
  spec = eval(IO.read('win32-job.gemspec'))
11
- if Gem::VERSION < "2.0"
12
- Gem::Builder.new(spec).build
13
- else
14
- require 'rubygems/package'
15
- Gem::Package.build(spec)
16
- end
12
+ spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
13
+ Gem::Package.build(spec, true)
17
14
  end
18
15
 
19
16
  desc 'Install the win32-job gem'
@@ -0,0 +1,46 @@
1
+ version: '{build}'
2
+ branches:
3
+ only:
4
+ - ffi
5
+ skip_tags: true
6
+ clone_depth: 10
7
+ environment:
8
+ matrix:
9
+ - ruby_version: 193
10
+ ruby_dir: 1.9.1
11
+ - ruby_version: 200
12
+ ruby_dir: 2.0.0
13
+ - ruby_version: 200-x64
14
+ ruby_dir: 2.0.0
15
+ - ruby_version: 21
16
+ ruby_dir: 2.1.0
17
+ - ruby_version: 21-x64
18
+ ruby_dir: 2.1.0
19
+ - ruby_version: 22
20
+ ruby_dir: 2.2.0
21
+ - ruby_version: 22-x64
22
+ ruby_dir: 2.2.0
23
+ install:
24
+ - ps: >-
25
+ $env:path = "C:\Ruby" + $env:ruby_version + "\bin;" + $env:path
26
+
27
+ $tpath = "C:\Ruby" + $env:ruby_version + "\lib\ruby\" + $env:ruby_dir + "\test"
28
+
29
+ if ((test-path $tpath) -eq $True){ rm -recurse -force $tpath }
30
+
31
+ gem update --system > $null
32
+
33
+ if ((gem query -i ffi) -eq $False){ gem install ffi --no-document }
34
+
35
+ if ((gem query -i test-unit -v ">= 3.0") -eq $False){ gem install test-unit --no-document }
36
+ cache:
37
+ - C:\Ruby193\lib\ruby\gems\1.9.1
38
+ - C:\Ruby200\lib\ruby\gems\2.0.0
39
+ - C:\Ruby200-x64\lib\ruby\gems\2.0.0
40
+ - C:\Ruby21\lib\ruby\gems\2.1.0
41
+ - C:\Ruby21-x64\lib\ruby\gems\2.1.0
42
+ - C:\Ruby22\lib\ruby\gems\2.2.0
43
+ - C:\Ruby22-x64\lib\ruby\gems\2.2.0
44
+ build: off
45
+ test_script:
46
+ - cmd: rake
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MREwDwYDVQQDDAhkamJl
3
+ cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
4
+ MB4XDTE1MDkwMjIwNDkxOFoXDTE2MDkwMTIwNDkxOFowPzERMA8GA1UEAwwIZGpi
5
+ ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
6
+ bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMyTkvXqRp6hLs9eoJOS
7
+ Hmi8kRYbq9Vkf15/hMxJpotYMgJVHHWrmDcC5Dye2PbnXjTkKf266Zw0PtT9h+lI
8
+ S3ts9HO+vaCFSMwFFZmnWJSpQ3CNw2RcHxjWkk9yF7imEM8Kz9ojhiDXzBetdV6M
9
+ gr0lV/alUr7TNVBDngbXEfTWscyXh1qd7xZ4EcOdsDktCe5G45N/o3662tPQvJsi
10
+ FOF0CM/KuBsa/HL1/eoEmF4B3EKIRfTHrQ3hu20Kv3RJ88QM4ec2+0dd97uX693O
11
+ zv6981fyEg+aXLkxrkViM/tz2qR2ZE0jPhHTREPYeMEgptRkTmWSKAuLVWrJEfgl
12
+ DtkCAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFEwe
13
+ nn6bfJADmuIDiMSOzedOrL+xMB0GA1UdEQQWMBSBEmRqYmVyZzk2QGdtYWlsLmNv
14
+ bTAdBgNVHRIEFjAUgRJkamJlcmc5NkBnbWFpbC5jb20wDQYJKoZIhvcNAQEFBQAD
15
+ ggEBAHmNOCWoDVD75zHFueY0viwGDVP1BNGFC+yXcb7u2GlK+nEMCORqzURbYPf7
16
+ tL+/hzmePIRz7i30UM//64GI1NLv9jl7nIwjhPpXpf7/lu2I9hOTsvwSumb5UiKC
17
+ /sqBxI3sfj9pr79Wpv4MuikX1XPik7Ncb7NPsJPw06Lvyc3Hkg5X2XpPtLtS+Gr2
18
+ wKJnmzb5rIPS1cmsqv0M9LPWflzfwoZ/SpnmhagP+g05p8bRNKjZSA2iImM/GyYZ
19
+ EJYzxdPOrx2n6NYR3Hk+vHP0U7UBSveI6+qx+ndQYaeyCn+GRX2PKS9h66YF/Q1V
20
+ tGSHgAmcLlkdGgan182qsE/4kKM=
21
+ -----END CERTIFICATE-----
@@ -0,0 +1 @@
1
+ require_relative 'win32/job'
@@ -1,505 +1,505 @@
1
- require_relative 'job/constants'
2
- require_relative 'job/functions'
3
- require_relative 'job/structs'
4
- require_relative 'job/helper'
5
-
6
- # The Win32 module serves as a namespace only.
7
- module Win32
8
-
9
- # The Job class encapsulates a Windows Job object.
10
- class Job
11
- include Windows::Constants
12
- include Windows::Functions
13
- include Windows::Structs
14
- extend Windows::Functions
15
-
16
- # The version of the win32-job library
17
- VERSION = '0.1.3'
18
-
19
- private
20
-
21
- # Valid options for the configure method
22
- VALID_OPTIONS = %w[
23
- active_process
24
- affinity
25
- breakaway_ok
26
- die_on_unhandled_exception
27
- job_memory
28
- job_time
29
- kill_on_job_close
30
- limit_job_time
31
- limit_affinity
32
- minimum_working_set
33
- maximum_working_set
34
- preserve_job_time
35
- priority_class
36
- process_memory
37
- process_time
38
- scheduling_class
39
- silent_breakaway_ok
40
- ]
41
-
42
- public
43
-
44
- # The name of the job, if specified.
45
- attr_reader :job_name
46
-
47
- alias :name :job_name
48
-
49
- # Create a new Job object identified by +name+. If no name is provided
50
- # then an anonymous job is created.
51
- #
52
- # If the job already exists then the existing job is opened instead, unless
53
- # the +open_existing+ method is false. In that case an error is
54
- # raised.
55
- #
56
- # The +security+ argument accepts a raw SECURITY_ATTRIBUTES struct that is
57
- # passed to the CreateJobObject function internally.
58
- #
59
- def initialize(name = nil, open_existing = true, security = nil)
60
- raise TypeError unless name.is_a?(String) if name
61
-
62
- @job_name = name
63
- @process_list = []
64
- @closed = false
65
-
66
- @job_handle = CreateJobObject(security, name)
67
-
68
- if @job_handle == 0
69
- FFI.raise_windows_error('CreateJobObject', FFI.errno)
70
- end
71
-
72
- if FFI.errno == ERROR_ALREADY_EXISTS && !open_existing
73
- raise ArgumentError, "job '#{name}' already exists"
74
- end
75
-
76
- if block_given?
77
- begin
78
- yield self
79
- ensure
80
- close
81
- end
82
- end
83
-
84
- ObjectSpace.define_finalizer(self, self.class.finalize(@job_handle, @closed))
85
- end
86
-
87
- # Add process +pid+ to the job object. Process ID's added to the
88
- # job are tracked via the Job#process_list accessor.
89
- #
90
- # Note that once a process is added to a job, the association cannot be
91
- # broken. A process can be associated with more than one job in a
92
- # hierarchy of nested jobs, however.
93
- #
94
- # You may add a maximum of 100 processes per job.
95
- #
96
- def add_process(pid)
97
- if @process_list.size > 99
98
- raise ArgumentError, "maximum number of processes reached"
99
- end
100
-
101
- phandle = OpenProcess(PROCESS_ALL_ACCESS, false, pid)
102
-
103
- if phandle == 0
104
- FFI.raise_windows_error('OpenProcess', FFI.errno)
105
- end
106
-
107
- pbool = FFI::MemoryPointer.new(:int)
108
-
109
- IsProcessInJob(phandle, 0, pbool)
110
-
111
- if pbool.read_int == 0
112
- unless AssignProcessToJobObject(@job_handle, phandle)
113
- FFI.raise_windows_error('AssignProcessToJobObject', FFI.errno)
114
- end
115
- @process_list << pid
116
- else
117
- raise ArgumentError, "pid #{pid} is already part of a job"
118
- end
119
-
120
- pid
121
- end
122
-
123
- # Close the job object.
124
- #
125
- def close
126
- CloseHandle(@job_handle) if @job_handle
127
- @closed = true
128
- end
129
-
130
- # Kill all processes associated with the job object that are
131
- # associated with the current process.
132
- #
133
- # Note that killing a process does not dissociate it from the job.
134
- #
135
- def kill
136
- unless TerminateJobObject(@job_handle, Process.pid)
137
- FFI.raise_windows_error('TerminateJobObject', FFI.errno)
138
- end
139
-
140
- @process_list = []
141
- end
142
-
143
- alias terminate kill
144
-
145
- # Set various job limits. Possible options are:
146
- #
147
- # * active_process => Numeric
148
- # Establishes a maximum number of simultaneously active processes
149
- # associated with the job.
150
- #
151
- # * affinity => Numeric
152
- # Causes all processes associated with the job to use the same
153
- # processor affinity.
154
- #
155
- # * breakaway_ok => Boolean
156
- # If any process associated with the job creates a child process using
157
- # the CREATE_BREAKAWAY_FROM_JOB flag while this limit is in effect, the
158
- # child process is not associated with the job.
159
- #
160
- # * die_on_unhandled_exception => Boolean
161
- # Forces a call to the SetErrorMode function with the SEM_NOGPFAULTERRORBOX
162
- # flag for each process associated with the job. If an exception occurs
163
- # and the system calls the UnhandledExceptionFilter function, the debugger
164
- # will be given a chance to act. If there is no debugger, the functions
165
- # returns EXCEPTION_EXECUTE_HANDLER. Normally, this will cause termination
166
- # of the process with the exception code as the exit status.
167
- #
168
- # * job_memory => Numeric
169
- # Causes all processes associated with the job to limit the job-wide
170
- # sum of their committed memory. When a process attempts to commit
171
- # memory that would exceed the job-wide limit, it fails. If the job
172
- # object is associated with a completion port, a
173
- # JOB_OBJECT_MSG_JOB_MEMORY_LIMIT message is sent to the completion
174
- # port.
175
- #
176
- # * job_time => Numeric
177
- # Establishes a user-mode execution time limit for the job.
178
- #
179
- # * kill_on_job_close => Boolean
180
- # Causes all processes associated with the job to terminate when the
181
- # last handle to the job is closed.
182
- #
183
- # * minimum_working_set_size => Numeric
184
- # Causes all processes associated with the job to use the same minimum
185
- # set size. If the job is nested, the effective working set size is the
186
- # smallest working set size in the job chain.
187
- #
188
- # * maximum_working_set_size => Numeric
189
- # Causes all processes associated with the job to use the same maximum
190
- # set size. If the job is nested, the effective working set size is the
191
- # smallest working set size in the job chain.
192
- #
193
- # * per_job_user_time_limit
194
- # The per-job user-mode execution time limit, in 100-nanosecond ticks.
195
- # The system adds the current time of the processes associated with the
196
- # job to this limit.
197
- #
198
- # For example, if you set this limit to 1 minute, and the job has a
199
- # process that has accumulated 5 minutes of user-mode time, the limit
200
- # actually enforced is 6 minutes.
201
- #
202
- # The system periodically checks to determine whether the sum of the
203
- # user-mode execution time for all processes is greater than this
204
- # end-of-job limit. If so all processes are terminated.
205
- #
206
- # * per_process_user_time_limit
207
- # The per-process user-mode execution time limit, in 100-nanosecond
208
- # ticks. The system periodically checks to determine whether each
209
- # process associated with the job has accumulated more user-mode time
210
- # than the set limit. If it has, the process is terminated.
211
- # If the job is nested, the effective limit is the most restrictive
212
- # limit in the job chain.
213
- #
214
- # * preserve_job_time => Boolean
215
- # Preserves any job time limits you previously set. As long as this flag
216
- # is set, you can establish a per-job time limit once, then alter other
217
- # limits in subsequent calls. This flag cannot be used with job_time.
218
- #
219
- # * priority_class => Numeric
220
- # Causes all processes associated with the job to use the same priority
221
- # class, e.g. ABOVE_NORMAL_PRIORITY_CLASS.
222
- #
223
- # * process_memory => Numeric
224
- # Causes all processes associated with the job to limit their committed
225
- # memory. When a process attempts to commit memory that would exceed
226
- # the per-process limit, it fails. If the job object is associated with
227
- # a completion port, a JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT message is
228
- # sent to the completion port. If the job is nested, the effective
229
- # memory limit is the most restrictive memory limit in the job chain.
230
- #
231
- # * process_time => Numeric
232
- # Establishes a user-mode execution time limit for each currently
233
- # active process and for all future processes associated with the job.
234
- #
235
- # * scheduling_class => Numeric
236
- # Causes all processes in the job to use the same scheduling class. If
237
- # the job is nested, the effective scheduling class is the lowest
238
- # scheduling class in the job chain.
239
- #
240
- # * silent_breakaway_ok => Boolean
241
- # Allows any process associated with the job to create child processes
242
- # that are not associated with the job. If the job is nested and its
243
- # immediate job object allows breakaway, the child process breaks away
244
- # from the immediate job object and from each job in the parent job chain,
245
- # moving up the hierarchy until it reaches a job that does not permit
246
- # breakaway. If the immediate job object does not allow breakaway, the
247
- # child process does not break away even if jobs in its parent job
248
- # chain allow it.
249
- #
250
- # * subset_affinity => Numeric
251
- # Allows processes to use a subset of the processor affinity for all
252
- # processes associated with the job.
253
- #--
254
- # The options are based on the LimitFlags of the
255
- # JOBOBJECT_BASIC_LIMIT_INFORMATION struct.
256
- #
257
- def configure_limit(options = {})
258
- unless options.is_a?(Hash)
259
- raise TypeError, "argument to configure must be a hash"
260
- end
261
-
262
- # Validate options
263
- options.each{ |key,value|
264
- unless VALID_OPTIONS.include?(key.to_s.downcase)
265
- raise ArgumentError, "invalid option '#{key}'"
266
- end
267
- }
268
-
269
- flags = 0
270
- struct = JOBOBJECT_EXTENDED_LIMIT_INFORMATION.new
271
-
272
- if options[:active_process]
273
- flags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS
274
- struct[:BasicInformatin][:ActiveProcessLimit] = options[:active_process]
275
- end
276
-
277
- if options[:affinity]
278
- flags |= JOB_OBJECT_LIMIT_AFFINITY
279
- struct[:BasicLimitInformation][:Affinity] = options[:affinity]
280
- end
281
-
282
- if options[:breakaway_ok]
283
- flags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK
284
- end
285
-
286
- if options[:die_on_unhandled_exception]
287
- flags |= JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION
288
- end
289
-
290
- if options[:job_memory]
291
- flags |= JOB_OBJECT_LIMIT_JOB_MEMORY
292
- struct[:JobMemoryLimit] = options[:job_memory]
293
- end
294
-
295
- if options[:per_job_user_time_limit]
296
- flags |= JOB_OBJECT_LIMIT_JOB_TIME
297
- struct[:BasicLimitInformation][:PerJobUserTimeLimit][:QuadPart] = options[:per_job_user_time_limit]
298
- end
299
-
300
- if options[:kill_on_job_close]
301
- flags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
302
- end
303
-
304
- if options[:preserve_job_time]
305
- flags |= JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME
306
- end
307
-
308
- if options[:priority_class]
309
- flags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS
310
- struct[:BasicLimitInformation][:PriorityClass] = options[:priority_class]
311
- end
312
-
313
- if options[:process_memory]
314
- flags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY
315
- struct[:ProcessMemoryLimit] = options[:process_memory]
316
- end
317
-
318
- if options[:process_time]
319
- flags |= JOB_OBJECT_LIMIT_PROCESS_TIME
320
- struct[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = options[:process_time]
321
- end
322
-
323
- if options[:scheduling_class]
324
- flags |= JOB_OBJECT_LIMIT_SCHEDULING_CLASS
325
- struct[:BasicLimitInformation][:SchedulingClass] = options[:scheduling_class]
326
- end
327
-
328
- if options[:silent_breakaway_ok]
329
- flags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK
330
- end
331
-
332
- if options[:subset_affinity]
333
- flags |= JOB_OBJECT_LIMIT_SUBSET_AFFINITY | JOB_OBJECT_LIMIT_AFFINITY
334
- end
335
-
336
- if options[:minimum_working_set_size]
337
- flags |= JOB_OBJECT_LIMIT_WORKINGSET
338
- struct[:BasicLimitInformation][:MinimumWorkingSetSize] = options[:minimum_working_set_size]
339
- end
340
-
341
- if options[:maximum_working_set_size]
342
- flags |= JOB_OBJECT_LIMIT_WORKINGSET
343
- struct[:BasicLimitInformation][:MaximumWorkingSetSize] = options[:maximum_working_set_size]
344
- end
345
-
346
- struct[:BasicLimitInformation][:LimitFlags] = flags
347
-
348
- bool = SetInformationJobObject(
349
- @job_handle,
350
- JobObjectExtendedLimitInformation,
351
- struct,
352
- struct.size
353
- )
354
-
355
- unless bool
356
- FFI.raise_windows_error('SetInformationJobObject', FFI.errno)
357
- end
358
-
359
- options
360
- end
361
-
362
- # Return a list of process ids that are part of the job.
363
- #
364
- def process_list
365
- info = JOBOBJECT_BASIC_PROCESS_ID_LIST.new
366
-
367
- bool = QueryInformationJobObject(
368
- @job_handle,
369
- JobObjectBasicProcessIdList,
370
- info,
371
- info.size,
372
- nil
373
- )
374
-
375
- unless bool
376
- FFI.raise_windows_error('QueryInformationJobObject', FFI.errno)
377
- end
378
-
379
- info[:ProcessIdList].to_a[0...info[:NumberOfProcessIdsInList]]
380
- end
381
-
382
- # Returns an AccountInfoStruct that shows various job accounting
383
- # information, such as total user time, total kernel time, the
384
- # total number of processes, and so on.
385
- #
386
- def account_info
387
- info = JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION.new
388
-
389
- bool = QueryInformationJobObject(
390
- @job_handle,
391
- JobObjectBasicAndIoAccountingInformation,
392
- info,
393
- info.size,
394
- nil
395
- )
396
-
397
- unless bool
398
- FFI.raise_windows_error('QueryInformationJobObject', FFI.errno)
399
- end
400
-
401
- struct = AccountInfo.new(
402
- info[:BasicInfo][:TotalUserTime][:QuadPart],
403
- info[:BasicInfo][:TotalKernelTime][:QuadPart],
404
- info[:BasicInfo][:ThisPeriodTotalUserTime][:QuadPart],
405
- info[:BasicInfo][:ThisPeriodTotalKernelTime][:QuadPart],
406
- info[:BasicInfo][:TotalPageFaultCount],
407
- info[:BasicInfo][:TotalProcesses],
408
- info[:BasicInfo][:ActiveProcesses],
409
- info[:BasicInfo][:TotalTerminatedProcesses],
410
- info[:IoInfo][:ReadOperationCount],
411
- info[:IoInfo][:WriteOperationCount],
412
- info[:IoInfo][:OtherOperationCount],
413
- info[:IoInfo][:ReadTransferCount],
414
- info[:IoInfo][:WriteTransferCount],
415
- info[:IoInfo][:OtherTransferCount]
416
- )
417
-
418
- struct
419
- end
420
-
421
- # Return limit information for the process group.
422
- #
423
- def limit_info
424
- info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION.new
425
-
426
- bool = QueryInformationJobObject(
427
- @job_handle,
428
- JobObjectExtendedLimitInformation,
429
- info,
430
- info.size,
431
- nil
432
- )
433
-
434
- unless bool
435
- FFI.raise_windows_error('QueryInformationJobObject', FFI.errno)
436
- end
437
-
438
- struct = LimitInfo.new(
439
- info[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart],
440
- info[:BasicLimitInformation][:PerJobUserTimeLimit][:QuadPart],
441
- info[:BasicLimitInformation][:LimitFlags],
442
- info[:BasicLimitInformation][:MinimumWorkingSetSize],
443
- info[:BasicLimitInformation][:MaximumWorkingSetSize],
444
- info[:BasicLimitInformation][:ActiveProcessLimit],
445
- info[:BasicLimitInformation][:Affinity],
446
- info[:BasicLimitInformation][:PriorityClass],
447
- info[:BasicLimitInformation][:SchedulingClass],
448
- info[:IoInfo][:ReadOperationCount],
449
- info[:IoInfo][:WriteOperationCount],
450
- info[:IoInfo][:OtherOperationCount],
451
- info[:IoInfo][:ReadTransferCount],
452
- info[:IoInfo][:WriteTransferCount],
453
- info[:IoInfo][:OtherTransferCount],
454
- info[:ProcessMemoryLimit],
455
- info[:JobMemoryLimit],
456
- info[:PeakProcessMemoryUsed],
457
- info[:PeakJobMemoryUsed]
458
- )
459
-
460
- struct
461
- end
462
-
463
- # Waits for the processes in the job to terminate.
464
- #--
465
- # See http://blogs.msdn.com/b/oldnewthing/archive/2013/04/05/10407778.aspx
466
- #
467
- def wait
468
- io_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
469
-
470
- if io_port == 0
471
- FFI.raise_windows_error('CreateIoCompletionPort', FFI.errno)
472
- end
473
-
474
- port = JOBOBJECT_ASSOCIATE_COMPLETION_PORT.new
475
- port[:CompletionKey] = @job_handle
476
- port[:CompletionPort] = io_port
477
-
478
- bool = SetInformationJobObject(
479
- @job_handle,
480
- JobObjectAssociateCompletionPortInformation,
481
- port,
482
- port.size
483
- )
484
-
485
- FFI.raise_windows_error('SetInformationJobObject', FFI.errno) unless bool
486
-
487
- olap = FFI::MemoryPointer.new(Overlapped)
488
- bytes = FFI::MemoryPointer.new(:ulong)
489
- ckey = FFI::MemoryPointer.new(:uintptr_t)
490
-
491
- while GetQueuedCompletionStatus(io_port, bytes, ckey, olap, INFINITE) &&
492
- !(ckey.read_pointer.to_i == @job_handle && bytes.read_ulong == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
493
- sleep 0.1
494
- end
495
- end
496
-
497
- private
498
-
499
- # Automatically close job object when it goes out of scope.
500
- #
501
- def self.finalize(handle, closed)
502
- proc{ CloseHandle(handle) unless closed }
503
- end
504
- end
505
- end
1
+ require_relative 'job/constants'
2
+ require_relative 'job/functions'
3
+ require_relative 'job/structs'
4
+ require_relative 'job/helper'
5
+
6
+ # The Win32 module serves as a namespace only.
7
+ module Win32
8
+
9
+ # The Job class encapsulates a Windows Job object.
10
+ class Job
11
+ include Windows::Constants
12
+ include Windows::Functions
13
+ include Windows::Structs
14
+ extend Windows::Functions
15
+
16
+ # The version of the win32-job library
17
+ VERSION = '0.1.4'
18
+
19
+ private
20
+
21
+ # Valid options for the configure method
22
+ VALID_OPTIONS = %w[
23
+ active_process
24
+ affinity
25
+ breakaway_ok
26
+ die_on_unhandled_exception
27
+ job_memory
28
+ job_time
29
+ kill_on_job_close
30
+ limit_job_time
31
+ limit_affinity
32
+ minimum_working_set
33
+ maximum_working_set
34
+ preserve_job_time
35
+ priority_class
36
+ process_memory
37
+ process_time
38
+ scheduling_class
39
+ silent_breakaway_ok
40
+ ]
41
+
42
+ public
43
+
44
+ # The name of the job, if specified.
45
+ attr_reader :job_name
46
+
47
+ alias :name :job_name
48
+
49
+ # Create a new Job object identified by +name+. If no name is provided
50
+ # then an anonymous job is created.
51
+ #
52
+ # If the job already exists then the existing job is opened instead, unless
53
+ # the +open_existing+ method is false. In that case an error is
54
+ # raised.
55
+ #
56
+ # The +security+ argument accepts a raw SECURITY_ATTRIBUTES struct that is
57
+ # passed to the CreateJobObject function internally.
58
+ #
59
+ def initialize(name = nil, open_existing = true, security = nil)
60
+ raise TypeError unless name.is_a?(String) if name
61
+
62
+ @job_name = name
63
+ @process_list = []
64
+ @closed = false
65
+
66
+ @job_handle = CreateJobObject(security, name)
67
+
68
+ if @job_handle == 0
69
+ FFI.raise_windows_error('CreateJobObject', FFI.errno)
70
+ end
71
+
72
+ if FFI.errno == ERROR_ALREADY_EXISTS && !open_existing
73
+ raise ArgumentError, "job '#{name}' already exists"
74
+ end
75
+
76
+ if block_given?
77
+ begin
78
+ yield self
79
+ ensure
80
+ close
81
+ end
82
+ end
83
+
84
+ ObjectSpace.define_finalizer(self, self.class.finalize(@job_handle, @closed))
85
+ end
86
+
87
+ # Add process +pid+ to the job object. Process ID's added to the
88
+ # job are tracked via the Job#process_list accessor.
89
+ #
90
+ # Note that once a process is added to a job, the association cannot be
91
+ # broken. A process can be associated with more than one job in a
92
+ # hierarchy of nested jobs, however.
93
+ #
94
+ # You may add a maximum of 100 processes per job.
95
+ #
96
+ def add_process(pid)
97
+ if @process_list.size > 99
98
+ raise ArgumentError, "maximum number of processes reached"
99
+ end
100
+
101
+ phandle = OpenProcess(PROCESS_ALL_ACCESS, false, pid)
102
+
103
+ if phandle == 0
104
+ FFI.raise_windows_error('OpenProcess', FFI.errno)
105
+ end
106
+
107
+ pbool = FFI::MemoryPointer.new(:int)
108
+
109
+ IsProcessInJob(phandle, 0, pbool)
110
+
111
+ if pbool.read_int == 0
112
+ unless AssignProcessToJobObject(@job_handle, phandle)
113
+ FFI.raise_windows_error('AssignProcessToJobObject', FFI.errno)
114
+ end
115
+ @process_list << pid
116
+ else
117
+ raise ArgumentError, "pid #{pid} is already part of a job"
118
+ end
119
+
120
+ pid
121
+ end
122
+
123
+ # Close the job object.
124
+ #
125
+ def close
126
+ CloseHandle(@job_handle) if @job_handle
127
+ @closed = true
128
+ end
129
+
130
+ # Kill all processes associated with the job object that are
131
+ # associated with the current process.
132
+ #
133
+ # Note that killing a process does not dissociate it from the job.
134
+ #
135
+ def kill
136
+ unless TerminateJobObject(@job_handle, Process.pid)
137
+ FFI.raise_windows_error('TerminateJobObject', FFI.errno)
138
+ end
139
+
140
+ @process_list = []
141
+ end
142
+
143
+ alias terminate kill
144
+
145
+ # Set various job limits. Possible options are:
146
+ #
147
+ # * active_process => Numeric
148
+ # Establishes a maximum number of simultaneously active processes
149
+ # associated with the job.
150
+ #
151
+ # * affinity => Numeric
152
+ # Causes all processes associated with the job to use the same
153
+ # processor affinity.
154
+ #
155
+ # * breakaway_ok => Boolean
156
+ # If any process associated with the job creates a child process using
157
+ # the CREATE_BREAKAWAY_FROM_JOB flag while this limit is in effect, the
158
+ # child process is not associated with the job.
159
+ #
160
+ # * die_on_unhandled_exception => Boolean
161
+ # Forces a call to the SetErrorMode function with the SEM_NOGPFAULTERRORBOX
162
+ # flag for each process associated with the job. If an exception occurs
163
+ # and the system calls the UnhandledExceptionFilter function, the debugger
164
+ # will be given a chance to act. If there is no debugger, the functions
165
+ # returns EXCEPTION_EXECUTE_HANDLER. Normally, this will cause termination
166
+ # of the process with the exception code as the exit status.
167
+ #
168
+ # * job_memory => Numeric
169
+ # Causes all processes associated with the job to limit the job-wide
170
+ # sum of their committed memory. When a process attempts to commit
171
+ # memory that would exceed the job-wide limit, it fails. If the job
172
+ # object is associated with a completion port, a
173
+ # JOB_OBJECT_MSG_JOB_MEMORY_LIMIT message is sent to the completion
174
+ # port.
175
+ #
176
+ # * job_time => Numeric
177
+ # Establishes a user-mode execution time limit for the job.
178
+ #
179
+ # * kill_on_job_close => Boolean
180
+ # Causes all processes associated with the job to terminate when the
181
+ # last handle to the job is closed.
182
+ #
183
+ # * minimum_working_set_size => Numeric
184
+ # Causes all processes associated with the job to use the same minimum
185
+ # set size. If the job is nested, the effective working set size is the
186
+ # smallest working set size in the job chain.
187
+ #
188
+ # * maximum_working_set_size => Numeric
189
+ # Causes all processes associated with the job to use the same maximum
190
+ # set size. If the job is nested, the effective working set size is the
191
+ # smallest working set size in the job chain.
192
+ #
193
+ # * per_job_user_time_limit
194
+ # The per-job user-mode execution time limit, in 100-nanosecond ticks.
195
+ # The system adds the current time of the processes associated with the
196
+ # job to this limit.
197
+ #
198
+ # For example, if you set this limit to 1 minute, and the job has a
199
+ # process that has accumulated 5 minutes of user-mode time, the limit
200
+ # actually enforced is 6 minutes.
201
+ #
202
+ # The system periodically checks to determine whether the sum of the
203
+ # user-mode execution time for all processes is greater than this
204
+ # end-of-job limit. If so all processes are terminated.
205
+ #
206
+ # * per_process_user_time_limit
207
+ # The per-process user-mode execution time limit, in 100-nanosecond
208
+ # ticks. The system periodically checks to determine whether each
209
+ # process associated with the job has accumulated more user-mode time
210
+ # than the set limit. If it has, the process is terminated.
211
+ # If the job is nested, the effective limit is the most restrictive
212
+ # limit in the job chain.
213
+ #
214
+ # * preserve_job_time => Boolean
215
+ # Preserves any job time limits you previously set. As long as this flag
216
+ # is set, you can establish a per-job time limit once, then alter other
217
+ # limits in subsequent calls. This flag cannot be used with job_time.
218
+ #
219
+ # * priority_class => Numeric
220
+ # Causes all processes associated with the job to use the same priority
221
+ # class, e.g. ABOVE_NORMAL_PRIORITY_CLASS.
222
+ #
223
+ # * process_memory => Numeric
224
+ # Causes all processes associated with the job to limit their committed
225
+ # memory. When a process attempts to commit memory that would exceed
226
+ # the per-process limit, it fails. If the job object is associated with
227
+ # a completion port, a JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT message is
228
+ # sent to the completion port. If the job is nested, the effective
229
+ # memory limit is the most restrictive memory limit in the job chain.
230
+ #
231
+ # * process_time => Numeric
232
+ # Establishes a user-mode execution time limit for each currently
233
+ # active process and for all future processes associated with the job.
234
+ #
235
+ # * scheduling_class => Numeric
236
+ # Causes all processes in the job to use the same scheduling class. If
237
+ # the job is nested, the effective scheduling class is the lowest
238
+ # scheduling class in the job chain.
239
+ #
240
+ # * silent_breakaway_ok => Boolean
241
+ # Allows any process associated with the job to create child processes
242
+ # that are not associated with the job. If the job is nested and its
243
+ # immediate job object allows breakaway, the child process breaks away
244
+ # from the immediate job object and from each job in the parent job chain,
245
+ # moving up the hierarchy until it reaches a job that does not permit
246
+ # breakaway. If the immediate job object does not allow breakaway, the
247
+ # child process does not break away even if jobs in its parent job
248
+ # chain allow it.
249
+ #
250
+ # * subset_affinity => Numeric
251
+ # Allows processes to use a subset of the processor affinity for all
252
+ # processes associated with the job.
253
+ #--
254
+ # The options are based on the LimitFlags of the
255
+ # JOBOBJECT_BASIC_LIMIT_INFORMATION struct.
256
+ #
257
+ def configure_limit(options = {})
258
+ unless options.is_a?(Hash)
259
+ raise TypeError, "argument to configure must be a hash"
260
+ end
261
+
262
+ # Validate options
263
+ options.each{ |key,value|
264
+ unless VALID_OPTIONS.include?(key.to_s.downcase)
265
+ raise ArgumentError, "invalid option '#{key}'"
266
+ end
267
+ }
268
+
269
+ flags = 0
270
+ struct = JOBOBJECT_EXTENDED_LIMIT_INFORMATION.new
271
+
272
+ if options[:active_process]
273
+ flags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS
274
+ struct[:BasicInformatin][:ActiveProcessLimit] = options[:active_process]
275
+ end
276
+
277
+ if options[:affinity]
278
+ flags |= JOB_OBJECT_LIMIT_AFFINITY
279
+ struct[:BasicLimitInformation][:Affinity] = options[:affinity]
280
+ end
281
+
282
+ if options[:breakaway_ok]
283
+ flags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK
284
+ end
285
+
286
+ if options[:die_on_unhandled_exception]
287
+ flags |= JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION
288
+ end
289
+
290
+ if options[:job_memory]
291
+ flags |= JOB_OBJECT_LIMIT_JOB_MEMORY
292
+ struct[:JobMemoryLimit] = options[:job_memory]
293
+ end
294
+
295
+ if options[:per_job_user_time_limit]
296
+ flags |= JOB_OBJECT_LIMIT_JOB_TIME
297
+ struct[:BasicLimitInformation][:PerJobUserTimeLimit][:QuadPart] = options[:per_job_user_time_limit]
298
+ end
299
+
300
+ if options[:kill_on_job_close]
301
+ flags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
302
+ end
303
+
304
+ if options[:preserve_job_time]
305
+ flags |= JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME
306
+ end
307
+
308
+ if options[:priority_class]
309
+ flags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS
310
+ struct[:BasicLimitInformation][:PriorityClass] = options[:priority_class]
311
+ end
312
+
313
+ if options[:process_memory]
314
+ flags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY
315
+ struct[:ProcessMemoryLimit] = options[:process_memory]
316
+ end
317
+
318
+ if options[:process_time]
319
+ flags |= JOB_OBJECT_LIMIT_PROCESS_TIME
320
+ struct[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = options[:process_time]
321
+ end
322
+
323
+ if options[:scheduling_class]
324
+ flags |= JOB_OBJECT_LIMIT_SCHEDULING_CLASS
325
+ struct[:BasicLimitInformation][:SchedulingClass] = options[:scheduling_class]
326
+ end
327
+
328
+ if options[:silent_breakaway_ok]
329
+ flags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK
330
+ end
331
+
332
+ if options[:subset_affinity]
333
+ flags |= JOB_OBJECT_LIMIT_SUBSET_AFFINITY | JOB_OBJECT_LIMIT_AFFINITY
334
+ end
335
+
336
+ if options[:minimum_working_set_size]
337
+ flags |= JOB_OBJECT_LIMIT_WORKINGSET
338
+ struct[:BasicLimitInformation][:MinimumWorkingSetSize] = options[:minimum_working_set_size]
339
+ end
340
+
341
+ if options[:maximum_working_set_size]
342
+ flags |= JOB_OBJECT_LIMIT_WORKINGSET
343
+ struct[:BasicLimitInformation][:MaximumWorkingSetSize] = options[:maximum_working_set_size]
344
+ end
345
+
346
+ struct[:BasicLimitInformation][:LimitFlags] = flags
347
+
348
+ bool = SetInformationJobObject(
349
+ @job_handle,
350
+ JobObjectExtendedLimitInformation,
351
+ struct,
352
+ struct.size
353
+ )
354
+
355
+ unless bool
356
+ FFI.raise_windows_error('SetInformationJobObject', FFI.errno)
357
+ end
358
+
359
+ options
360
+ end
361
+
362
+ # Return a list of process ids that are part of the job.
363
+ #
364
+ def process_list
365
+ info = JOBOBJECT_BASIC_PROCESS_ID_LIST.new
366
+
367
+ bool = QueryInformationJobObject(
368
+ @job_handle,
369
+ JobObjectBasicProcessIdList,
370
+ info,
371
+ info.size,
372
+ nil
373
+ )
374
+
375
+ unless bool
376
+ FFI.raise_windows_error('QueryInformationJobObject', FFI.errno)
377
+ end
378
+
379
+ info[:ProcessIdList].to_a[0...info[:NumberOfProcessIdsInList]]
380
+ end
381
+
382
+ # Returns an AccountInfoStruct that shows various job accounting
383
+ # information, such as total user time, total kernel time, the
384
+ # total number of processes, and so on.
385
+ #
386
+ def account_info
387
+ info = JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION.new
388
+
389
+ bool = QueryInformationJobObject(
390
+ @job_handle,
391
+ JobObjectBasicAndIoAccountingInformation,
392
+ info,
393
+ info.size,
394
+ nil
395
+ )
396
+
397
+ unless bool
398
+ FFI.raise_windows_error('QueryInformationJobObject', FFI.errno)
399
+ end
400
+
401
+ struct = AccountInfo.new(
402
+ info[:BasicInfo][:TotalUserTime][:QuadPart],
403
+ info[:BasicInfo][:TotalKernelTime][:QuadPart],
404
+ info[:BasicInfo][:ThisPeriodTotalUserTime][:QuadPart],
405
+ info[:BasicInfo][:ThisPeriodTotalKernelTime][:QuadPart],
406
+ info[:BasicInfo][:TotalPageFaultCount],
407
+ info[:BasicInfo][:TotalProcesses],
408
+ info[:BasicInfo][:ActiveProcesses],
409
+ info[:BasicInfo][:TotalTerminatedProcesses],
410
+ info[:IoInfo][:ReadOperationCount],
411
+ info[:IoInfo][:WriteOperationCount],
412
+ info[:IoInfo][:OtherOperationCount],
413
+ info[:IoInfo][:ReadTransferCount],
414
+ info[:IoInfo][:WriteTransferCount],
415
+ info[:IoInfo][:OtherTransferCount]
416
+ )
417
+
418
+ struct
419
+ end
420
+
421
+ # Return limit information for the process group.
422
+ #
423
+ def limit_info
424
+ info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION.new
425
+
426
+ bool = QueryInformationJobObject(
427
+ @job_handle,
428
+ JobObjectExtendedLimitInformation,
429
+ info,
430
+ info.size,
431
+ nil
432
+ )
433
+
434
+ unless bool
435
+ FFI.raise_windows_error('QueryInformationJobObject', FFI.errno)
436
+ end
437
+
438
+ struct = LimitInfo.new(
439
+ info[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart],
440
+ info[:BasicLimitInformation][:PerJobUserTimeLimit][:QuadPart],
441
+ info[:BasicLimitInformation][:LimitFlags],
442
+ info[:BasicLimitInformation][:MinimumWorkingSetSize],
443
+ info[:BasicLimitInformation][:MaximumWorkingSetSize],
444
+ info[:BasicLimitInformation][:ActiveProcessLimit],
445
+ info[:BasicLimitInformation][:Affinity],
446
+ info[:BasicLimitInformation][:PriorityClass],
447
+ info[:BasicLimitInformation][:SchedulingClass],
448
+ info[:IoInfo][:ReadOperationCount],
449
+ info[:IoInfo][:WriteOperationCount],
450
+ info[:IoInfo][:OtherOperationCount],
451
+ info[:IoInfo][:ReadTransferCount],
452
+ info[:IoInfo][:WriteTransferCount],
453
+ info[:IoInfo][:OtherTransferCount],
454
+ info[:ProcessMemoryLimit],
455
+ info[:JobMemoryLimit],
456
+ info[:PeakProcessMemoryUsed],
457
+ info[:PeakJobMemoryUsed]
458
+ )
459
+
460
+ struct
461
+ end
462
+
463
+ # Waits for the processes in the job to terminate.
464
+ #--
465
+ # See http://blogs.msdn.com/b/oldnewthing/archive/2013/04/05/10407778.aspx
466
+ #
467
+ def wait
468
+ io_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
469
+
470
+ if io_port == 0
471
+ FFI.raise_windows_error('CreateIoCompletionPort', FFI.errno)
472
+ end
473
+
474
+ port = JOBOBJECT_ASSOCIATE_COMPLETION_PORT.new
475
+ port[:CompletionKey] = @job_handle
476
+ port[:CompletionPort] = io_port
477
+
478
+ bool = SetInformationJobObject(
479
+ @job_handle,
480
+ JobObjectAssociateCompletionPortInformation,
481
+ port,
482
+ port.size
483
+ )
484
+
485
+ FFI.raise_windows_error('SetInformationJobObject', FFI.errno) unless bool
486
+
487
+ olap = FFI::MemoryPointer.new(Overlapped)
488
+ bytes = FFI::MemoryPointer.new(:ulong)
489
+ ckey = FFI::MemoryPointer.new(:uintptr_t)
490
+
491
+ while GetQueuedCompletionStatus(io_port, bytes, ckey, olap, INFINITE) &&
492
+ !(ckey.read_pointer.to_i == @job_handle && bytes.read_ulong == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
493
+ sleep 0.1
494
+ end
495
+ end
496
+
497
+ private
498
+
499
+ # Automatically close job object when it goes out of scope.
500
+ #
501
+ def self.finalize(handle, closed)
502
+ proc{ CloseHandle(handle) unless closed }
503
+ end
504
+ end
505
+ end