diff options
author | Patrick Metcalfe <git@patrickmetcalfe.com> | 2015-04-19 11:27:51 -0500 |
---|---|---|
committer | Patrick Metcalfe <git@patrickmetcalfe.com> | 2015-04-19 11:27:51 -0500 |
commit | 7d87d6af10f86aee3ab1630334a3fb2eacbd45ff (patch) | |
tree | 026a35e569665d08c074da9f4638d72703fb45c6 | |
parent | c7fce7422f467407f7544062661cbbb726b9ef38 (diff) | |
download | bundler-7d87d6af10f86aee3ab1630334a3fb2eacbd45ff.tar.gz |
refactor parallel installer
-rw-r--r-- | lib/bundler/installer.rb | 43 | ||||
-rw-r--r-- | lib/bundler/installer/parallel_installer.rb | 117 | ||||
-rw-r--r-- | spec/install/parallel/spec_installation_spec.rb | 37 |
3 files changed, 155 insertions, 42 deletions
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 7365b8f27e..a3a2eabdc3 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -277,48 +277,7 @@ module Bundler end def install_in_parallel(size, standalone, force = false) - name2spec = {} - remains = {} - enqueued = {} - specs.each do |spec| - name2spec[spec.name] = spec - remains[spec.name] = true - end - - worker_pool = Worker.new size, lambda { |name, worker_num| - spec = name2spec[name] - message = install_gem_from_spec spec, standalone, worker_num, force - { :name => spec.name, :post_install => message } - } - - # Keys in the remains hash represent uninstalled gems specs. - # We enqueue all gem specs that do not have any dependencies. - # Later we call this lambda again to install specs that depended on - # previously installed specifications. We continue until all specs - # are installed. - enqueue_remaining_specs = lambda do - remains.keys.each do |name| - next if enqueued[name] - spec = name2spec[name] - if ready_to_install?(spec, remains) - worker_pool.enq name - enqueued[name] = true - end - end - end - enqueue_remaining_specs.call - - until remains.empty? - message = worker_pool.deq - remains.delete message[:name] - if message[:post_install] - Installer.post_install_messages[message[:name]] = message[:post_install] - end - enqueue_remaining_specs.call - end - message - ensure - worker_pool && worker_pool.stop + ParallelInstaller.call(self, specs, size, standalone, force) end # We only want to install a gem spec if all its dependencies are met. diff --git a/lib/bundler/installer/parallel_installer.rb b/lib/bundler/installer/parallel_installer.rb new file mode 100644 index 0000000000..d4a9f7c253 --- /dev/null +++ b/lib/bundler/installer/parallel_installer.rb @@ -0,0 +1,117 @@ +require 'bundler/worker' + + +class ParallelInstaller + + class SpecInstallation + + attr_accessor :spec, :name, :post_install_message, :state + def initialize(spec) + @spec, @name = spec, spec.name + @state = :none + @post_install_message = "" + end + + def installed? + state == :installed + end + + def enqueued? + state == :enqueued + end + + # Only true when spec in neither installed nor already enqueued + def ready_to_enqueue? + !installed? && !enqueued? + end + + def has_post_install_message? + post_install_message.empty? + end + + def ignorable_dependency?(dep) + dep.type == :development || dep.name == @name + end + + # Checks installed dependencies against spec's dependencies to make + # sure needed dependencies have been installed. + def dependencies_installed?(remaining_specs) + installed_specs = remaining_specs.reject(&:installed?).map(&:name) + already_installed = lambda {|dep| installed_specs.include? dep.name } + dependencies.all? {|d| already_installed[d] } + end + + # Represents only the non-development dependencies and the ones that + # are itself. + def dependencies + @dependencies ||= all_dependencies.reject {|dep| ignorable_dependency? dep } + end + + # Represents all dependencies + def all_dependencies + @spec.dependencies + end + end + + def self.call(*args) + new(*args).call + end + + # Returns max number of threads machine can handle with a min of 1 + def self.max_threads + [Bundler.settings[:jobs].to_i-1, 1].max + end + + def initialize(installer, all_specs, size, standalone, force) + @installer = installer + @size = size + @standalone = standalone + @force = force + @specs = all_specs.map { |s| SpecInstallation.new(s) } + end + + def call + enqueue_specs + process_specs until @specs.all?(&:installed?) + ensure + worker_pool && worker_pool.stop + end + + def worker_pool + @worker_pool ||= Bundler::Worker.new @size, lambda { |spec_install, worker_num| + message = @installer.install_gem_from_spec spec_install.spec, @standalone, worker_num, @force + spec_install.post_install_message = message unless message.nil? + spec_install + } + end + + # Dequeue a spec and save its post-install message anf then enqueue the + # remaining specs. + # Some specs might've had to wait til this spec was installed to be + # processed so the call to `enqueue_specs` is important after every + # dequeue. + def process_specs + spec = worker_pool.deq + spec.state = :installed + collect_post_install_message spec if spec.has_post_install_message? + enqueue_specs + end + + def collect_post_install_message(spec) + Bundler::Installer.post_install_messages[spec.name] = spec.post_install_message + end + + # Keys in the remains hash represent uninstalled gems specs. + # We enqueue all gem specs that do not have any dependencies. + # Later we call this lambda again to install specs that depended on + # previously installed specifications. We continue until all specs + # are installed. + def enqueue_specs + @specs.select(&:ready_to_enqueue?).each do |spec| + if spec.dependencies_installed? @specs + worker_pool.enq spec + spec.state = :enqueued + end + end + end +end diff --git a/spec/install/parallel/spec_installation_spec.rb b/spec/install/parallel/spec_installation_spec.rb new file mode 100644 index 0000000000..97288fe02f --- /dev/null +++ b/spec/install/parallel/spec_installation_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require 'bundler/installer/parallel_installer' + +describe ParallelInstaller::SpecInstallation do + describe "#ready_to_enqueue?" do + + let!(:dep) do + a_spec = Object.new + def a_spec.name + "I like tests" + end + a_spec + end + + context "when in enqueued state" do + it "is falsey" do + spec = ParallelInstaller::SpecInstallation.new(dep) + spec.state = :enqueued + expect(spec.ready_to_enqueue?).to be_falsey + end + end + + context "when in installed state" do + it "returns falsey" do + spec = ParallelInstaller::SpecInstallation.new(dep) + spec.state = :installed + expect(spec.ready_to_enqueue?).to be_falsey + end + end + + it "returns truthy" do + spec = ParallelInstaller::SpecInstallation.new(dep) + expect(spec.ready_to_enqueue?).to be_truthy + end + end + +end |