def run
      # short circuit if we're not serial and already running
      return if @solver_thread and !@serial

      handler = lambda do |*args|
        p ["solved:", solved.to_a]
        p ["working:", vm_working.to_a]
        p ["waiting:", @waiters.to_a]
      end

      %w[USR2 INFO].each { |sig| trap(sig, &handler) if Signal.list[sig] }

      queue_runner = lambda do
        run = true

        while run
          service_resolved_waiters

          ready = []

          if @queue.empty?
            if @serial
              return
            else
              with_timeout do
                # this is where most of the execution time is spent, so ensure
                # waiters get considered here.
                service_resolved_waiters
                ready << @queue.shift
              end
            end
          end

          while !@queue.empty?
            ready << @queue.shift
          end

          ready.each do |r|
            if r
              @solved_mutex.synchronize do
                solved.add(r)
                @working.delete(r)
                vm_working.delete(r)
              end
            else
              run = false
            end
          end
        end
      end

      if @serial
        service_resolved_waiters
        queue_runner.call
      else
        @solver_thread = Thread.new do
          with_timeout(false) { service_resolved_waiters }
          queue_runner.call
        end

        # we depend on at_exit hooks being fired, and Thread#abort_on_exception
        # doesn't fire them. This solution bubbles up the exceptions in a similar
        # fashion without actually sacrificing the at_exit functionality.
        Thread.new do
          begin
            @solver_thread.join
          rescue Exception => e
            $stderr.puts "Solver thread encountered an exception:"
            $stderr.puts "#{e.class.name}: #{e.message}"
            $stderr.puts e.backtrace.join("\n")
            Kernel.exit 1
          end
        end
      end
    end
def run
      # short circuit if we're not serial and already running
      return if @solver_thread and !@serial

      handler = lambda do |*args|
        p ["solved:", solved.to_a]
        p ["working:", vm_working.to_a]
        p ["waiting:", @waiters.to_a]
      end

      %w[USR2 INFO].each { |sig| trap(sig, &handler) if Signal.list[sig] }

      queue_runner = lambda do
        run = true

        while run
          service_resolved_waiters

          ready = []

          if @queue.empty?
            if @serial
              return
            else
              with_timeout do
                # this is where most of the execution time is spent, so ensure
                # waiters get considered here.
                service_resolved_waiters
                ready << @queue.shift
              end
            end
          end

          while !@queue.empty?
            ready << @queue.shift
          end

          ready.each do |r|
            if r
              @solved_mutex.synchronize do
                solved.add(r)
                @working.delete(r)
                vm_working.delete(r)
              end
            else
              run = false
            end
          end
        end
      end

      if @serial
        service_resolved_waiters
        queue_runner.call
      else
        @solver_thread = Thread.new do
          with_timeout(false) { service_resolved_waiters }
          queue_runner.call
        end

        # we depend on at_exit hooks being fired, and Thread#abort_on_exception
        # doesn't fire them. This solution bubbles up the exceptions in a similar
        # fashion without actually sacrificing the at_exit functionality.
        Thread.new do
          begin
            @solver_thread.join
          rescue Exception => e
            $stderr.puts "Solver thread encountered an exception:"
            $stderr.puts "#{e.class.name}: #{e.message}"
            $stderr.puts e.backtrace.join("\n")
            Kernel.exit 1
          end
        end
      end
    end
Start the scheduler. In serial mode this call will block until the whole
 dependency graph is satisfied, or one of the provisions fails, at which
 point an exception will be raised. In parallel mode, this call completes
 immediately, and you should use #wait_for to control main thread flow.

 This call also installs a SIGINFO (Ctrl+T in the terminal on macs) and
 SIGUSR2 handler which can be used to get information on the status of
 what's solved and what's working.

 Immediately returns if in threaded mode and the solver is already running.