Don't reuse gRPC channels

It seems that bad things happen when two gRPC stubs share one gRPC
channel so let's stop doing that. The downside of this is that we
create more gRPC connections; one per stub.
This commit is contained in:
Jacob Vosmaer 2017-05-10 14:18:59 +02:00
parent e261b4b851
commit 43f037c903
12 changed files with 58 additions and 66 deletions

View file

@ -1163,8 +1163,6 @@ class Repository
@project.repository_storage_path
end
delegate :gitaly_channel, :gitaly_repository, to: :raw_repository
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, path_with_namespace + '.git')
end

View file

@ -1,6 +1,8 @@
require 'uri'
# Make sure we initialize our Gitaly channels before Sidekiq starts multi-threaded execution.
if Gitlab.config.gitaly.enabled || Rails.env.test?
Gitlab::GitalyClient.configure_channels
Gitlab.config.repositories.storages.keys.each do |storage|
# Force validation of each address
Gitlab::GitalyClient.address(storage)
end
end

View file

@ -27,13 +27,15 @@ module Gitlab
# Rugged repo object
attr_reader :rugged
attr_reader :storage
# 'path' must be the path to a _bare_ git repository, e.g.
# /path/to/my-repo.git
def initialize(repository_storage, relative_path)
@repository_storage = repository_storage
def initialize(storage, relative_path)
@storage = storage
@relative_path = relative_path
storage_path = Gitlab.config.repositories.storages[@repository_storage]['path']
storage_path = Gitlab.config.repositories.storages[@storage]['path']
@path = File.join(storage_path, @relative_path)
@name = @relative_path.split("/").last
@attributes = Gitlab::Git::Attributes.new(path)
@ -965,11 +967,7 @@ module Gitlab
end
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@repository_storage, @relative_path)
end
def gitaly_channel
Gitlab::GitalyClient.get_channel(@repository_storage)
Gitlab::GitalyClient::Util.repository(@storage, @relative_path)
end
private

View file

@ -4,56 +4,42 @@ module Gitlab
module GitalyClient
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'.freeze
# This function is not thread-safe because it updates Hashes in instance variables.
def self.configure_channels
@addresses = {}
@channels = {}
Gitlab.config.repositories.storages.each do |name, params|
address = params['gitaly_address']
unless address.present?
raise "storage #{name.inspect} is missing a gitaly_address"
MUTEX = Mutex.new
private_constant :MUTEX
def self.stub(name, storage)
MUTEX.synchronize do
@stubs ||= {}
@stubs[storage] ||= {}
@stubs[storage][name] ||= begin
klass = Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
addr = address(storage)
addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
klass.new(addr, :this_channel_is_insecure)
end
unless URI(address).scheme.in?(%w(tcp unix))
raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'"
end
@addresses[name] = address
@channels[name] = new_channel(address)
end
end
def self.new_channel(address)
address = address.sub(%r{^tcp://}, '') if URI(address).scheme == 'tcp'
# NOTE: When Gitaly runs on a Unix socket, permissions are
# handled using the file system and no additional authentication is
# required (therefore the :this_channel_is_insecure flag)
# TODO: Add authentication support when Gitaly is running on a TCP socket.
GRPC::Core::Channel.new(address, {}, :this_channel_is_insecure)
def self.clear_stubs!
MUTEX.synchronize do
@stubs = nil
end
end
def self.get_channel(storage)
if !Rails.env.production? && @channels.nil?
# In development mode the Rails auto-loader may reset the instance
# variables of this class. What we do here is not thread-safe. In normal
# circumstances (including production) these instance variables have
# been initialized from config/initializers.
configure_channels
def self.address(storage)
params = Gitlab.config.repositories.storages[storage]
raise "storage not found: #{storage.inspect}" if params.nil?
address = params['gitaly_address']
unless address.present?
raise "storage #{storage.inspect} is missing a gitaly_address"
end
@channels[storage]
end
def self.get_address(storage)
if !Rails.env.production? && @addresses.nil?
# In development mode the Rails auto-loader may reset the instance
# variables of this class. What we do here is not thread-safe. In normal
# circumstances (including development) these instance variables have
# been initialized from config/initializers.
configure_channels
unless URI(address).scheme.in?(%w(tcp unix))
raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'"
end
@addresses[storage]
address
end
def self.enabled?

View file

@ -11,7 +11,7 @@ module Gitlab
end
def is_ancestor(ancestor_id, child_id)
stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: @repository.gitaly_channel)
stub = GitalyClient.stub(:commit, @repository.storage)
request = Gitaly::CommitIsAncestorRequest.new(
repository: @gitaly_repo,
ancestor_id: ancestor_id,
@ -52,7 +52,7 @@ module Gitlab
end
def diff_service_stub
Gitaly::Diff::Stub.new(nil, nil, channel_override: @repository.gitaly_channel)
GitalyClient.stub(:diff, @repository.storage)
end
end
end

View file

@ -6,7 +6,7 @@ module Gitlab
# 'repository' is a Gitlab::Git::Repository
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
@stub = GitalyClient.stub(:notifications, repository.storage)
end
def post_receive

View file

@ -6,7 +6,7 @@ module Gitlab
# 'repository' is a Gitlab::Git::Repository
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@stub = Gitaly::Ref::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
@stub = GitalyClient.stub(:ref, repository.storage)
end
def default_branch_name

View file

@ -26,7 +26,7 @@ module Gitlab
}
if Gitlab.config.gitaly.enabled
address = Gitlab::GitalyClient.get_address(project.repository_storage)
address = Gitlab::GitalyClient.address(project.repository_storage)
params[:Repository] = repository.gitaly_repository.to_h
feature_enabled = case action.to_s

View file

@ -1,14 +1,19 @@
require 'spec_helper'
describe Gitlab::GitalyClient, lib: true do
describe '.new_channel' do
describe '.stub' do
before { described_class.clear_stubs! }
context 'when passed a UNIX socket address' do
it 'passes the address as-is to GRPC::Core::Channel initializer' do
it 'passes the address as-is to GRPC' do
address = 'unix:/tmp/gitaly.sock'
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'default' => { 'gitaly_address' => address }
})
expect(GRPC::Core::Channel).to receive(:new).with(address, any_args)
expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args)
described_class.new_channel(address)
described_class.stub(:commit, 'default')
end
end
@ -17,9 +22,13 @@ describe Gitlab::GitalyClient, lib: true do
address = 'localhost:9876'
prefixed_address = "tcp://#{address}"
expect(GRPC::Core::Channel).to receive(:new).with(address, any_args)
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'default' => { 'gitaly_address' => prefixed_address }
})
described_class.new_channel(prefixed_address)
expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args)
described_class.stub(:commit, 'default')
end
end
end

View file

@ -202,7 +202,7 @@ describe Gitlab::Workhorse, lib: true do
context 'when Gitaly is enabled' do
let(:gitaly_params) do
{
GitalyAddress: Gitlab::GitalyClient.get_address('default')
GitalyAddress: Gitlab::GitalyClient.address('default')
}
end

View file

@ -120,7 +120,7 @@ module TestEnv
end
def setup_gitaly
socket_path = Gitlab::GitalyClient.get_address('default').sub(/\Aunix:/, '')
socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
unless File.directory?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")

View file

@ -236,7 +236,6 @@ describe 'gitlab:app namespace rake task' do
'custom' => { 'path' => Settings.absolute('tmp/tests/custom_storage'), 'gitaly_address' => gitaly_address }
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
Gitlab::GitalyClient.configure_channels
# Create the projects now, after mocking the settings but before doing the backup
project_a