diff options
author | Ahmad Sherif <ahmad.m.sherif@gmail.com> | 2017-10-18 19:37:28 +0300 |
---|---|---|
committer | Ahmad Sherif <ahmad.m.sherif@gmail.com> | 2017-10-18 19:37:28 +0300 |
commit | d75e345507fa2488b9ede0fe90a80a5223a95c61 (patch) | |
tree | ecff1a2cf06f4208c0ca513756a36bb067e24690 | |
parent | 02676c4ffc19853668ba3a8262068350ddae5d52 (diff) | |
parent | 1372c960e0b04a22de22abaf47d2f8ad69f3d9e7 (diff) |
Merge branch 'update-gitlab-git' into 'master'
Use gitlab_git c23c09366db610c1
See merge request gitlab-org/gitaly!415
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | ruby/lib/gitlab/git.rb | 3 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/REVISION | 2 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git.rb | 4 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/conflict/file.rb | 86 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/conflict/parser.rb | 91 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/conflict/resolver.rb | 91 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/env.rb | 17 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/operation_service.rb | 12 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/popen.rb | 63 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/repository.rb | 64 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/rev_list.rb | 2 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker.rb | 14 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker_settings.rb | 29 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/storage/health.rb | 20 | ||||
-rw-r--r-- | ruby/vendor/gitlab_git/lib/gitlab/git/storage/null_circuit_breaker.rb | 13 |
16 files changed, 479 insertions, 34 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f109eee54..91e12ad3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ UNRELEASED - Use sentry fingerprinting to group exceptions https://gitlab.com/gitlab-org/gitaly/merge_requests/417 +- Use gitlab_git c23c09366db610c1 + https://gitlab.com/gitlab-org/gitaly/merge_requests/415 v0.48.0 diff --git a/ruby/lib/gitlab/git.rb b/ruby/lib/gitlab/git.rb index 16e8af252..ba98f07d1 100644 --- a/ruby/lib/gitlab/git.rb +++ b/ruby/lib/gitlab/git.rb @@ -5,6 +5,7 @@ require 'linguist/blob_helper' # Ruby on Rails mix-ins that GitLab::Git code relies on require 'active_support/core_ext/object/blank' require 'active_support/core_ext/numeric/bytes' +require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/enumerable' @@ -25,6 +26,8 @@ require_relative File.join(vendor_gitlab_git, 'lib/gitlab/git/ref.rb') # Require all .rb files we can find in the vendored gitlab/git directory dir = File.expand_path(File.join('..', vendor_gitlab_git, 'lib/gitlab/'), __FILE__) Dir["#{dir}/git/**/*.rb"].sort.each do |ruby_file| + next if ruby_file.include?('circuit_breaker') + require_relative ruby_file.sub(dir, File.join(vendor_gitlab_git, 'lib/gitlab/')).sub(%r{^/*}, '') end diff --git a/ruby/vendor/gitlab_git/REVISION b/ruby/vendor/gitlab_git/REVISION index 7c3aa3cb2..acf51f1fc 100644 --- a/ruby/vendor/gitlab_git/REVISION +++ b/ruby/vendor/gitlab_git/REVISION @@ -1 +1 @@ -4a0f720a502ac02423cb9db20727ba386de3e1f1 +c23c09366db610c1994fa592961ae838001ed5b0 diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git.rb b/ruby/vendor/gitlab_git/lib/gitlab/git.rb index c78fe63f9..1f31cdbc9 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git.rb @@ -66,6 +66,10 @@ module Gitlab end end end + + def diff_line_code(file_path, new_line_position, old_line_position) + "#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}" + end end end end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/file.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/file.rb new file mode 100644 index 000000000..fc1595f1f --- /dev/null +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/file.rb @@ -0,0 +1,86 @@ +module Gitlab + module Git + module Conflict + class File + attr_reader :content, :their_path, :our_path, :our_mode, :repository + + def initialize(repository, commit_oid, conflict, content) + @repository = repository + @commit_oid = commit_oid + @their_path = conflict[:theirs][:path] + @our_path = conflict[:ours][:path] + @our_mode = conflict[:ours][:mode] + @content = content + end + + def lines + return @lines if defined?(@lines) + + begin + @type = 'text' + @lines = Gitlab::Git::Conflict::Parser.parse(content, + our_path: our_path, + their_path: their_path) + rescue Gitlab::Git::Conflict::Parser::ParserError + @type = 'text-editor' + @lines = nil + end + end + + def type + lines unless @type + + @type.inquiry + end + + def our_blob + # REFACTOR NOTE: the source of `commit_oid` used to be + # `merge_request.diff_refs.head_sha`. Instead of passing this value + # around the new lib structure, I decided to use `@commit_oid` which is + # equivalent to `merge_request.source_branch_head.raw.rugged_commit.oid`. + # That is what `merge_request.diff_refs.head_sha` is equivalent to when + # `merge_request` is not persisted (see `MergeRequest#diff_head_commit`). + # I think using the same oid is more consistent anyways, but if Conflicts + # start breaking, the change described above is a good place to look at. + @our_blob ||= repository.blob_at(@commit_oid, our_path) + end + + def line_code(line) + Gitlab::Git.diff_line_code(our_path, line[:line_new], line[:line_old]) + end + + def resolve_lines(resolution) + section_id = nil + + lines.map do |line| + unless line[:type] + section_id = nil + next line + end + + section_id ||= line_code(line) + + case resolution[section_id] + when 'head' + next unless line[:type] == 'new' + when 'origin' + next unless line[:type] == 'old' + else + raise Gitlab::Git::Conflict::Resolver::ResolutionError, "Missing resolution for section ID: #{section_id}" + end + + line + end.compact + end + + def resolve_content(resolution) + if resolution == content + raise Gitlab::Git::Conflict::Resolver::ResolutionError, "Resolved content has no changes for file #{our_path}" + end + + resolution + end + end + end + end +end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/parser.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/parser.rb new file mode 100644 index 000000000..3effa9d2d --- /dev/null +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/parser.rb @@ -0,0 +1,91 @@ +module Gitlab + module Git + module Conflict + class Parser + UnresolvableError = Class.new(StandardError) + UnmergeableFile = Class.new(UnresolvableError) + UnsupportedEncoding = Class.new(UnresolvableError) + + # Recoverable errors - the conflict can be resolved in an editor, but not with + # sections. + ParserError = Class.new(StandardError) + UnexpectedDelimiter = Class.new(ParserError) + MissingEndDelimiter = Class.new(ParserError) + + class << self + def parse(text, our_path:, their_path:, parent_file: nil) + validate_text!(text) + + line_obj_index = 0 + line_old = 1 + line_new = 1 + type = nil + lines = [] + conflict_start = "<<<<<<< #{our_path}" + conflict_middle = '=======' + conflict_end = ">>>>>>> #{their_path}" + + text.each_line.map do |line| + full_line = line.delete("\n") + + if full_line == conflict_start + validate_delimiter!(type.nil?) + + type = 'new' + elsif full_line == conflict_middle + validate_delimiter!(type == 'new') + + type = 'old' + elsif full_line == conflict_end + validate_delimiter!(type == 'old') + + type = nil + elsif line[0] == '\\' + type = 'nonewline' + lines << { + full_line: full_line, + type: type, + line_obj_index: line_obj_index, + line_old: line_old, + line_new: line_new + } + else + lines << { + full_line: full_line, + type: type, + line_obj_index: line_obj_index, + line_old: line_old, + line_new: line_new + } + + line_old += 1 if type != 'new' + line_new += 1 if type != 'old' + + line_obj_index += 1 + end + end + + raise MissingEndDelimiter unless type.nil? + + lines + end + + private + + def validate_text!(text) + raise UnmergeableFile if text.blank? # Typically a binary file + raise UnmergeableFile if text.length > 200.kilobytes + + text.force_encoding('UTF-8') + + raise UnsupportedEncoding unless text.valid_encoding? + end + + def validate_delimiter!(condition) + raise UnexpectedDelimiter unless condition + end + end + end + end + end +end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/resolver.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/resolver.rb new file mode 100644 index 000000000..df509c5f4 --- /dev/null +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/conflict/resolver.rb @@ -0,0 +1,91 @@ +module Gitlab + module Git + module Conflict + class Resolver + ConflictSideMissing = Class.new(StandardError) + ResolutionError = Class.new(StandardError) + + def initialize(repository, our_commit, target_repository, their_commit) + @repository = repository + @our_commit = our_commit.rugged_commit + @target_repository = target_repository + @their_commit = their_commit.rugged_commit + end + + def conflicts + @conflicts ||= begin + target_index = @target_repository.rugged.merge_commits(@our_commit, @their_commit) + + # We don't need to do `with_repo_branch_commit` here, because the target + # project always fetches source refs when creating merge request diffs. + target_index.conflicts.map do |conflict| + raise ConflictSideMissing unless conflict[:theirs] && conflict[:ours] + + Gitlab::Git::Conflict::File.new( + @target_repository, + @our_commit.oid, + conflict, + target_index.merge_file(conflict[:ours][:path])[:data] + ) + end + end + end + + def resolve_conflicts(user, files, source_branch:, target_branch:, commit_message:) + @repository.with_repo_branch_commit(@target_repository, target_branch) do + files.each do |file_params| + conflict_file = conflict_for_path(file_params[:old_path], file_params[:new_path]) + + write_resolved_file_to_index(conflict_file, file_params) + end + + unless index.conflicts.empty? + missing_files = index.conflicts.map { |file| file[:ours][:path] } + + raise ResolutionError, "Missing resolutions for the following files: #{missing_files.join(', ')}" + end + + commit_params = { + message: commit_message, + parents: [@our_commit, @their_commit].map(&:oid) + } + + @repository.commit_index(user, source_branch, index, commit_params) + end + end + + def conflict_for_path(old_path, new_path) + conflicts.find do |conflict| + conflict.their_path == old_path && conflict.our_path == new_path + end + end + + private + + # We can only write when getting the merge index from the source + # project, because we will write to that project. We don't use this all + # the time because this fetches a ref into the source project, which + # isn't needed for reading. + def index + @index ||= @repository.rugged.merge_commits(@our_commit, @their_commit) + end + + def write_resolved_file_to_index(file, params) + if params[:sections] + resolved_lines = file.resolve_lines(params[:sections]) + new_file = resolved_lines.map { |line| line[:full_line] }.join("\n") + + new_file << "\n" if file.our_blob.data.ends_with?("\n") + elsif params[:content] + new_file = file.resolve_content(params[:content]) + end + + our_path = file.our_path + + index.add(path: our_path, oid: @repository.rugged.write(new_file, :blob), mode: file.our_mode) + index.conflict_remove(our_path) + end + end + end + end +end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/env.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/env.rb index f80193ac5..9d0b47a1a 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/env.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/env.rb @@ -11,9 +11,11 @@ module Gitlab # # This class is thread-safe via RequestStore. class Env - WHITELISTED_GIT_VARIABLES = %w[ + WHITELISTED_VARIABLES = %w[ GIT_OBJECT_DIRECTORY + GIT_OBJECT_DIRECTORY_RELATIVE GIT_ALTERNATE_OBJECT_DIRECTORIES + GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE ].freeze def self.set(env) @@ -28,12 +30,23 @@ module Gitlab RequestStore.fetch(:gitlab_git_env) { {} } end + def self.to_env_hash + env = {} + + all.compact.each do |key, value| + value = value.join(File::PATH_SEPARATOR) if value.is_a?(Array) + env[key.to_s] = value + end + + env + end + def self.[](key) all[key] end def self.whitelist_git_env(env) - env.select { |key, _| WHITELISTED_GIT_VARIABLES.include?(key.to_s) }.with_indifferent_access + env.select { |key, _| WHITELISTED_VARIABLES.include?(key.to_s) }.with_indifferent_access end end end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/operation_service.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/operation_service.rb index d835dcca8..ab94ba8a7 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/operation_service.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/operation_service.rb @@ -3,9 +3,17 @@ module Gitlab class OperationService include Gitlab::Git::Popen - WithBranchResult = Struct.new(:newrev, :repo_created, :branch_created) do + BranchUpdate = Struct.new(:newrev, :repo_created, :branch_created) do alias_method :repo_created?, :repo_created alias_method :branch_created?, :branch_created + + def self.from_gitaly(branch_update) + new( + branch_update.commit_id, + branch_update.repo_created, + branch_update.branch_created + ) + end end attr_reader :user, :repository @@ -112,7 +120,7 @@ module Gitlab ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name update_ref_in_hooks(ref, newrev, oldrev) - WithBranchResult.new(newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev)) + BranchUpdate.new(newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev)) end def find_oldrev_from_branch(newrev, branch) diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/popen.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/popen.rb index 3d2fc471d..b45da6020 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/popen.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/popen.rb @@ -5,6 +5,8 @@ require 'open3' module Gitlab module Git module Popen + FAST_GIT_PROCESS_TIMEOUT = 15.seconds + def popen(cmd, path, vars = {}) unless cmd.is_a?(Array) raise "System commands must be given as an array of strings" @@ -27,6 +29,67 @@ module Gitlab [@cmd_output, @cmd_status] end + + def popen_with_timeout(cmd, timeout, path, vars = {}) + unless cmd.is_a?(Array) + raise "System commands must be given as an array of strings" + end + + path ||= Dir.pwd + vars['PWD'] = path + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + rout, wout = IO.pipe + rerr, werr = IO.pipe + + pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true) + + begin + status = process_wait_with_timeout(pid, timeout) + + # close write ends so we could read them + wout.close + werr.close + + cmd_output = rout.readlines.join + cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output + + [cmd_output, status.exitstatus] + rescue Timeout::Error => e + kill_process_group_for_pid(pid) + + raise e + ensure + wout.close unless wout.closed? + werr.close unless werr.closed? + + rout.close + rerr.close + end + end + + def process_wait_with_timeout(pid, timeout) + deadline = timeout.seconds.from_now + wait_time = 0.01 + + while deadline > Time.now + sleep(wait_time) + _, status = Process.wait2(pid, Process::WNOHANG) + + return status unless status.nil? + end + + raise Timeout::Error, "Timeout waiting for process ##{pid}" + end + + def kill_process_group_for_pid(pid) + Process.kill("KILL", -pid) + Process.wait(pid) + rescue Errno::ESRCH + end end end end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/repository.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/repository.rb index 89b654253..59a54b48e 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/repository.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/repository.rb @@ -12,6 +12,10 @@ module Gitlab GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES ].freeze + ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES = %w[ + GIT_OBJECT_DIRECTORY_RELATIVE + GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE + ].freeze SEARCH_CONTEXT_LINES = 3 NoRepository = Class.new(StandardError) @@ -193,7 +197,7 @@ module Gitlab def has_local_branches? gitaly_migrate(:has_local_branches) do |is_enabled| if is_enabled - gitaly_ref_client.has_local_branches? + gitaly_repository_client.has_local_branches? else has_local_branches_rugged? end @@ -700,7 +704,17 @@ module Gitlab tags.find { |tag| tag.name == name } end - def merge(user, source_sha, target_branch, message) + def merge(user, source_sha, target_branch, message, &block) + gitaly_migrate(:operation_user_merge_branch) do |is_enabled| + if is_enabled + gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block) + else + rugged_merge(user, source_sha, target_branch, message, &block) + end + end + end + + def rugged_merge(user, source_sha, target_branch, message) committer = Gitlab::Git.committer_hash(email: user.email, name: user.name) OperationService.new(user, self).with_branch(target_branch) do |start_commit| @@ -990,7 +1004,7 @@ module Gitlab tmp_ref = fetch_ref( start_repository, source_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", - target_ref: "refs/tmp/#{SecureRandom.hex}/head" + target_ref: "refs/tmp/#{SecureRandom.hex}" ) yield commit(sha) @@ -1058,6 +1072,13 @@ module Gitlab end # Refactoring aid; allows us to copy code from app/models/repository.rb + def run_git_with_timeout(args, timeout, env: {}) + circuit_breaker.perform do + popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env) + end + end + + # Refactoring aid; allows us to copy code from app/models/repository.rb def commit(ref = 'HEAD') Gitlab::Git::Commit.find(self, ref) end @@ -1082,6 +1103,30 @@ module Gitlab @has_visible_content = has_local_branches? end + def fetch(remote = 'origin') + args = %W(#{Gitlab.config.git.bin_path} fetch #{remote}) + + popen(args, @path).last.zero? + end + + def blob_at(sha, path) + Gitlab::Git::Blob.find(self, sha, path) unless Gitlab::Git.blank_ref?(sha) + end + + def commit_index(user, branch_name, index, options) + committer = user_to_committer(user) + + OperationService.new(user, self).with_branch(branch_name) do + commit_params = options.merge( + tree: index.write_tree(rugged), + author: committer, + committer: committer + ) + + create_commit(commit_params) + end + end + def gitaly_repository Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository) end @@ -1112,6 +1157,8 @@ module Gitlab raise NoRepository.new(e) rescue GRPC::BadStatus => e raise CommandError.new(e) + rescue GRPC::InvalidArgument => e + raise ArgumentError.new(e) end private @@ -1218,7 +1265,16 @@ module Gitlab end def alternate_object_directories - Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).compact + relative_paths = Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact + + if relative_paths.any? + relative_paths.map { |d| File.join(path, d) } + else + Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES) + .flatten + .compact + .flat_map { |d| d.split(File::PATH_SEPARATOR) } + end end # Get the content of a blob for a given commit. If the blob is a commit diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/rev_list.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/rev_list.rb index 92a6a6725..60b2a4ec4 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/rev_list.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/rev_list.rb @@ -28,7 +28,7 @@ module Gitlab private def execute(args) - output, status = popen(args, nil, Gitlab::Git::Env.all.stringify_keys) + output, status = popen(args, nil, Gitlab::Git::Env.to_env_hash) unless status.zero? raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}" diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker.rb index 1eaa2d83f..0456ad9a1 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker.rb @@ -2,15 +2,13 @@ module Gitlab module Git module Storage class CircuitBreaker + include CircuitBreakerSettings + FailureInfo = Struct.new(:last_failure, :failure_count) attr_reader :storage, :hostname, - :storage_path, - :failure_count_threshold, - :failure_wait_time, - :failure_reset_time, - :storage_timeout + :storage_path delegate :last_failure, :failure_count, to: :failure_info @@ -18,7 +16,7 @@ module Gitlab pattern = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}*" Gitlab::Git::Storage.redis.with do |redis| - all_storage_keys = redis.keys(pattern) + all_storage_keys = redis.scan_each(match: pattern).to_a redis.del(*all_storage_keys) unless all_storage_keys.empty? end @@ -53,10 +51,6 @@ module Gitlab config = Gitlab.config.repositories.storages[@storage] @storage_path = config['path'] - @failure_count_threshold = config['failure_count_threshold'] - @failure_wait_time = config['failure_wait_time'] - @failure_reset_time = config['failure_reset_time'] - @storage_timeout = config['storage_timeout'] end def perform diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker_settings.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker_settings.rb new file mode 100644 index 000000000..d2313fe7c --- /dev/null +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/circuit_breaker_settings.rb @@ -0,0 +1,29 @@ +module Gitlab + module Git + module Storage + module CircuitBreakerSettings + def failure_count_threshold + application_settings.circuitbreaker_failure_count_threshold + end + + def failure_wait_time + application_settings.circuitbreaker_failure_wait_time + end + + def failure_reset_time + application_settings.circuitbreaker_failure_reset_time + end + + def storage_timeout + application_settings.circuitbreaker_storage_timeout + end + + private + + def application_settings + Gitlab::CurrentSettings.current_application_settings + end + end + end + end +end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/storage/health.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/health.rb index 1564e94b7..7049772fe 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/storage/health.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/health.rb @@ -23,26 +23,36 @@ module Gitlab end end - def self.all_keys_for_storages(storage_names, redis) + private_class_method def self.all_keys_for_storages(storage_names, redis) keys_per_storage = {} redis.pipelined do storage_names.each do |storage_name| pattern = pattern_for_storage(storage_name) + matched_keys = redis.scan_each(match: pattern) - keys_per_storage[storage_name] = redis.keys(pattern) + keys_per_storage[storage_name] = matched_keys end end - keys_per_storage + # We need to make sure each lazy-loaded `Enumerator` for matched keys + # is loaded into an array. + # + # Otherwise it would be loaded in the second `Redis#pipelined` block + # within `.load_for_keys`. In this pipelined call, the active + # Redis-client changes again, so the values would not be available + # until the end of that pipelined-block. + keys_per_storage.each do |storage_name, key_future| + keys_per_storage[storage_name] = key_future.to_a + end end - def self.load_for_keys(keys_per_storage, redis) + private_class_method def self.load_for_keys(keys_per_storage, redis) info_for_keys = {} redis.pipelined do keys_per_storage.each do |storage_name, keys_future| - info_for_storage = keys_future.value.map do |key| + info_for_storage = keys_future.map do |key| { name: key, failure_count: redis.hget(key, :failure_count) } end diff --git a/ruby/vendor/gitlab_git/lib/gitlab/git/storage/null_circuit_breaker.rb b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/null_circuit_breaker.rb index 297c043d0..60c6791a7 100644 --- a/ruby/vendor/gitlab_git/lib/gitlab/git/storage/null_circuit_breaker.rb +++ b/ruby/vendor/gitlab_git/lib/gitlab/git/storage/null_circuit_breaker.rb @@ -2,15 +2,14 @@ module Gitlab module Git module Storage class NullCircuitBreaker + include CircuitBreakerSettings + # These will have actual values attr_reader :storage, :hostname # These will always have nil values - attr_reader :storage_path, - :failure_wait_time, - :failure_reset_time, - :storage_timeout + attr_reader :storage_path def initialize(storage, hostname, error: nil) @storage = storage @@ -26,16 +25,12 @@ module Gitlab !!@error end - def failure_count_threshold - 1 - end - def last_failure circuit_broken? ? Time.now : nil end def failure_count - circuit_broken? ? 1 : 0 + circuit_broken? ? failure_count_threshold : 0 end def failure_info |