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
5 changes: 5 additions & 0 deletions bundler/lib/bundler/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ def run(options)
Bundler.create_bundle_path

ProcessLock.lock do
# Invalidate any stale gem specification cache from before we acquired the lock.
# Another process may have installed gems while we were waiting.
Gem::Specification.reset
@definition.sources.clear_cache

@definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])

if @definition.dependencies.empty?
Expand Down
5 changes: 5 additions & 0 deletions bundler/lib/bundler/source/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ def dependency_api_available?
@allow_remote && api_fetchers.any?
end

def clear_cache
@installed_specs = nil
@default_specs = nil
end

protected

def remote_names
Expand Down
4 changes: 4 additions & 0 deletions bundler/lib/bundler/source_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ def remote!
all_sources.each(&:remote!)
end

def clear_cache
rubygems_sources.each(&:clear_cache)
end

private

def map_sources(replacement_sources)
Expand Down
30 changes: 30 additions & 0 deletions bundler/spec/bundler/source/rubygems_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,36 @@
end
end

describe "#clear_cache" do
it "clears the installed_specs cache" do
source = described_class.new

# Access installed_specs to populate the cache
source.send(:installed_specs)
expect(source.instance_variable_get(:@installed_specs)).not_to be_nil

# Expire the cache
source.clear_cache

# Cache should be cleared
expect(source.instance_variable_get(:@installed_specs)).to be_nil
end

it "clears the default_specs cache" do
source = described_class.new

# Access default_specs to populate the cache
source.send(:default_specs)
expect(source.instance_variable_get(:@default_specs)).not_to be_nil

# Expire the cache
source.clear_cache

# Cache should be cleared
expect(source.instance_variable_get(:@default_specs)).to be_nil
end
end

describe "log debug information" do
it "log the time spent downloading and installing a gem" do
build_repo2 do
Expand Down
10 changes: 10 additions & 0 deletions bundler/spec/bundler/source_list_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,16 @@
end
end

describe "#clear_cache" do
let(:rubygems_source) { source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) }

it "calls #clear_cache on all rubygems sources" do
expect(rubygems_source).to receive(:clear_cache)
expect(source_list.global_rubygems_source).to receive(:clear_cache)
source_list.clear_cache
end
end

describe "implicit_global_source?" do
context "when a global rubygem source provided" do
it "returns a falsy value" do
Expand Down
56 changes: 56 additions & 0 deletions bundler/spec/install/process_lock_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,61 @@
expect(processed).to eq true
end
end

it "refreshes gem specification cache after waiting for lock" do
build_repo2 do
build_gem "myrack", "1.0.0"
end

gemfile <<-G
source "https://gem.repo2"
gem "myrack"
G

# First, install the gem so it's available
bundle "install"
expect(out).to include("Installing myrack")

# Queue for thread-safe communication
lock_acquired = Queue.new
can_release_lock = Queue.new
install_output = Queue.new

# Thread holds lock (simulating another bundle process that just finished installing)
thread = Thread.new do
Bundler::ProcessLock.lock(default_bundle_path) do
# Signal that we have the lock
lock_acquired << true
# Wait until main thread signals we can release
can_release_lock.pop
end
end

# Wait for thread to acquire lock
lock_acquired.pop

# Start another install in a thread - it will wait for the lock
install_thread = Thread.new do
bundle "install", verbose: true
install_output << out
end

# Give subprocess time to start and begin waiting for lock
sleep 0.5

# Signal thread to release the lock
can_release_lock << true

# Wait for both threads to complete
thread.join
install_thread.join

second_install_out = install_output.pop

expect(the_bundle).to include_gems "myrack 1.0.0"
# The second install should have refreshed its cache after acquiring
# the lock and seen that myrack was already installed
expect(second_install_out).to include("Using myrack")
end
end
end