@@ -9,6 +9,7 @@ class Rubygems < Source
99
1010 # Ask for X gems per API request
1111 API_REQUEST_SIZE = 100
12+ REQUIRE_MUTEX = Mutex . new
1213
1314 attr_accessor :remotes
1415
@@ -21,6 +22,8 @@ def initialize(options = {})
2122 @allow_local = options [ "allow_local" ] || false
2223 @prefer_local = false
2324 @checksum_store = Checksum ::Store . new
25+ @gem_installers = { }
26+ @gem_installers_mutex = Mutex . new
2427
2528 Array ( options [ "remotes" ] ) . reverse_each { |r | add_remote ( r ) }
2629
@@ -162,49 +165,40 @@ def specs
162165 end
163166 end
164167
165- def install ( spec , options = { } )
168+ def download ( spec , options = { } )
166169 if ( spec . default_gem? && !cached_built_in_gem ( spec , local : options [ :local ] ) ) || ( installed? ( spec ) && !options [ :force ] )
167- print_using_message "Using #{ version_message ( spec , options [ :previous_spec ] ) } "
168- return nil # no post-install message
170+ return true
169171 end
170172
171- path = fetch_gem_if_possible ( spec , options [ :previous_spec ] )
172- raise GemNotFound , "Could not find #{ spec . file_name } for installation" unless path
173-
174- return if Bundler . settings [ :no_install ]
175-
176- install_path = rubygems_dir
177- bin_path = Bundler . system_bindir
178-
179- require_relative "../rubygems_gem_installer"
180-
181- installer = Bundler ::RubyGemsGemInstaller . at (
182- path ,
183- security_policy : Bundler . rubygems . security_policies [ Bundler . settings [ "trust-policy" ] ] ,
184- install_dir : install_path . to_s ,
185- bin_dir : bin_path . to_s ,
186- ignore_dependencies : true ,
187- wrappers : true ,
188- env_shebang : true ,
189- build_args : options [ :build_args ] ,
190- bundler_extension_cache_path : extension_cache_path ( spec )
191- )
173+ installer = rubygems_gem_installer ( spec , options )
192174
193175 if spec . remote
194176 s = begin
195177 installer . spec
196178 rescue Gem ::Package ::FormatError
197- Bundler . rm_rf ( path )
179+ Bundler . rm_rf ( installer . gem )
198180 raise
199181 rescue Gem ::Security ::Exception => e
200182 raise SecurityError ,
201- "The gem #{ File . basename ( path , ". gem" ) } can't be installed because " \
183+ "The gem #{ installer . gem } can't be installed because " \
202184 "the security policy didn't allow it, with the message: #{ e . message } "
203185 end
204186
205187 spec . __swap__ ( s )
206188 end
207189
190+ spec
191+ end
192+
193+ def install ( spec , options = { } )
194+ if ( spec . default_gem? && !cached_built_in_gem ( spec , local : options [ :local ] ) ) || ( installed? ( spec ) && !options [ :force ] )
195+ print_using_message "Using #{ version_message ( spec , options [ :previous_spec ] ) } "
196+ return nil # no post-install message
197+ end
198+
199+ return if Bundler . settings [ :no_install ]
200+
201+ installer = rubygems_gem_installer ( spec , options )
208202 spec . source . checksum_store . register ( spec , installer . gem_checksum )
209203
210204 message = "Installing #{ version_message ( spec , options [ :previous_spec ] ) } "
@@ -516,6 +510,34 @@ def extension_cache_slug(spec)
516510 return unless remote = spec . remote
517511 remote . cache_slug
518512 end
513+
514+ # We are using a mutex to reaed and write from/to the hash.
515+ # The reason this double synchronization was added is for performance
516+ # and lock the mutex for the shortest possible amount of time. Otherwise,
517+ # all threads are fighting over this mutex and when it gets acquired it gets locked
518+ # until a threads finishes downloading a gem, leaving the other threads waiting
519+ # doing nothing.
520+ def rubygems_gem_installer ( spec , options )
521+ @gem_installers_mutex . synchronize { @gem_installers [ spec . name ] } || begin
522+ path = fetch_gem_if_possible ( spec , options [ :previous_spec ] )
523+ raise GemNotFound , "Could not find #{ spec . file_name } for installation" unless path
524+
525+ REQUIRE_MUTEX . synchronize { require_relative "../rubygems_gem_installer" }
526+
527+ installer = Bundler ::RubyGemsGemInstaller . at (
528+ path ,
529+ security_policy : Bundler . rubygems . security_policies [ Bundler . settings [ "trust-policy" ] ] ,
530+ install_dir : rubygems_dir . to_s ,
531+ bin_dir : Bundler . system_bindir . to_s ,
532+ ignore_dependencies : true ,
533+ wrappers : true ,
534+ env_shebang : true ,
535+ build_args : options [ :build_args ] ,
536+ bundler_extension_cache_path : extension_cache_path ( spec )
537+ )
538+ @gem_installers_mutex . synchronize { @gem_installers [ spec . name ] ||= installer }
539+ end
540+ end
519541 end
520542 end
521543end
0 commit comments