Commit a1ee3658 authored by Yorick Peterse's avatar Yorick Peterse

Merge branch 'mk/improve-usage-of-request-store' into 'master'

Resolve "Provide NullStore for RequestStore"

Closes #51718

See merge request gitlab-org/gitlab-ce!21848
parents cdff2c66 74ae1358
...@@ -10,11 +10,7 @@ module WithPerformanceBar ...@@ -10,11 +10,7 @@ module WithPerformanceBar
def peek_enabled? def peek_enabled?
return false unless Gitlab::PerformanceBar.enabled?(current_user) return false unless Gitlab::PerformanceBar.enabled?(current_user)
if RequestStore.active? Gitlab::SafeRequestStore.fetch(:peek_enabled) { cookie_or_default_value }
RequestStore.fetch(:peek_enabled) { cookie_or_default_value }
else
cookie_or_default_value
end
end end
private private
......
...@@ -74,7 +74,7 @@ class Ability ...@@ -74,7 +74,7 @@ class Ability
end end
def policy_for(user, subject = :global) def policy_for(user, subject = :global)
cache = RequestStore.active? ? RequestStore : {} cache = Gitlab::SafeRequestStore.active? ? Gitlab::SafeRequestStore : {}
DeclarativePolicy.policy_for(user, subject, cache: cache) DeclarativePolicy.policy_for(user, subject, cache: cache)
end end
......
...@@ -16,9 +16,9 @@ module BulkMemberAccessLoad ...@@ -16,9 +16,9 @@ module BulkMemberAccessLoad
key = max_member_access_for_resource_key(resource_klass, memoization_index) key = max_member_access_for_resource_key(resource_klass, memoization_index)
access = {} access = {}
if RequestStore.active? if Gitlab::SafeRequestStore.active?
RequestStore.store[key] ||= {} Gitlab::SafeRequestStore[key] ||= {}
access = RequestStore.store[key] access = Gitlab::SafeRequestStore[key]
end end
# Look up only the IDs we need # Look up only the IDs we need
......
...@@ -27,11 +27,7 @@ module CacheableAttributes ...@@ -27,11 +27,7 @@ module CacheableAttributes
end end
def cached def cached
if RequestStore.active? Gitlab::SafeRequestStore[:"#{name}_cached_attributes"] ||= retrieve_from_cache
RequestStore[:"#{name}_cached_attributes"] ||= retrieve_from_cache
else
retrieve_from_cache
end
end end
def retrieve_from_cache def retrieve_from_cache
......
...@@ -20,11 +20,7 @@ class LegacyDiffNote < Note ...@@ -20,11 +20,7 @@ class LegacyDiffNote < Note
end end
def project_repository def project_repository
if RequestStore.active? Gitlab::SafeRequestStore.fetch("project:#{project_id}:repository") { self.project.repository }
RequestStore.fetch("project:#{project_id}:repository") { self.project.repository }
else
self.project.repository
end
end end
def diff_file_hash def diff_file_hash
......
...@@ -148,8 +148,8 @@ class Namespace < ActiveRecord::Base ...@@ -148,8 +148,8 @@ class Namespace < ActiveRecord::Base
def find_fork_of(project) def find_fork_of(project)
return nil unless project.fork_network return nil unless project.fork_network
if RequestStore.active? if Gitlab::SafeRequestStore.active?
forks_in_namespace = RequestStore.fetch("namespaces:#{id}:forked_projects") do forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do
Hash.new do |found_forks, project| Hash.new do |found_forks, project|
found_forks[project] = project.fork_network.find_forks_in(projects).first found_forks[project] = project.fork_network.find_forks_in(projects).first
end end
......
...@@ -2266,11 +2266,7 @@ class Project < ActiveRecord::Base ...@@ -2266,11 +2266,7 @@ class Project < ActiveRecord::Base
end end
end end
if RequestStore.active? Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
RequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
check_access.call
end
else
check_access.call check_access.call
end end
end end
......
...@@ -168,6 +168,7 @@ user objects for every username we can remove the need for running the same ...@@ -168,6 +168,7 @@ user objects for every username we can remove the need for running the same
query for every mention of `@alice`. query for every mention of `@alice`.
Caching data per transaction can be done using Caching data per transaction can be done using
[RequestStore](https://github.com/steveklabnik/request_store). Caching data in [RequestStore](https://github.com/steveklabnik/request_store) (use
Redis can be done using [Rails' caching `Gitlab::SafeRequestStore` to avoid having to remember to check
`RequestStore.active?`). Caching data in Redis can be done using [Rails' caching
system](http://guides.rubyonrails.org/caching_with_rails.html). system](http://guides.rubyonrails.org/caching_with_rails.html).
...@@ -296,7 +296,7 @@ module Banzai ...@@ -296,7 +296,7 @@ module Banzai
# Returns projects for the given paths. # Returns projects for the given paths.
def find_for_paths(paths) def find_for_paths(paths)
if RequestStore.active? if Gitlab::SafeRequestStore.active?
cache = refs_cache cache = refs_cache
to_query = paths - cache.keys to_query = paths - cache.keys
...@@ -340,7 +340,7 @@ module Banzai ...@@ -340,7 +340,7 @@ module Banzai
end end
def refs_cache def refs_cache
RequestStore["banzai_#{parent_type}_refs".to_sym] ||= {} Gitlab::SafeRequestStore["banzai_#{parent_type}_refs".to_sym] ||= {}
end end
def parent_type def parent_type
......
...@@ -97,9 +97,7 @@ module Banzai ...@@ -97,9 +97,7 @@ module Banzai
private private
def external_issues_cached(attribute) def external_issues_cached(attribute)
return project.public_send(attribute) unless RequestStore.active? # rubocop:disable GitlabSecurity/PublicSend cached_attributes = Gitlab::SafeRequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} }
cached_attributes = RequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} }
cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? # rubocop:disable GitlabSecurity/PublicSend cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? # rubocop:disable GitlabSecurity/PublicSend
cached_attributes[project.id][attribute] cached_attributes[project.id][attribute]
end end
......
...@@ -166,7 +166,7 @@ module Banzai ...@@ -166,7 +166,7 @@ module Banzai
# objects that have not yet been queried. For objects that have already # objects that have not yet been queried. For objects that have already
# been queried the object is returned from the cache. # been queried the object is returned from the cache.
def collection_objects_for_ids(collection, ids) def collection_objects_for_ids(collection, ids)
if RequestStore.active? if Gitlab::SafeRequestStore.active?
ids = ids.map(&:to_i) ids = ids.map(&:to_i)
cache = collection_cache[collection_cache_key(collection)] cache = collection_cache[collection_cache_key(collection)]
to_query = ids - cache.keys to_query = ids - cache.keys
...@@ -248,7 +248,7 @@ module Banzai ...@@ -248,7 +248,7 @@ module Banzai
end end
def collection_cache def collection_cache
RequestStore[:banzai_collection_cache] ||= Hash.new do |hash, key| Gitlab::SafeRequestStore[:banzai_collection_cache] ||= Hash.new do |hash, key|
hash[key] = {} hash[key] = {}
end end
end end
......
module Banzai module Banzai
module RequestStoreReferenceCache module RequestStoreReferenceCache
def cached_call(request_store_key, cache_key, path: []) def cached_call(request_store_key, cache_key, path: [])
if RequestStore.active? if Gitlab::SafeRequestStore.active?
cache = RequestStore[request_store_key] ||= Hash.new do |hash, key| cache = Gitlab::SafeRequestStore[request_store_key] ||= Hash.new do |hash, key|
hash[key] = Hash.new { |h, k| h[k] = {} } hash[key] = Hash.new { |h, k| h[k] = {} }
end end
......
...@@ -28,11 +28,7 @@ class Feature ...@@ -28,11 +28,7 @@ class Feature
end end
def persisted_names def persisted_names
if RequestStore.active? Gitlab::SafeRequestStore[:flipper_persisted_names] ||= FlipperFeature.feature_names
RequestStore[:flipper_persisted_names] ||= FlipperFeature.feature_names
else
FlipperFeature.feature_names
end
end end
def persisted?(feature) def persisted?(feature)
...@@ -76,11 +72,7 @@ class Feature ...@@ -76,11 +72,7 @@ class Feature
end end
def flipper def flipper
if RequestStore.active? @flipper ||= (Gitlab::SafeRequestStore[:flipper] ||= build_flipper_instance)
RequestStore[:flipper] ||= build_flipper_instance
else
@flipper ||= build_flipper_instance
end
end end
def build_flipper_instance def build_flipper_instance
......
...@@ -26,8 +26,8 @@ module Gitlab ...@@ -26,8 +26,8 @@ module Gitlab
define_method(method_name) do |*args| define_method(method_name) do |*args|
store = store =
if RequestStore.active? if Gitlab::SafeRequestStore.active?
RequestStore.store Gitlab::SafeRequestStore.store
else else
ivar_name = # ! and ? cannot be used as ivar name ivar_name = # ! and ? cannot be used as ivar name
"@cache_#{method_name.to_s.tr('!?', "\u2605\u2606")}" "@cache_#{method_name.to_s.tr('!?', "\u2605\u2606")}"
......
...@@ -2,11 +2,7 @@ module Gitlab ...@@ -2,11 +2,7 @@ module Gitlab
module CurrentSettings module CurrentSettings
class << self class << self
def current_application_settings def current_application_settings
if RequestStore.active? Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
RequestStore.fetch(:current_application_settings) { ensure_application_settings! }
else
ensure_application_settings!
end
end end
def fake_application_settings(attributes = {}) def fake_application_settings(attributes = {})
......
...@@ -101,18 +101,14 @@ module Gitlab ...@@ -101,18 +101,14 @@ module Gitlab
return @diff_file if defined?(@diff_file) return @diff_file if defined?(@diff_file)
@diff_file = begin @diff_file = begin
if RequestStore.active? key = {
key = { project_id: repository.project.id,
project_id: repository.project.id, start_sha: start_sha,
start_sha: start_sha, head_sha: head_sha,
head_sha: head_sha, path: file_path
path: file_path }
}
Gitlab::SafeRequestStore.fetch(key) { find_diff_file(repository) }
RequestStore.fetch(key) { find_diff_file(repository) }
else
find_diff_file(repository)
end
end end
end end
......
...@@ -47,7 +47,7 @@ module Gitlab ...@@ -47,7 +47,7 @@ module Gitlab
end end
def appearance def appearance
RequestStore.store[:appearance] ||= (Appearance.current || Appearance.new) Gitlab::SafeRequestStore[:appearance] ||= (Appearance.current || Appearance.new)
end end
def appearance_favicon def appearance_favicon
......
...@@ -17,18 +17,18 @@ module Gitlab ...@@ -17,18 +17,18 @@ module Gitlab
].freeze ].freeze
def self.set(gl_repository, env) def self.set(gl_repository, env)
return unless RequestStore.active? return unless Gitlab::SafeRequestStore.active?
raise "missing gl_repository" if gl_repository.blank? raise "missing gl_repository" if gl_repository.blank?
RequestStore.store[:gitlab_git_env] ||= {} Gitlab::SafeRequestStore[:gitlab_git_env] ||= {}
RequestStore.store[:gitlab_git_env][gl_repository] = whitelist_git_env(env) Gitlab::SafeRequestStore[:gitlab_git_env][gl_repository] = whitelist_git_env(env)
end end
def self.all(gl_repository) def self.all(gl_repository)
return {} unless RequestStore.active? return {} unless Gitlab::SafeRequestStore.active?
h = RequestStore.fetch(:gitlab_git_env) { {} } h = Gitlab::SafeRequestStore.fetch(:gitlab_git_env) { {} }
h.fetch(gl_repository, {}) h.fetch(gl_repository, {})
end end
......
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
to: :failure_info to: :failure_info
def self.for_storage(storage) def self.for_storage(storage)
cached_circuitbreakers = RequestStore.fetch(:circuitbreaker_cache) do cached_circuitbreakers = Gitlab::SafeRequestStore.fetch(:circuitbreaker_cache) do
Hash.new do |hash, storage_name| Hash.new do |hash, storage_name|
hash[storage_name] = build(storage_name) hash[storage_name] = build(storage_name)
end end
......
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
redis.del(*all_storage_keys) unless all_storage_keys.empty? redis.del(*all_storage_keys) unless all_storage_keys.empty?
end end
RequestStore.delete(:circuitbreaker_cache) Gitlab::SafeRequestStore.delete(:circuitbreaker_cache)
end end
def self.load(cache_key) def self.load(cache_key)
......
...@@ -115,11 +115,7 @@ module Gitlab ...@@ -115,11 +115,7 @@ module Gitlab
def version(commit_id) def version(commit_id)
commit_find_proc = -> { Gitlab::Git::Commit.find(@repository, commit_id) } commit_find_proc = -> { Gitlab::Git::Commit.find(@repository, commit_id) }
if RequestStore.active? Gitlab::SafeRequestStore.fetch([:wiki_version_commit, commit_id]) { commit_find_proc.call }
RequestStore.fetch([:wiki_version_commit, commit_id]) { commit_find_proc.call }
else
commit_find_proc.call
end
end end
def assert_type!(object, klass) def assert_type!(object, klass)
......
...@@ -316,7 +316,7 @@ module Gitlab ...@@ -316,7 +316,7 @@ module Gitlab
# Ensures that Gitaly is not being abuse through n+1 misuse etc # Ensures that Gitaly is not being abuse through n+1 misuse etc
def self.enforce_gitaly_request_limits(call_site) def self.enforce_gitaly_request_limits(call_site)
# Only count limits in request-response environments (not sidekiq for example) # Only count limits in request-response environments (not sidekiq for example)
return unless RequestStore.active? return unless Gitlab::SafeRequestStore.active?
# This is this actual number of times this call was made. Used for information purposes only # This is this actual number of times this call was made. Used for information purposes only
actual_call_count = increment_call_count("gitaly_#{call_site}_actual") actual_call_count = increment_call_count("gitaly_#{call_site}_actual")
...@@ -340,7 +340,7 @@ module Gitlab ...@@ -340,7 +340,7 @@ module Gitlab
end end
def self.allow_n_plus_1_calls def self.allow_n_plus_1_calls
return yield unless RequestStore.active? return yield unless Gitlab::SafeRequestStore.active?
begin begin
increment_call_count(:gitaly_call_count_exception_block_depth) increment_call_count(:gitaly_call_count_exception_block_depth)
...@@ -351,25 +351,25 @@ module Gitlab ...@@ -351,25 +351,25 @@ module Gitlab
end end
def self.get_call_count(key) def self.get_call_count(key)
RequestStore.store[key] || 0 Gitlab::SafeRequestStore[key] || 0
end end
private_class_method :get_call_count private_class_method :get_call_count
def self.increment_call_count(key) def self.increment_call_count(key)
RequestStore.store[key] ||= 0 Gitlab::SafeRequestStore[key] ||= 0
RequestStore.store[key] += 1 Gitlab::SafeRequestStore[key] += 1
end end
private_class_method :increment_call_count private_class_method :increment_call_count
def self.decrement_call_count(key) def self.decrement_call_count(key)
RequestStore.store[key] -= 1 Gitlab::SafeRequestStore[key] -= 1
end end
private_class_method :decrement_call_count private_class_method :decrement_call_count
# Returns an estimate of the number of Gitaly calls made for this # Returns an estimate of the number of Gitaly calls made for this
# request # request
def self.get_request_count def self.get_request_count
return 0 unless RequestStore.active? return 0 unless Gitlab::SafeRequestStore.active?
gitaly_migrate_count = get_call_count("gitaly_migrate_actual") gitaly_migrate_count = get_call_count("gitaly_migrate_actual")
gitaly_call_count = get_call_count("gitaly_call_actual") gitaly_call_count = get_call_count("gitaly_call_actual")
...@@ -386,28 +386,28 @@ module Gitlab ...@@ -386,28 +386,28 @@ module Gitlab
end end
def self.reset_counts def self.reset_counts
return unless RequestStore.active? return unless Gitlab::SafeRequestStore.active?
%w[migrate call].each do |call_site| %w[migrate call].each do |call_site|
RequestStore.store["gitaly_#{call_site}_actual"] = 0 Gitlab::SafeRequestStore["gitaly_#{call_site}_actual"] = 0
RequestStore.store["gitaly_#{call_site}_permitted"] = 0 Gitlab::SafeRequestStore["gitaly_#{call_site}_permitted"] = 0
end end
end end
def self.add_call_details(details) def self.add_call_details(details)
id = details.delete(:id) id = details.delete(:id)
return unless id && RequestStore.active? && RequestStore.store[:peek_enabled] return unless id && Gitlab::SafeRequestStore[:peek_enabled]
RequestStore.store['gitaly_call_details'] ||= {} Gitlab::SafeRequestStore['gitaly_call_details'] ||= {}
RequestStore.store['gitaly_call_details'][id] ||= {} Gitlab::SafeRequestStore['gitaly_call_details'][id] ||= {}
RequestStore.store['gitaly_call_details'][id].merge!(details) Gitlab::SafeRequestStore['gitaly_call_details'][id].merge!(details)
end end
def self.list_call_details def self.list_call_details
return {} unless RequestStore.active? && RequestStore.store[:peek_enabled] return {} unless Gitlab::SafeRequestStore[:peek_enabled]
RequestStore.store['gitaly_call_details'] || {} Gitlab::SafeRequestStore['gitaly_call_details'] || {}
end end
def self.expected_server_version def self.expected_server_version
...@@ -445,22 +445,22 @@ module Gitlab ...@@ -445,22 +445,22 @@ module Gitlab
# Count a stack. Used for n+1 detection # Count a stack. Used for n+1 detection
def self.count_stack def self.count_stack
return unless RequestStore.active? return unless Gitlab::SafeRequestStore.active?
stack_string = Gitlab::Profiler.clean_backtrace(caller).drop(1).join("\n") stack_string = Gitlab::Profiler.clean_backtrace(caller).drop(1).join("\n")
RequestStore.store[:stack_counter] ||= Hash.new Gitlab::SafeRequestStore[:stack_counter] ||= Hash.new
count = RequestStore.store[:stack_counter][stack_string] || 0 count = Gitlab::SafeRequestStore[:stack_counter][stack_string] || 0
RequestStore.store[:stack_counter][stack_string] = count + 1 Gitlab::SafeRequestStore[:stack_counter][stack_string] = count + 1
end end
private_class_method :count_stack private_class_method :count_stack
# Returns a count for the stack which called Gitaly the most times. Used for n+1 detection # Returns a count for the stack which called Gitaly the most times. Used for n+1 detection
def self.max_call_count def self.max_call_count
return 0 unless RequestStore.active? return 0 unless Gitlab::SafeRequestStore.active?
stack_counter = RequestStore.store[:stack_counter] stack_counter = Gitlab::SafeRequestStore[:stack_counter]
return 0 unless stack_counter return 0 unless stack_counter
stack_counter.values.max stack_counter.values.max
...@@ -469,9 +469,9 @@ module Gitlab ...@@ -469,9 +469,9 @@ module Gitlab
# Returns the stacks that calls Gitaly the most times. Used for n+1 detection # Returns the stacks that calls Gitaly the most times. Used for n+1 detection
def self.max_stacks def self.max_stacks
return nil unless RequestStore.active? return nil unless Gitlab::SafeRequestStore.active?
stack_counter = RequestStore.store[:stack_counter] stack_counter = Gitlab::SafeRequestStore[:stack_counter]
return nil unless stack_counter return nil unless stack_counter
max = max_call_count max = max_call_count
......
...@@ -240,22 +240,23 @@ module Gitlab ...@@ -240,22 +240,23 @@ module Gitlab
end end
def find_commit(revision) def find_commit(revision)
if RequestStore.active? if Gitlab::SafeRequestStore.active?
# We don't use RequeStstore.fetch(key) { ... } directly because `revision` # We don't use Gitlab::SafeRequestStore.fetch(key) { ... } directly
# can be a branch name, so we can't use it as a key as it could point # because `revision` can be a branch name, so we can't use it as a key
# to another commit later on (happens a lot in tests). # as it could point to another commit later on (happens a lot in
# tests).
key = { key = {
storage: @gitaly_repo.storage_name, storage: @gitaly_repo.storage_name,
relative_path: @gitaly_repo.relative_path, relative_path: @gitaly_repo.relative_path,
commit_id: revision commit_id: revision
} }
return RequestStore[key] if RequestStore.exist?(key) return Gitlab::SafeRequestStore[key] if Gitlab::SafeRequestStore.exist?(key)
commit = call_find_commit(revision) commit = call_find_commit(revision)
return unless commit return unless commit
key[:commit_id] = commit.id key[:commit_id] = commit.id
RequestStore[key] = commit Gitlab::SafeRequestStore[key] = commit
else else
call_find_commit(revision) call_find_commit(revision)
end end
......
module Gitlab module Gitlab
# Class for counting and caching the number of issuables per state. # Class for counting and caching the number of issuables per state.
class IssuablesCountForState class IssuablesCountForState
# The name of the RequestStore cache key. # The name of the Gitlab::SafeRequestStore cache key.
CACHE_KEY = :issuables_count_for_state CACHE_KEY = :issuables_count_for_state
# The state values that can be safely casted to a Symbol. # The state values that can be safely casted to a Symbol.
...@@ -10,12 +10,7 @@ module Gitlab ...@@ -10,12 +10,7 @@ module Gitlab
# finder - The finder class to use for retrieving the issuables. # finder - The finder class to use for retrieving the issuables.
def initialize(finder) def initialize(finder)
@finder = finder @finder = finder
@cache = @cache = Gitlab::SafeRequestStore[CACHE_KEY] ||= initialize_cache
if RequestStore.active?
RequestStore[CACHE_KEY] ||= initialize_cache
else
initialize_cache
end
end end
def for_state_or_opened(state = nil) def for_state_or_opened(state = nil)
......
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
end end
def self.build def self.build
RequestStore[self.cache_key] ||= new(self.full_log_path) Gitlab::SafeRequestStore[self.cache_key] ||= new(self.full_log_path)
end end
def self.full_log_path def self.full_log_path
......
# frozen_string_literal: true
# Used by Gitlab::SafeRequestStore
module Gitlab
# The methods `begin!`, `clear!`, and `end!` are not defined because they
# should only be called directly on `RequestStore`.
class NullRequestStore
def store
{}
end
def active?
end
def read(key)
end
def [](key)
end
def write(key, value)
value
end
def []=(key, value)
value
end
def exist?(key)
false
end
def fetch(key, &block)
yield
end
def delete(key, &block)
yield(key) if block_given?
end
end
end
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
end end
subscribe('sql.active_record') do |_, start, finish, _, data| subscribe('sql.active_record') do |_, start, finish, _, data|
if RequestStore.active? && RequestStore.store[:peek_enabled] if Gitlab::SafeRequestStore.store[:peek_enabled]
# data[:cached] is only available starting from Rails 5.1.0 # data[:cached] is only available starting from Rails 5.1.0
# https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L113 # https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L113
# Before that, data[:name] was set to 'CACHE' # Before that, data[:name] was set to 'CACHE'
......
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
class RequestContext class RequestContext
class << self class << self
def client_ip def client_ip
RequestStore[:client_ip] Gitlab::SafeRequestStore[:client_ip]
end end
end end
...@@ -13,7 +13,7 @@ module Gitlab ...@@ -13,7 +13,7 @@ module Gitlab
def call(env) def call(env)
req = Rack::Request.new(env) req = Rack::Request.new(env)
RequestStore[:client_ip] = req.ip Gitlab::SafeRequestStore[:client_ip] = req.ip
@app.call(env) @app.call(env)
end end
......
# frozen_string_literal: true
module Gitlab
module SafeRequestStore
NULL_STORE = Gitlab::NullRequestStore.new
class << self
# These methods should always run directly against RequestStore
delegate :clear!, :begin!, :end!, :active?, to: :RequestStore
# These methods will run against NullRequestStore if RequestStore is disabled
delegate :read, :[], :write, :[]=, :exist?, :fetch, :delete, to: :store
end
def self.store
if RequestStore.active?
RequestStore
else
NULL_STORE
end
end
end
end
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
end end
def temporarily_allowed?(key) def temporarily_allowed?(key)
if RequestStore.active? if Gitlab::SafeRequestStore.active?
temporarily_allow_request_store[key] > 0 temporarily_allow_request_store[key] > 0
else else
TEMPORARILY_ALLOW_MUTEX.synchronize do TEMPORARILY_ALLOW_MUTEX.synchronize do
...@@ -26,11 +26,11 @@ module Gitlab ...@@ -26,11 +26,11 @@ module Gitlab
end end
def temporarily_allow_request_store def temporarily_allow_request_store
RequestStore[:temporarily_allow] ||= Hash.new(0) Gitlab::SafeRequestStore[:temporarily_allow] ||= Hash.new(0)
end end
def temporarily_allow_add(key, value) def temporarily_allow_add(key, value)
if RequestStore.active? if Gitlab::SafeRequestStore.active?
temporarily_allow_request_store[key] += value temporarily_allow_request_store[key] += value
else else
TEMPORARILY_ALLOW_MUTEX.synchronize do TEMPORARILY_ALLOW_MUTEX.synchronize do
......
...@@ -79,13 +79,9 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do ...@@ -79,13 +79,9 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
expect(link).to eq helper.url_for_issue(issue_id, project, only_path: true) expect(link).to eq helper.url_for_issue(issue_id, project, only_path: true)
end end
context 'with RequestStore enabled' do context 'with RequestStore enabled', :request_store do
let(:reference_filter) { HTML::Pipeline.new([described_class]) } let(:reference_filter) { HTML::Pipeline.new([described_class]) }
before do
allow(RequestStore).to receive(:active?).and_return(true)
end
it 'queries the collection on the first call' do it 'queries the collection on the first call' do
expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original
expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original
......
...@@ -263,11 +263,10 @@ describe Banzai::ReferenceParser::BaseParser do ...@@ -263,11 +263,10 @@ describe Banzai::ReferenceParser::BaseParser do
end end
end end
context 'with RequestStore enabled' do context 'with RequestStore enabled', :request_store do
before do before do
cache = Hash.new { |hash, key| hash[key] = {} } cache = Hash.new { |hash, key| hash[key] = {} }
allow(RequestStore).to receive(:active?).and_return(true)
allow(subject).to receive(:collection_cache).and_return(cache) allow(subject).to receive(:collection_cache).and_return(cache)
end end
......
...@@ -91,7 +91,11 @@ describe Feature do ...@@ -91,7 +91,11 @@ describe Feature do
end end
describe '.flipper' do describe '.flipper' do
shared_examples 'a memoized Flipper instance' do before do
described_class.instance_variable_set(:@flipper, nil)
end
context 'when request store is inactive' do
it 'memoizes the Flipper instance' do it 'memoizes the Flipper instance' do
expect(Flipper).to receive(:new).once.and_call_original expect(Flipper).to receive(:new).once.and_call_original
...@@ -101,16 +105,14 @@ describe Feature do ...@@ -101,16 +105,14 @@ describe Feature do
end end
end end
context 'when request store is inactive' do context 'when request store is active', :request_store do
before do it 'memoizes the Flipper instance' do
expect(Flipper).to receive(:new).once.and_call_original
described_class.flipper
described_class.instance_variable_set(:@flipper, nil) described_class.instance_variable_set(:@flipper, nil)
described_class.flipper
end end
it_behaves_like 'a memoized Flipper instance'
end
context 'when request store is inactive', :request_store do
it_behaves_like 'a memoized Flipper instance'
end end
end end
......
...@@ -4,11 +4,7 @@ describe Gitlab::Git::HookEnv do ...@@ -4,11 +4,7 @@ describe Gitlab::Git::HookEnv do
let(:gl_repository) { 'project-123' } let(:gl_repository) { 'project-123' }
describe ".set" do describe ".set" do
context 'with RequestStore.store disabled' do context 'with RequestStore disabled' do
before do
allow(RequestStore).to receive(:active?).and_return(false)
end
it 'does not store anything' do it 'does not store anything' do
described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo') described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
...@@ -16,11 +12,7 @@ describe Gitlab::Git::HookEnv do ...@@ -16,11 +12,7 @@ describe Gitlab::Git::HookEnv do
end end
end end
context 'with RequestStore.store enabled' do context 'with RequestStore enabled', :request_store do
before do
allow(RequestStore).to receive(:active?).and_return(true)
end
it 'whitelist some `GIT_*` variables and stores them using RequestStore' do it 'whitelist some `GIT_*` variables and stores them using RequestStore' do
described_class.set( described_class.set(
gl_repository, gl_repository,
...@@ -41,9 +33,8 @@ describe Gitlab::Git::HookEnv do ...@@ -41,9 +33,8 @@ describe Gitlab::Git::HookEnv do
end end
describe ".all" do describe ".all" do
context 'with RequestStore.store enabled' do context 'with RequestStore enabled', :request_store do
before do before do
allow(RequestStore).to receive(:active?).and_return(true)
described_class.set( described_class.set(
gl_repository, gl_repository,
GIT_OBJECT_DIRECTORY_RELATIVE: 'foo', GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
...@@ -60,7 +51,7 @@ describe Gitlab::Git::HookEnv do ...@@ -60,7 +51,7 @@ describe Gitlab::Git::HookEnv do
end end
describe ".to_env_hash" do describe ".to_env_hash" do
context 'with RequestStore.store enabled' do context 'with RequestStore enabled', :request_store do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
let(:key) { 'GIT_OBJECT_DIRECTORY_RELATIVE' } let(:key) { 'GIT_OBJECT_DIRECTORY_RELATIVE' }
...@@ -76,7 +67,6 @@ describe Gitlab::Git::HookEnv do ...@@ -76,7 +67,6 @@ describe Gitlab::Git::HookEnv do
with_them do with_them do
before do before do
allow(RequestStore).to receive(:active?).and_return(true)
described_class.set(gl_repository, key.to_sym => input) described_class.set(gl_repository, key.to_sym => input)
end end
...@@ -92,7 +82,7 @@ describe Gitlab::Git::HookEnv do ...@@ -92,7 +82,7 @@ describe Gitlab::Git::HookEnv do
end end
describe 'thread-safety' do describe 'thread-safety' do
context 'with RequestStore.store enabled' do context 'with RequestStore enabled', :request_store do
before do before do
allow(RequestStore).to receive(:active?).and_return(true) allow(RequestStore).to receive(:active?).and_return(true)
described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo') described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::NullRequestStore do
let(:null_store) { described_class.new }
describe '#store' do
it 'returns an empty hash' do
expect(null_store.store).to eq({})
end
end
describe '#active?' do
it 'returns falsey' do
expect(null_store.active?).to be_falsey
end
end
describe '#read' do
it 'returns nil' do
expect(null_store.read('foo')).to be nil
end
end
describe '#[]' do
it 'returns nil' do
expect(null_store['foo']).to be nil
end
end
describe '#write' do
it 'returns the same value' do
expect(null_store.write('key', 'value')).to eq('value')
end
end
describe '#[]=' do
it 'returns the same value' do
expect(null_store['key'] = 'value').to eq('value')
end
end
describe '#exist?' do
it 'returns falsey' do
expect(null_store.exist?('foo')).to be_falsey
end
end
describe '#fetch' do
it 'returns the block result' do
expect(null_store.fetch('key') { 'block result' }).to eq('block result')
end
end
describe '#delete' do
context 'when a block is given' do
it 'yields the key to the block' do
expect do |b|
null_store.delete('foo', &b)
end.to yield_with_args('foo')
end
it 'returns the block result' do
expect(null_store.delete('foo') { |key| 'block result' }).to eq('block result')
end
end
context 'when a block is not given' do
it 'returns nil' do
expect(null_store.delete('foo')).to be nil
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SafeRequestStore do
describe '.store' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect(described_class.store).to eq(RequestStore)
end
end
context 'when RequestStore is NOT active' do
it 'does not use RequestStore' do
expect(described_class.store).to be_a(Gitlab::NullRequestStore)
end
end
end
describe '.begin!' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect(RequestStore).to receive(:begin!)
described_class.begin!
end
end
context 'when RequestStore is NOT active' do
it 'uses RequestStore' do
expect(RequestStore).to receive(:begin!)
described_class.begin!
end
end
end
describe '.clear!' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect(RequestStore).to receive(:clear!).twice.and_call_original
described_class.clear!
end
end
context 'when RequestStore is NOT active' do
it 'uses RequestStore' do
expect(RequestStore).to receive(:clear!).and_call_original
described_class.clear!
end
end
end
describe '.end!' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect(RequestStore).to receive(:end!).twice.and_call_original
described_class.end!
end
end
context 'when RequestStore is NOT active' do
it 'uses RequestStore' do
expect(RequestStore).to receive(:end!).and_call_original
described_class.end!
end
end
end
describe '.write' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect do
described_class.write('foo', true)
end.to change { described_class.read('foo') }.from(nil).to(true)
end
end
context 'when RequestStore is NOT active' do
it 'does not use RequestStore' do
expect do
described_class.write('foo', true)
end.not_to change { described_class.read('foo') }.from(nil)
end
end
end
describe '.[]=' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect do
described_class['foo'] = true
end.to change { described_class.read('foo') }.from(nil).to(true)
end
end
context 'when RequestStore is NOT active' do
it 'does not use RequestStore' do
expect do
described_class['foo'] = true
end.not_to change { described_class.read('foo') }.from(nil)
end
end
end
describe '.read' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect do
RequestStore.write('foo', true)
end.to change { described_class.read('foo') }.from(nil).to(true)
end
end
context 'when RequestStore is NOT active' do
it 'does not use RequestStore' do
expect do
RequestStore.write('foo', true)
end.not_to change { described_class.read('foo') }.from(nil)
RequestStore.clear! # Clean up
end
end
end
describe '.[]' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect do
RequestStore.write('foo', true)
end.to change { described_class['foo'] }.from(nil).to(true)
end
end
context 'when RequestStore is NOT active' do
it 'does not use RequestStore' do
expect do
RequestStore.write('foo', true)
end.not_to change { described_class['foo'] }.from(nil)
RequestStore.clear! # Clean up
end
end
end
describe '.exist?' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect do
RequestStore.write('foo', 'not nil')
end.to change { described_class.exist?('foo') }.from(false).to(true)
end
end
context 'when RequestStore is NOT active' do
it 'does not use RequestStore' do
expect do
RequestStore.write('foo', 'not nil')
end.not_to change { described_class.exist?('foo') }.from(false)
RequestStore.clear! # Clean up
end
end
end
describe '.fetch' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
expect do
described_class.fetch('foo') { 'block result' }
end.to change { described_class.read('foo') }.from(nil).to('block result')
end
end
context 'when RequestStore is NOT active' do
it 'does not use RequestStore' do
RequestStore.clear! # Ensure clean
expect do
described_class.fetch('foo') { 'block result' }
end.not_to change { described_class.read('foo') }.from(nil)
RequestStore.clear! # Clean up
end
end
end
describe '.delete' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
described_class.write('foo', true)
expect do
described_class.delete('foo')
end.to change { described_class.read('foo') }.from(true).to(nil)
end
context 'when given a block and the key exists' do
it 'does not execute the block' do
described_class.write('foo', true)
expect do |b|
described_class.delete('foo', &b)
end.not_to yield_control
end
end
context 'when given a block and the key does not exist' do
it 'yields the key and returns the block result' do
result = described_class.delete('foo') { |key| "#{key} block result" }
expect(result).to eq('foo block result')
end
end
end
context 'when RequestStore is NOT active' do
around do |example|
RequestStore.write('foo', true)
example.run
RequestStore.clear! # Clean up
end
it 'does not use RequestStore' do
expect do
described_class.delete('foo')
end.not_to change { RequestStore.read('foo') }.from(true)
end
context 'when given a block' do
it 'yields the key and returns the block result' do
result = described_class.delete('foo') { |key| "#{key} block result" }
expect(result).to eq('foo block result')
end
end
end
end
end
...@@ -65,7 +65,7 @@ describe Commit do ...@@ -65,7 +65,7 @@ describe Commit do
key = "Commit:author:#{commit.author_email.downcase}" key = "Commit:author:#{commit.author_email.downcase}"
expect(RequestStore.store[key]).to eq(user) expect(Gitlab::SafeRequestStore[key]).to eq(user)
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment