mirror of
https://github.com/ruby/ruby.git
synced 2025-08-24 13:34:17 +02:00

This changes the CompactIndexClient to store etags received from the compact index in separate files rather than relying on the MD5 checksum of the file as the etag. Smoothes the upgrade from md5 etags to opaque by generating them when no etag file exists. This should reduce the initial impact of changing the caching behavior by reducing cache misses when the MD5 etag is the same. Eventually, the MD5 behavior should be retired and the etag should be considered completely opaque with no assumption that MD5 would match.
126 lines
3.9 KiB
Ruby
126 lines
3.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "pathname"
|
|
require "set"
|
|
|
|
module Bundler
|
|
class CompactIndexClient
|
|
# NOTE: MD5 is here not because we expect a server to respond with it, but
|
|
# because we use it to generate the etag on first request during the upgrade
|
|
# to the compact index client that uses opaque etags saved to files.
|
|
# Remove once 2.5.0 has been out for a while.
|
|
SUPPORTED_DIGESTS = { "sha-256" => :SHA256, "md5" => :MD5 }.freeze
|
|
DEBUG_MUTEX = Thread::Mutex.new
|
|
|
|
def self.debug
|
|
return unless ENV["DEBUG_COMPACT_INDEX"]
|
|
DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
|
|
end
|
|
|
|
class Error < StandardError; end
|
|
|
|
require_relative "compact_index_client/cache"
|
|
require_relative "compact_index_client/cache_file"
|
|
require_relative "compact_index_client/updater"
|
|
|
|
attr_reader :directory
|
|
|
|
def initialize(directory, fetcher)
|
|
@directory = Pathname.new(directory)
|
|
@updater = Updater.new(fetcher)
|
|
@cache = Cache.new(@directory)
|
|
@endpoints = Set.new
|
|
@info_checksums_by_name = {}
|
|
@parsed_checksums = false
|
|
@mutex = Thread::Mutex.new
|
|
end
|
|
|
|
def execution_mode=(block)
|
|
Bundler::CompactIndexClient.debug { "execution_mode=" }
|
|
@endpoints = Set.new
|
|
|
|
@execution_mode = block
|
|
end
|
|
|
|
# @return [Lambda] A lambda that takes an array of inputs and a block, and
|
|
# maps the inputs with the block in parallel.
|
|
#
|
|
def execution_mode
|
|
@execution_mode || sequentially
|
|
end
|
|
|
|
def sequential_execution_mode!
|
|
self.execution_mode = sequentially
|
|
end
|
|
|
|
def sequentially
|
|
@sequentially ||= lambda do |inputs, &blk|
|
|
inputs.map(&blk)
|
|
end
|
|
end
|
|
|
|
def names
|
|
Bundler::CompactIndexClient.debug { "/names" }
|
|
update("names", @cache.names_path, @cache.names_etag_path)
|
|
@cache.names
|
|
end
|
|
|
|
def versions
|
|
Bundler::CompactIndexClient.debug { "/versions" }
|
|
update("versions", @cache.versions_path, @cache.versions_etag_path)
|
|
versions, @info_checksums_by_name = @cache.versions
|
|
versions
|
|
end
|
|
|
|
def dependencies(names)
|
|
Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
|
|
execution_mode.call(names) do |name|
|
|
update_info(name)
|
|
@cache.dependencies(name).map {|d| d.unshift(name) }
|
|
end.flatten(1)
|
|
end
|
|
|
|
def update_and_parse_checksums!
|
|
Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
|
|
return @info_checksums_by_name if @parsed_checksums
|
|
update("versions", @cache.versions_path, @cache.versions_etag_path)
|
|
@info_checksums_by_name = @cache.checksums
|
|
@parsed_checksums = true
|
|
end
|
|
|
|
private
|
|
|
|
def update(remote_path, local_path, local_etag_path)
|
|
Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
|
|
unless synchronize { @endpoints.add?(remote_path) }
|
|
Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
|
|
return
|
|
end
|
|
@updater.update(url(remote_path), local_path, local_etag_path)
|
|
end
|
|
|
|
def update_info(name)
|
|
Bundler::CompactIndexClient.debug { "update_info(#{name})" }
|
|
path = @cache.info_path(name)
|
|
unless existing = @info_checksums_by_name[name]
|
|
Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
|
|
return
|
|
end
|
|
checksum = SharedHelpers.checksum_for_file(path, :MD5)
|
|
if checksum == existing
|
|
Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
|
|
return
|
|
end
|
|
Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
|
|
update("info/#{name}", path, @cache.info_etag_path(name))
|
|
end
|
|
|
|
def url(path)
|
|
path
|
|
end
|
|
|
|
def synchronize
|
|
@mutex.synchronize { yield }
|
|
end
|
|
end
|
|
end
|