Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib/bundler/fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,15 @@ def fetch_spec(spec)
elsif cached_spec_path = gemspec_cached_path(spec_file_name)
Bundler.load_gemspec(cached_spec_path)
else
Bundler.safe_load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body)
# Lazily downloading a single gemspec is a network operation like any
# other, so it gets the same retry treatment as `specs_with_retry`.
# Without this a single transient failure aborts the whole resolution,
# which is most painful without a lockfile, where a resolve can fetch
# a great many gemspecs one at a time.
body = Bundler::Retry.new("fetcher", FAIL_ERRORS).attempts do
downloader.fetch(uri).body
end
Bundler.safe_load_marshal Bundler.rubygems.inflate(body)
end
raise MarshalError, "is #{spec.inspect}" unless spec.is_a?(Gem::Specification)
spec
Expand Down
27 changes: 27 additions & 0 deletions spec/bundler/fetcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,33 @@ def configured_connection
end
end

context "when the download fails transiently" do
let(:spec) { Gem::Specification.new(name, version) }
let(:downloaded_data) { Zlib::Deflate.deflate(Marshal.dump(spec)) }

it "retries the download and returns the spec" do
expect(Bundler::Fetcher::Downloader).to receive(:new).and_return(downloader)
expect(downloader).to receive(:fetch).twice do
@attempts = (@attempts || 0) + 1
raise Bundler::HTTPError, "transient network failure" if @attempts == 1
body
end

result = fetcher.fetch_spec([name, version, platform])
expect(result).to eq(spec)
end

it "does not retry an error that bypasses retries" do
expect(Bundler::Fetcher::Downloader).to receive(:new).and_return(downloader)
expect(downloader).to receive(:fetch).once.and_raise(
Bundler::Fetcher::AuthenticationRequiredError.new("http://example.org")
)

expect { fetcher.fetch_spec([name, version, platform]) }.
to raise_error(Bundler::Fetcher::AuthenticationRequiredError)
end
end

context "when attempting to load an unexpected class" do
let(:downloaded_data) { Zlib::Deflate.deflate(Marshal.dump(3)) }

Expand Down