Commit aada5273 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Use RequestStoreWrap for Commit#author

We also try to use instance variable to cache the result if
RequestStore is not available, so we could keep the same logic,
using the same cache key. Also introduce a way to specify method
specific cache key
parent 143fc48a
class Commit class Commit
extend ActiveModel::Naming extend ActiveModel::Naming
extend Gitlab::Cache::RequestStoreWrap
include ActiveModel::Conversion include ActiveModel::Conversion
include Noteable include Noteable
...@@ -169,19 +170,9 @@ class Commit ...@@ -169,19 +170,9 @@ class Commit
end end
def author def author
if RequestStore.active? User.find_by_any_email(author_email.downcase)
key = "commit_author:#{author_email.downcase}"
# nil is a valid value since no author may exist in the system
if RequestStore.store.key?(key)
@author = RequestStore.store[key]
else
@author = find_author_by_any_email
RequestStore.store[key] = @author
end
else
@author ||= find_author_by_any_email
end
end end
request_store_wrap(:author) { author_email.downcase }
def committer def committer
@committer ||= User.find_by_any_email(committer_email.downcase) @committer ||= User.find_by_any_email(committer_email.downcase)
...@@ -368,10 +359,6 @@ class Commit ...@@ -368,10 +359,6 @@ class Commit
end end
end end
def find_author_by_any_email
User.find_by_any_email(author_email.downcase)
end
def repo_changes def repo_changes
changes = { added: [], modified: [], removed: [] } changes = { added: [], modified: [], removed: [] }
......
...@@ -37,23 +37,45 @@ module Gitlab ...@@ -37,23 +37,45 @@ module Gitlab
end end
end end
def request_store_wrap(method_name) def request_store_wrap(method_name, &method_key_block)
const_get(:RequestStoreWrapExtension) const_get(:RequestStoreWrapExtension).module_eval do
.send(:define_method, method_name) do |*args| define_method(method_name) do |*args|
return super(*args) unless RequestStore.active? store =
if RequestStore.active?
RequestStore.store
else
ivar_name = # ! and ? cannot be used as ivar name
"@#{method_name.to_s.tr('!', "\u2605").tr('?', "\u2606")}"
klass = self.class instance_variable_get(ivar_name) ||
key = [klass.name, instance_variable_set(ivar_name, {})
method_name, end
*instance_exec(&klass.request_store_wrap_key),
*args].join(':') key = send("#{method_name}_cache_key", args)
if RequestStore.store.key?(key) if store.key?(key)
RequestStore.store[key] store[key]
else else
RequestStore.store[key] = super(*args) store[key] = super(*args)
end end
end end
cache_key_method_name = "#{method_name}_cache_key"
define_method(cache_key_method_name) do |args|
klass = self.class
instance_key = instance_exec(&klass.request_store_wrap_key) if
klass.request_store_wrap_key
method_key = instance_exec(&method_key_block) if method_key_block
[klass.name, method_name, *instance_key, *method_key, *args]
.join(':')
end
private cache_key_method_name
end
end end
end end
end end
......
...@@ -5,20 +5,17 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do ...@@ -5,20 +5,17 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do
Class.new do Class.new do
extend Gitlab::Cache::RequestStoreWrap extend Gitlab::Cache::RequestStoreWrap
attr_accessor :id, :name, :result attr_accessor :id, :name, :result, :extra
def self.name def self.name
'ExpensiveAlgorithm' 'ExpensiveAlgorithm'
end end
def initialize(id, name, result) def initialize(id, name, result, extra = nil)
self.id = id self.id = id
self.name = name self.name = name
self.result = result self.result = result
end self.extra = nil
request_store_wrap_key do
[id, name]
end end
request_store_wrap def compute(arg) request_store_wrap def compute(arg)
...@@ -28,6 +25,11 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do ...@@ -28,6 +25,11 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do
request_store_wrap def repute(arg) request_store_wrap def repute(arg)
result << arg result << arg
end end
def dispute(arg)
result << arg
end
request_store_wrap(:dispute) { extra }
end end
end end
...@@ -50,51 +52,69 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do ...@@ -50,51 +52,69 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do
expect(algorithm.result).to eq(result) expect(algorithm.result).to eq(result)
end end
it 'computes twice for the different keys, id' do it 'computes twice for the different class name' do
algorithm.compute(true) algorithm.compute(true)
algorithm.id = 'ad' allow(klass).to receive(:name).and_return('CheapAlgo')
result = algorithm.compute(true) result = algorithm.compute(true)
expect(result).to eq([true, true]) expect(result).to eq([true, true])
expect(algorithm.result).to eq(result) expect(algorithm.result).to eq(result)
end end
it 'computes twice for the different keys, name' do it 'computes twice for the different method' do
algorithm.compute(true) algorithm.compute(true)
algorithm.name = 'same' result = algorithm.repute(true)
result = algorithm.compute(true)
expect(result).to eq([true, true]) expect(result).to eq([true, true])
expect(algorithm.result).to eq(result) expect(algorithm.result).to eq(result)
end end
it 'computes twice for the different class name' do it 'computes twice if RequestStore starts over' do
algorithm.compute(true) algorithm.compute(true)
allow(klass).to receive(:name).and_return('CheapAlgo') RequestStore.end!
RequestStore.clear!
RequestStore.begin!
result = algorithm.compute(true) result = algorithm.compute(true)
expect(result).to eq([true, true]) expect(result).to eq([true, true])
expect(algorithm.result).to eq(result) expect(algorithm.result).to eq(result)
end end
it 'computes twice for the different method' do context 'when request_store_wrap_key is provided' do
before do
klass.request_store_wrap_key do
[id, name]
end
end
it 'computes twice for the different keys, id' do
algorithm.compute(true) algorithm.compute(true)
result = algorithm.repute(true) algorithm.id = 'ad'
result = algorithm.compute(true)
expect(result).to eq([true, true]) expect(result).to eq([true, true])
expect(algorithm.result).to eq(result) expect(algorithm.result).to eq(result)
end end
it 'computes twice if RequestStore starts over' do it 'computes twice for the different keys, name' do
algorithm.compute(true) algorithm.compute(true)
RequestStore.end! algorithm.name = 'same'
RequestStore.clear!
RequestStore.begin!
result = algorithm.compute(true) result = algorithm.compute(true)
expect(result).to eq([true, true]) expect(result).to eq([true, true])
expect(algorithm.result).to eq(result) expect(algorithm.result).to eq(result)
end end
it 'uses extra method cache key if provided' do
algorithm.dispute(true) # miss
algorithm.extra = true
algorithm.dispute(true) # miss
result = algorithm.dispute(true) # hit
expect(result).to eq([true, true])
expect(algorithm.result).to eq(result)
end
end
end end
context 'when RequestStore is inactive' do context 'when RequestStore is inactive' do
...@@ -102,10 +122,18 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do ...@@ -102,10 +122,18 @@ describe Gitlab::Cache::RequestStoreWrap, :request_store do
RequestStore.end! RequestStore.end!
end end
it 'computes twice even if everything is the same' do it 'computes only once if it is the same instance for the same key' do
algorithm.compute(true) algorithm.compute(true)
result = algorithm.compute(true) result = algorithm.compute(true)
expect(result).to eq([true])
expect(algorithm.result).to eq(result)
end
it 'computes twice for different instances even if keys are the same' do
algorithm.compute(true)
result = klass.new('id', 'name', algorithm.result).compute(true)
expect(result).to eq([true, true]) expect(result).to eq([true, true])
expect(algorithm.result).to eq(result) expect(algorithm.result).to eq(result)
end end
......
...@@ -19,17 +19,15 @@ describe Commit, models: true do ...@@ -19,17 +19,15 @@ describe Commit, models: true do
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
end end
it 'caches the author' do it 'caches the author', :request_store do
allow(RequestStore).to receive(:active?).and_return(true)
user = create(:user, email: commit.author_email) user = create(:user, email: commit.author_email)
expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original expect(User).to receive(:find_by_any_email).and_call_original
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
key = "commit_author:#{commit.author_email}" key = "Commit:author:#{commit.author_email.downcase}"
expect(RequestStore.store[key]).to eq(user) expect(RequestStore.store[key]).to eq(user)
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
RequestStore.store.clear
end end
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