class Elevage::ProvisionerRunQueue

ProvisionerRunQueue

Manage multiple queued and running `Elevage::Provisioner` objects/threads

Attributes

build_status_interval[RW]
busy_wait_timeout[RW]
max_concurrent[RW]
provisioners[RW]
running_tasks[R]

Public Class Methods

new() click to toggle source

Create a new run queue @return [Elevage::ProvisionerRunQueue]

# File lib/elevage/provisionerrunqueue.rb, line 16
def initialize
  @running_tasks = 0 # We start out with nothing running
  @max_concurrent = BUILD_CONCURRENT_DEFAULT
  @busy_wait_timeout = BUILD_CHILD_WAIT_TIMEOUT
  @build_status_interval = BUILD_STATUS_INTERVAL
  @provisioners = []
  @children = {}
end

Public Instance Methods

run() click to toggle source

Process the queue

# File lib/elevage/provisionerrunqueue.rb, line 28
def run
  puts "#{Time.now} [#{$$}]: Provisioning started."
  @provisioners.each do |provisioner|
    # Make sure we're not running more jobs than we're allowed
    wait_for_tasks
    child_pid = fork do
      provision_task task: provisioner
    end
    @children[child_pid] = provisioner.name
    @running_tasks += 1
  end
  # Hang around until we collect all the rest of the children
  wait_for_tasks state: :collect
  puts "#{Time.now} [#{$$}]: Provisioning completed."
end
to_s() click to toggle source

Display a string representation @return [String]

# File lib/elevage/provisionerrunqueue.rb, line 49
def to_s
  puts "Running Tasks: #{@running_tasks}"
  puts "Max Concurrency: #{@max_concurrent}"
  puts "Wait status interval: #{@build_status_interval}"
  puts 'Current Child processes:'
  @children.each do |pid, name|
    puts " - [#{pid}]: #{name}"
  end
  puts 'Queued Provisioners:'
  @provisioners.each do |provisioner|
    puts " - #{provisioner.name}"
  end
end

Private Instance Methods

provision_task(task: nil) click to toggle source

Private

provision_task is the method that should execute in the child process, and contain all the logic for the child process. @param [Elevage::Provisioner] task a Provisioner from the queue

# File lib/elevage/provisionerrunqueue.rb, line 73
def provision_task(task: nil)
  start_time = Time.now
  print "#{Time.now} [#{$$}]: #{task.name} Provisioning...\n"
  status = task.build ? 'succeeded' : 'FAILED'
  run_time = Time.now - start_time
  print "#{Time.now} [#{$$}]: #{task.name} #{status} in #{run_time.round(2)} seconds.\n"
end
wait_for_tasks(state: :running) click to toggle source

Private

Wait for child tasks to return Since our trap for SIGCHLD will clean up the @running_tasks count and the children hash, here we can just keep checking until @running_tasks is 0. If we've been waiting at least a minute, print out a notice of what we're still waiting for. @param [String] state Disposition; whether we are still `:running` or are trying to `:collect` still-running tasks

# File lib/elevage/provisionerrunqueue.rb, line 94
def wait_for_tasks(state: :running)
  i = interval = @build_status_interval / @busy_wait_timeout

  # We are either "running", and waiting for a child to return so we can
  # dispatch a new child, or we are "collecting", in which case we have
  # no more children waiting to be dispatched, and are waiting for them
  # all to finish.
  while @running_tasks >= @max_concurrent && state.eql?(:running) || @running_tasks > 0 && state.eql?(:collect)

    # Always having to clean up after our children...
    @children.keys.each do |pid|
      childpid = Process.wait(pid, Process::WNOHANG | Process::WUNTRACED)
      unless childpid.nil?
        @children.delete(childpid)
        @running_tasks -= 1
      end
    end

    # Is it time for a status update yet?
    if i <= 0
      print "#{Time.now} [#{$$}]: Waiting for #{@children.size} jobs:\n"
      @children.each do |pid, name|
        print " - #{pid}: #{name}\n"
      end
      # reset the status counter
      i = interval
    end

    # tick the status counter
    i -= 1
    sleep @busy_wait_timeout
  end
end