Skip to content

Commit 14daf5c

Browse files
committed
Separate Bundler I/O concurrency
1 parent 2361605 commit 14daf5c

18 files changed

Lines changed: 159 additions & 52 deletions

lib/bundler/fetcher/compact_index.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def in_parallel(inputs, &blk)
113113
def bundle_worker(func = nil)
114114
@bundle_worker ||= begin
115115
worker_name = "Compact Index (#{display_uri.host})"
116-
Bundler::Worker.new(Bundler.settings.processor_count, worker_name, func)
116+
Bundler::Worker.new(Bundler.settings.metadata_parallelization, worker_name, func)
117117
end
118118
@bundle_worker.tap do |worker|
119119
worker.instance_variable_set(:@func, func) if func

lib/bundler/fetcher/gem_remote_fetcher.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class GemRemoteFetcher < Gem::RemoteFetcher
88
def initialize(*)
99
super
1010

11-
@pool_size = Bundler.settings.installation_parallelization
11+
@pool_size = Bundler.settings.download_parallelization
1212
ssl_ca_cert = Bundler.settings[:ssl_ca_cert]
1313
@cert_files << ssl_ca_cert if ssl_ca_cert
1414
end

lib/bundler/installer/parallel_installer.rb

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ def self.call(*args, **kwargs)
6464

6565
attr_reader :size
6666

67-
def initialize(installer, all_specs, size, standalone, force, local: false, skip: nil)
67+
def initialize(installer, all_specs, size, standalone, force, local: false, skip: nil, download_size: Bundler.settings.download_parallelization)
6868
@installer = installer
6969
@size = size
70+
@download_size = download_size
7071
@standalone = standalone
7172
@force = force
7273
@local = local
@@ -91,7 +92,7 @@ def call
9192
Gem::Specification.reset
9293
end
9394

94-
if @size > 1
95+
if @size > 1 || @download_size > 1
9596
install_with_worker
9697
else
9798
install_serially
@@ -101,6 +102,7 @@ def call
101102
@specs
102103
ensure
103104
worker_pool&.stop
105+
download_worker_pool&.stop
104106
end
105107

106108
private
@@ -171,16 +173,17 @@ def install_serially
171173
end
172174

173175
def worker_pool
174-
@worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda {|spec_install, worker_num|
175-
case spec_install.state
176-
when :enqueued
177-
do_download(spec_install, worker_num)
178-
when :installable
179-
do_install(spec_install, worker_num)
180-
else
181-
spec_install
182-
end
183-
}
176+
@worker_pool ||= Bundler::Worker.new(@size, "Parallel Installer",
177+
->(spec_install, worker_num) { do_install(spec_install, worker_num) }, response_queue: response_queue)
178+
end
179+
180+
def download_worker_pool
181+
@download_worker_pool ||= Bundler::Worker.new(@download_size, "Gem Downloader",
182+
->(spec_install, worker_num) { do_download(spec_install, worker_num) }, response_queue: response_queue)
183+
end
184+
185+
def response_queue
186+
@response_queue ||= Thread::Queue.new
184187
end
185188

186189
def do_download(spec_install, worker_num)
@@ -218,24 +221,24 @@ def do_install(spec_install, worker_num)
218221
spec_install
219222
end
220223

221-
# Dequeue a spec and save its post-install message and then enqueue the
222-
# remaining specs.
223-
# Some specs might've had to wait til this spec was installed to be
224-
# processed so the call to `enqueue_specs` is important after every
225-
# dequeue.
224+
# Process one completed download or installation. Downloads can finish
225+
# before their dependencies are installed, so check all downloaded specs
226+
# after each completion and enqueue any that are now installable.
226227
def process_specs(installed_specs)
227228
spec = worker_pool.deq
228229

229230
if spec.installed?
230231
installed_specs[spec.name] = true
231-
return
232232
elsif spec.failed?
233233
return
234-
elsif spec.ready_to_install?(installed_specs)
235-
spec.state = :installable
236234
end
237235

238-
worker_pool.enq(spec, priority: spec.enqueue_with_priority?)
236+
@specs.each do |candidate|
237+
next unless candidate.ready_to_install?(installed_specs)
238+
239+
candidate.state = :installable
240+
worker_pool.enq(candidate, priority: candidate.enqueue_with_priority?)
241+
end
239242
end
240243

241244
def finished_installing?
@@ -274,11 +277,8 @@ def require_tree_for_spec(spec)
274277
t
275278
end
276279

277-
# Keys in the remains hash represent uninstalled gems specs.
278-
# We enqueue all gem specs that do not have any dependencies.
279-
# Later we call this lambda again to install specs that depended on
280-
# previously installed specifications. We continue until all specs
281-
# are installed.
280+
# Queue every missing spec for download. `process_specs` schedules each
281+
# downloaded spec for installation once its dependencies are installed.
282282
def enqueue_specs(installed_specs)
283283
@specs.each do |spec|
284284
if spec.installed?
@@ -287,7 +287,7 @@ def enqueue_specs(installed_specs)
287287
end
288288

289289
spec.state = :enqueued
290-
worker_pool.enq spec
290+
download_worker_pool.enq spec
291291
end
292292
end
293293
end

lib/bundler/man/bundle-config.1

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ Cooldown filtering depends on the gem server providing a per\-version \fBcreated
117117
.IP "\(bu" 4
118118
\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
119119
.IP "\(bu" 4
120+
\fBdownload_jobs\fR (\fBBUNDLE_DOWNLOAD_JOBS\fR): The number of gems Bundler can download in parallel\. Defaults to three times the number of installation jobs, capped at eight\.
121+
.IP "\(bu" 4
120122
\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
121123
.IP "\(bu" 4
122124
\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\.
@@ -135,12 +137,14 @@ Cooldown filtering depends on the gem server providing a per\-version \fBcreated
135137
.IP "\(bu" 4
136138
\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
137139
.IP "\(bu" 4
138-
\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can download and install in parallel\. Defaults to the number of available processors\.
140+
\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of parallel installation jobs\. Defaults to the number of available processors\.
139141
.IP "\(bu" 4
140142
\fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR): The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\.
141143
.IP "\(bu" 4
142144
\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR): Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\. Bundler's own checksum is only included when its \fB\.gem\fR file is cached, which may not be the case when Bundler is installed as a default gem\.
143145
.IP "\(bu" 4
146+
\fBmetadata_jobs\fR (\fBBUNDLE_METADATA_JOBS\fR): The number of compact index metadata requests Bundler can make in parallel\. Defaults to the number of download jobs\.
147+
.IP "\(bu" 4
144148
\fBno_build_extension\fR (\fBBUNDLE_NO_BUILD_EXTENSION\fR): Whether Bundler should skip building native extensions during installation\. When set, gems are installed without compiling their C extensions\. To build extensions later, unset this setting and run \fBbundle pristine <gem>\fR\.
145149
.IP "\(bu" 4
146150
\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.

lib/bundler/man/bundle-config.1.ronn

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
190190
* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):
191191
Stop Bundler from checking if a newer Bundler version is available on
192192
rubygems.org.
193+
* `download_jobs` (`BUNDLE_DOWNLOAD_JOBS`):
194+
The number of gems Bundler can download in parallel. Defaults to three times
195+
the number of installation jobs, capped at eight.
193196
* `force_ruby_platform` (`BUNDLE_FORCE_RUBY_PLATFORM`):
194197
Ignore the current machine's platform and install only `ruby` platform gems.
195198
As a result, gems with native extensions will be compiled from source.
@@ -222,7 +225,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
222225
* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`):
223226
Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`.
224227
* `jobs` (`BUNDLE_JOBS`):
225-
The number of gems Bundler can download and install in parallel.
228+
The number of parallel installation jobs.
226229
Defaults to the number of available processors.
227230
* `lockfile` (`BUNDLE_LOCKFILE`):
228231
The path to the lockfile that bundler should use. By default, Bundler adds
@@ -232,6 +235,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
232235
Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true.
233236
Bundler's own checksum is only included when its `.gem` file is cached, which
234237
may not be the case when Bundler is installed as a default gem.
238+
* `metadata_jobs` (`BUNDLE_METADATA_JOBS`):
239+
The number of compact index metadata requests Bundler can make in parallel.
240+
Defaults to the number of download jobs.
235241
* `no_build_extension` (`BUNDLE_NO_BUILD_EXTENSION`):
236242
Whether Bundler should skip building native extensions during installation.
237243
When set, gems are installed without compiling their C extensions.

lib/bundler/man/bundle-install.1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Bundler will not call Rubygems' API endpoint (default) but download and cache a
2626
The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
2727
.TP
2828
\fB\-\-jobs=<number>\fR, \fB\-j=<number>\fR
29-
The maximum number of parallel download and install jobs\. The default is the number of available processors\.
29+
The maximum number of parallel installation jobs\. The default is the number of available processors\.
3030
.TP
3131
\fB\-\-local\fR
3232
Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems' cache or in \fBvendor/cache\fR\. Note that if an appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\.

lib/bundler/man/bundle-install.1.ronn

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ update process below under [CONSERVATIVE UPDATING][].
6161
to this location.
6262

6363
* `--jobs=<number>`, `-j=<number>`:
64-
The maximum number of parallel download and install jobs. The default is the
65-
number of available processors.
64+
The maximum number of parallel installation jobs. The default is the number
65+
of available processors.
6666

6767
* `--local`:
6868
Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the

lib/bundler/man/bundle-update.1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Fall back to using the single\-file index of all gems\.
3939
Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\.
4040
.TP
4141
\fB\-\-jobs=<number>\fR, \fB\-j=<number>\fR
42-
Specify the number of jobs to run in parallel\. The default is the number of available processors\.
42+
Specify the number of installation jobs to run in parallel\. The default is the number of available processors\.
4343
.TP
4444
\fB\-\-retry=[<number>]\fR
4545
Retry failed network or git requests for \fInumber\fR times\.

lib/bundler/man/bundle-update.1.ronn

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ gem.
6565
Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)].
6666

6767
* `--jobs=<number>`, `-j=<number>`:
68-
Specify the number of jobs to run in parallel. The default is the number of
69-
available processors.
68+
Specify the number of installation jobs to run in parallel. The default is
69+
the number of available processors.
7070

7171
* `--retry=[<number>]`:
7272
Retry failed network or git requests for <number> times.

lib/bundler/settings.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ class Settings
4545

4646
NUMBER_KEYS = %w[
4747
cooldown
48+
download_jobs
4849
jobs
50+
metadata_jobs
4951
redirect
5052
retry
5153
ssl_verify_mode
@@ -307,10 +309,18 @@ def app_cache_path
307309
@app_cache_path ||= self[:cache_path] || "vendor/cache"
308310
end
309311

312+
def download_parallelization
313+
self[:download_jobs] || [installation_parallelization * 3, 8].min
314+
end
315+
310316
def installation_parallelization
311317
self[:jobs] || processor_count
312318
end
313319

320+
def metadata_parallelization
321+
self[:metadata_jobs] || download_parallelization
322+
end
323+
314324
def validate!
315325
all.each do |raw_key|
316326
[@local_config, @env_config, @global_config].each do |settings|

0 commit comments

Comments
 (0)