Class: Win32::Job
- Inherits:
-
Object
- Object
- Win32::Job
- Extended by:
- Windows::Functions
- Includes:
- Windows::Constants, Windows::Functions, Windows::Structs
- Defined in:
- lib/win32/job.rb
Overview
The Job class encapsulates a Windows Job object.
Constant Summary collapse
- VERSION =
The version of the win32-job library
'0.1.3'
Constants included from Windows::Constants
Windows::Constants::ABOVE_NORMAL_PRIORITY_CLASS, Windows::Constants::BELOW_NORMAL_PRIORITY_CLASS, Windows::Constants::HIGH_PRIORITY_CLASS, Windows::Constants::IDLE_PRIORITY_CLASS, Windows::Constants::NORMAL_PRIORITY_CLASS, Windows::Constants::REALTIME_PRIORITY_CLASS
Instance Attribute Summary collapse
-
#job_name ⇒ Object
(also: #name)
readonly
The name of the job, if specified.
Instance Method Summary collapse
-
#account_info ⇒ Object
Returns an AccountInfoStruct that shows various job accounting information, such as total user time, total kernel time, the total number of processes, and so on.
-
#add_process(pid) ⇒ Object
Add process
pid
to the job object. -
#close ⇒ Object
Close the job object.
-
#configure_limit(options = {}) ⇒ Object
Set various job limits.
-
#initialize(name = nil, open_existing = true, security = nil) ⇒ Job
constructor
Create a new Job object identified by
name
. -
#kill ⇒ Object
(also: #terminate)
Kill all processes associated with the job object that are associated with the current process.
-
#limit_info ⇒ Object
Return limit information for the process group.
-
#process_list ⇒ Object
Return a list of process ids that are part of the job.
-
#wait ⇒ Object
Waits for the processes in the job to terminate.
Constructor Details
#initialize(name = nil, open_existing = true, security = nil) ⇒ Job
Create a new Job object identified by name
. If no name is provided then an anonymous job is created.
If the job already exists then the existing job is opened instead, unless the open_existing
method is false. In that case an error is raised.
The security
argument accepts a raw SECURITY_ATTRIBUTES struct that is passed to the CreateJobObject function internally.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/win32/job.rb', line 59 def initialize(name = nil, open_existing = true, security = nil) raise TypeError unless name.is_a?(String) if name @job_name = name @process_list = [] @closed = false @job_handle = CreateJobObject(security, name) if @job_handle == 0 FFI.raise_windows_error('CreateJobObject', FFI.errno) end if FFI.errno == ERROR_ALREADY_EXISTS && !open_existing raise ArgumentError, "job '#{name}' already exists" end if block_given? begin yield self ensure close end end ObjectSpace.define_finalizer(self, self.class.finalize(@job_handle, @closed)) end |
Instance Attribute Details
#job_name ⇒ Object (readonly) Also known as: name
The name of the job, if specified.
45 46 47 |
# File 'lib/win32/job.rb', line 45 def job_name @job_name end |
Instance Method Details
#account_info ⇒ Object
Returns an AccountInfoStruct that shows various job accounting information, such as total user time, total kernel time, the total number of processes, and so on.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/win32/job.rb', line 386 def account_info info = JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION.new bool = QueryInformationJobObject( @job_handle, JobObjectBasicAndIoAccountingInformation, info, info.size, nil ) unless bool FFI.raise_windows_error('QueryInformationJobObject', FFI.errno) end struct = AccountInfo.new( info[:BasicInfo][:TotalUserTime][:QuadPart], info[:BasicInfo][:TotalKernelTime][:QuadPart], info[:BasicInfo][:ThisPeriodTotalUserTime][:QuadPart], info[:BasicInfo][:ThisPeriodTotalKernelTime][:QuadPart], info[:BasicInfo][:TotalPageFaultCount], info[:BasicInfo][:TotalProcesses], info[:BasicInfo][:ActiveProcesses], info[:BasicInfo][:TotalTerminatedProcesses], info[:IoInfo][:ReadOperationCount], info[:IoInfo][:WriteOperationCount], info[:IoInfo][:OtherOperationCount], info[:IoInfo][:ReadTransferCount], info[:IoInfo][:WriteTransferCount], info[:IoInfo][:OtherTransferCount] ) struct end |
#add_process(pid) ⇒ Object
Add process pid
to the job object. Process ID’s added to the job are tracked via the Job#process_list accessor.
Note that once a process is added to a job, the association cannot be broken. A process can be associated with more than one job in a hierarchy of nested jobs, however.
You may add a maximum of 100 processes per job.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/win32/job.rb', line 96 def add_process(pid) if @process_list.size > 99 raise ArgumentError, "maximum number of processes reached" end phandle = OpenProcess(PROCESS_ALL_ACCESS, false, pid) if phandle == 0 FFI.raise_windows_error('OpenProcess', FFI.errno) end pbool = FFI::MemoryPointer.new(:int) IsProcessInJob(phandle, 0, pbool) if pbool.read_int == 0 unless AssignProcessToJobObject(@job_handle, phandle) FFI.raise_windows_error('AssignProcessToJobObject', FFI.errno) end @process_list << pid else raise ArgumentError, "pid #{pid} is already part of a job" end pid end |
#close ⇒ Object
Close the job object.
125 126 127 128 |
# File 'lib/win32/job.rb', line 125 def close CloseHandle(@job_handle) if @job_handle @closed = true end |
#configure_limit(options = {}) ⇒ Object
Set various job limits. Possible options are:
-
active_process => Numeric
Establishes a maximum number of simultaneously active processes associated with the job.
-
affinity => Numeric
Causes all processes associated with the job to use the same processor affinity.
-
breakaway_ok => Boolean
If any process associated with the job creates a child process using the CREATE_BREAKAWAY_FROM_JOB flag while this limit is in effect, the child process is not associated with the job.
-
die_on_unhandled_exception => Boolean
Forces a call to the SetErrorMode function with the SEM_NOGPFAULTERRORBOX flag for each process associated with the job. If an exception occurs and the system calls the UnhandledExceptionFilter function, the debugger will be given a chance to act. If there is no debugger, the functions returns EXCEPTION_EXECUTE_HANDLER. Normally, this will cause termination of the process with the exception code as the exit status.
-
job_memory => Numeric
Causes all processes associated with the job to limit the job-wide sum of their committed memory. When a process attempts to commit memory that would exceed the job-wide limit, it fails. If the job object is associated with a completion port, a JOB_OBJECT_MSG_JOB_MEMORY_LIMIT message is sent to the completion port.
-
job_time => Numeric
Establishes a user-mode execution time limit for the job.
-
kill_on_job_close => Boolean
Causes all processes associated with the job to terminate when the last handle to the job is closed.
-
minimum_working_set_size => Numeric
Causes all processes associated with the job to use the same minimum set size. If the job is nested, the effective working set size is the smallest working set size in the job chain.
-
maximum_working_set_size => Numeric
Causes all processes associated with the job to use the same maximum set size. If the job is nested, the effective working set size is the smallest working set size in the job chain.
-
per_job_user_time_limit
The per-job user-mode execution time limit, in 100-nanosecond ticks. The system adds the current time of the processes associated with the job to this limit. For example, if you set this limit to 1 minute, and the job has a process that has accumulated 5 minutes of user-mode time, the limit actually enforced is 6 minutes. The system periodically checks to determine whether the sum of the user-mode execution time for all processes is greater than this end-of-job limit. If so all processes are terminated.
-
per_process_user_time_limit
The per-process user-mode execution time limit, in 100-nanosecond ticks. The system periodically checks to determine whether each process associated with the job has accumulated more user-mode time than the set limit. If it has, the process is terminated. If the job is nested, the effective limit is the most restrictive limit in the job chain.
-
preserve_job_time => Boolean
Preserves any job time limits you previously set. As long as this flag is set, you can establish a per-job time limit once, then alter other limits in subsequent calls. This flag cannot be used with job_time.
-
priority_class => Numeric
Causes all processes associated with the job to use the same priority class, e.g. ABOVE_NORMAL_PRIORITY_CLASS.
-
process_memory => Numeric
Causes all processes associated with the job to limit their committed memory. When a process attempts to commit memory that would exceed the per-process limit, it fails. If the job object is associated with a completion port, a JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT message is sent to the completion port. If the job is nested, the effective memory limit is the most restrictive memory limit in the job chain.
-
process_time => Numeric
Establishes a user-mode execution time limit for each currently active process and for all future processes associated with the job.
-
scheduling_class => Numeric
Causes all processes in the job to use the same scheduling class. If the job is nested, the effective scheduling class is the lowest scheduling class in the job chain.
-
silent_breakaway_ok => Boolean
Allows any process associated with the job to create child processes that are not associated with the job. If the job is nested and its immediate job object allows breakaway, the child process breaks away from the immediate job object and from each job in the parent job chain, moving up the hierarchy until it reaches a job that does not permit breakaway. If the immediate job object does not allow breakaway, the child process does not break away even if jobs in its parent job chain allow it.
-
subset_affinity => Numeric
Allows processes to use a subset of the processor affinity for all processes associated with the job.
– The options are based on the LimitFlags of the JOBOBJECT_BASIC_LIMIT_INFORMATION struct.
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/win32/job.rb', line 257 def configure_limit( = {}) unless .is_a?(Hash) raise TypeError, "argument to configure must be a hash" end # Validate options .each{ |key,value| unless VALID_OPTIONS.include?(key.to_s.downcase) raise ArgumentError, "invalid option '#{key}'" end } flags = 0 struct = JOBOBJECT_EXTENDED_LIMIT_INFORMATION.new if [:active_process] flags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS struct[:BasicInformatin][:ActiveProcessLimit] = [:active_process] end if [:affinity] flags |= JOB_OBJECT_LIMIT_AFFINITY struct[:BasicLimitInformation][:Affinity] = [:affinity] end if [:breakaway_ok] flags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK end if [:die_on_unhandled_exception] flags |= JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION end if [:job_memory] flags |= JOB_OBJECT_LIMIT_JOB_MEMORY struct[:JobMemoryLimit] = [:job_memory] end if [:per_job_user_time_limit] flags |= JOB_OBJECT_LIMIT_JOB_TIME struct[:BasicLimitInformation][:PerJobUserTimeLimit][:QuadPart] = [:per_job_user_time_limit] end if [:kill_on_job_close] flags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE end if [:preserve_job_time] flags |= JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME end if [:priority_class] flags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS struct[:BasicLimitInformation][:PriorityClass] = [:priority_class] end if [:process_memory] flags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY struct[:ProcessMemoryLimit] = [:process_memory] end if [:process_time] flags |= JOB_OBJECT_LIMIT_PROCESS_TIME struct[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = [:process_time] end if [:scheduling_class] flags |= JOB_OBJECT_LIMIT_SCHEDULING_CLASS struct[:BasicLimitInformation][:SchedulingClass] = [:scheduling_class] end if [:silent_breakaway_ok] flags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK end if [:subset_affinity] flags |= JOB_OBJECT_LIMIT_SUBSET_AFFINITY | JOB_OBJECT_LIMIT_AFFINITY end if [:minimum_working_set_size] flags |= JOB_OBJECT_LIMIT_WORKINGSET struct[:BasicLimitInformation][:MinimumWorkingSetSize] = [:minimum_working_set_size] end if [:maximum_working_set_size] flags |= JOB_OBJECT_LIMIT_WORKINGSET struct[:BasicLimitInformation][:MaximumWorkingSetSize] = [:maximum_working_set_size] end struct[:BasicLimitInformation][:LimitFlags] = flags bool = SetInformationJobObject( @job_handle, JobObjectExtendedLimitInformation, struct, struct.size ) unless bool FFI.raise_windows_error('SetInformationJobObject', FFI.errno) end end |
#kill ⇒ Object Also known as: terminate
Kill all processes associated with the job object that are associated with the current process.
Note that killing a process does not dissociate it from the job.
135 136 137 138 139 140 141 |
# File 'lib/win32/job.rb', line 135 def kill unless TerminateJobObject(@job_handle, Process.pid) FFI.raise_windows_error('TerminateJobObject', FFI.errno) end @process_list = [] end |
#limit_info ⇒ Object
Return limit information for the process group.
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/win32/job.rb', line 423 def limit_info info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION.new bool = QueryInformationJobObject( @job_handle, JobObjectExtendedLimitInformation, info, info.size, nil ) unless bool FFI.raise_windows_error('QueryInformationJobObject', FFI.errno) end struct = LimitInfo.new( info[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart], info[:BasicLimitInformation][:PerJobUserTimeLimit][:QuadPart], info[:BasicLimitInformation][:LimitFlags], info[:BasicLimitInformation][:MinimumWorkingSetSize], info[:BasicLimitInformation][:MaximumWorkingSetSize], info[:BasicLimitInformation][:ActiveProcessLimit], info[:BasicLimitInformation][:Affinity], info[:BasicLimitInformation][:PriorityClass], info[:BasicLimitInformation][:SchedulingClass], info[:IoInfo][:ReadOperationCount], info[:IoInfo][:WriteOperationCount], info[:IoInfo][:OtherOperationCount], info[:IoInfo][:ReadTransferCount], info[:IoInfo][:WriteTransferCount], info[:IoInfo][:OtherTransferCount], info[:ProcessMemoryLimit], info[:JobMemoryLimit], info[:PeakProcessMemoryUsed], info[:PeakJobMemoryUsed] ) struct end |
#process_list ⇒ Object
Return a list of process ids that are part of the job.
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/win32/job.rb', line 364 def process_list info = JOBOBJECT_BASIC_PROCESS_ID_LIST.new bool = QueryInformationJobObject( @job_handle, JobObjectBasicProcessIdList, info, info.size, nil ) unless bool FFI.raise_windows_error('QueryInformationJobObject', FFI.errno) end info[:ProcessIdList].to_a[0...info[:NumberOfProcessIdsInList]] end |
#wait ⇒ Object
Waits for the processes in the job to terminate. – See blogs.msdn.com/b/oldnewthing/archive/2013/04/05/10407778.aspx
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'lib/win32/job.rb', line 467 def wait io_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) if io_port == 0 FFI.raise_windows_error('CreateIoCompletionPort', FFI.errno) end port = JOBOBJECT_ASSOCIATE_COMPLETION_PORT.new port[:CompletionKey] = @job_handle port[:CompletionPort] = io_port bool = SetInformationJobObject( @job_handle, JobObjectAssociateCompletionPortInformation, port, port.size ) FFI.raise_windows_error('SetInformationJobObject', FFI.errno) unless bool olap = FFI::MemoryPointer.new(Overlapped) bytes = FFI::MemoryPointer.new(:ulong) ckey = FFI::MemoryPointer.new(:uintptr_t) while GetQueuedCompletionStatus(io_port, bytes, ckey, olap, INFINITE) && !(ckey.read_pointer.to_i == @job_handle && bytes.read_ulong == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) sleep 0.1 end end |