win32-job 0.1.3 → 0.1.4

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 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