Skip to content

Commit 1bb483c

Browse files
author
root
committed
Add Memcached adapter for GitCache
1 parent fe1b6da commit 1bb483c

File tree

13 files changed

+194
-48
lines changed

13 files changed

+194
-48
lines changed

Diff for: Gemfile

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ gem 'gitlab-grack', git: 'https://github.com/n-rodriguez/grack.git', require: 'g
99
# HAML views
1010
gem 'haml-rails'
1111

12+
# Memcached client for GitCache
13+
gem 'dalli'
14+
1215
# Syntaxic coloration
1316
gem 'github-markup'
1417
gem 'redcarpet', '~> 2.3.0'

Diff for: app/helpers/gitolite_plugin_settings_helper.rb

+8
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ def git_cache_options
7979
end
8080

8181

82+
def git_cache_adapters
83+
[
84+
['Database', 'database'],
85+
['Memcached', 'memcached']
86+
]
87+
end
88+
89+
8290
def log_level_options
8391
[
8492
[l(:label_debug), 'debug'],

Diff for: app/services/gitolite_accessor.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def purge_trash_bin(repositories)
7676

7777
def flush_git_cache
7878
logger.info('Flush Git Cache !')
79-
ActiveRecord::Base.connection.execute('TRUNCATE git_caches')
79+
RedmineGitHosting::Cache.flush_cache!
8080
end
8181

8282

Diff for: app/views/settings/_gitolite_config_cache.html.haml

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- gitolite_cache_max_time = RedmineGitHosting::Config.get_setting(:gitolite_cache_max_time)
33
- gitolite_cache_max_size = RedmineGitHosting::Config.get_setting(:gitolite_cache_max_size)
44
- gitolite_cache_max_elements = RedmineGitHosting::Config.get_setting(:gitolite_cache_max_elements)
5+
- gitolite_cache_adapter = RedmineGitHosting::Config.get_setting(:gitolite_cache_adapter)
56

67
%h3= l(:label_gitolite_cache_config)
78

@@ -20,3 +21,8 @@
2021
%label= l(:label_gitolite_cache_max_elements)
2122
= text_field_tag 'settings[gitolite_cache_max_elements]', gitolite_cache_max_elements, size: 20
2223
%br
24+
25+
%p
26+
%label= l(:label_gitolite_cache_adapter)
27+
= select_tag 'settings[gitolite_cache_adapter]', options_for_select(git_cache_adapters, gitolite_cache_adapter)
28+
%br

Diff for: config/locales/settings/en.yml

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ en:
100100
label_gitolite_cache_max_time: Max cache time
101101
label_gitolite_cache_max_size: Max cache element size
102102
label_gitolite_cache_max_elements: Max cache elements
103+
label_gitolite_cache_adapter: Cache Adapter
103104

104105
# Gitolite Notifications
105106
label_tab_notify: Notifications

Diff for: config/locales/settings/fr.yml

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ fr:
100100
label_gitolite_cache_max_time: Temps maximal du cache
101101
label_gitolite_cache_max_size: Taille maximale d'un élément du cache
102102
label_gitolite_cache_max_elements: Nombre maximal d'éléments dans le cache
103+
label_gitolite_cache_adapter: Cache Adapter
103104

104105
# Gitolite Notifications
105106
label_tab_notify: Notifications

Diff for: init.rb

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
:gitolite_cache_max_time => '86400',
5151
:gitolite_cache_max_size => '16',
5252
:gitolite_cache_max_elements => '2000',
53+
:gitolite_cache_adapter => 'database',
5354

5455
# Gitolite Access Config
5556
:ssh_server_domain => 'localhost',

Diff for: lib/redmine_git_hosting/cache.rb

+8-36
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,6 @@ module Cache
33

44
class << self
55

6-
def max_cache_time
7-
RedmineGitHosting::Config.gitolite_cache_max_time
8-
end
9-
10-
11-
def max_cache_elements
12-
RedmineGitHosting::Config.gitolite_cache_max_elements
13-
end
14-
15-
166
# Used in ShellRedirector but define here to keep a clean interface.
177
def max_cache_size
188
RedmineGitHosting::Config.gitolite_cache_max_size
@@ -21,37 +11,26 @@ def max_cache_size
2111

2212
def set_cache(repo_id, out_value, primary_key, secondary_key = nil)
2313
command = compose_key(primary_key, secondary_key)
24-
adapter.apply_cache_limit(max_cache_elements) if adapter.set_cache(command, out_value, repo_id)
14+
adapter.apply_cache_limit if adapter.set_cache(command, out_value, repo_id)
2515
end
2616

2717

2818
def get_cache(primary_key, secondary_key = nil)
2919
command = compose_key(primary_key, secondary_key)
3020
cached = adapter.get_cache(command)
21+
# Return result as a string stream
22+
cached.nil? ? nil : StringIO.new(cached)
23+
end
3124

32-
if cached
33-
if valid_cache_entry?(cached.created_at)
34-
# Update updated_at flag
35-
cached.touch unless cached.command_output.nil?
36-
out = cached.command_output
37-
else
38-
cached.destroy
39-
out = nil
40-
end
41-
else
42-
out = nil
43-
end
4425

45-
# Return result as a string stream
46-
out.nil? ? nil : StringIO.new(out)
26+
def flush_cache!
27+
adapter.flush_cache!
4728
end
4829

4930

5031
# After resetting cache timing parameters -- delete entries that no-longer match
5132
def clear_obsolete_cache_entries
52-
return if max_cache_time < 0 # No expiration needed
53-
limit = Time.now - max_cache_time
54-
adapter.clear_obsolete_cache_entries(limit)
33+
adapter.clear_obsolete_cache_entries
5534
end
5635

5736

@@ -62,20 +41,13 @@ def clear_cache_for_repository(repo_id)
6241

6342

6443
def adapter
65-
@adapter ||= Cache::Adapter.factory
44+
Cache::Adapter.factory
6645
end
6746

6847

6948
private
7049

7150

72-
def valid_cache_entry?(cached_entry_date)
73-
current_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
74-
expired = (current_time.to_i - cached_entry_date.to_i > max_cache_time)
75-
(!expired || max_cache_time < 0) ? true : false
76-
end
77-
78-
7951
def compose_key(key1, key2)
8052
if key2 && !key2.blank?
8153
key1 + "\n" + key2

Diff for: lib/redmine_git_hosting/cache/abstract_cache.rb

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
module RedmineGitHosting::Cache
22
class AbstractCache
33

4+
attr_reader :max_cache_size
5+
attr_reader :max_cache_time
6+
attr_reader :max_cache_elements
7+
8+
9+
def initialize
10+
@max_cache_size = RedmineGitHosting::Config.gitolite_cache_max_size
11+
@max_cache_time = RedmineGitHosting::Config.gitolite_cache_max_time
12+
@max_cache_elements = RedmineGitHosting::Config.gitolite_cache_max_elements
13+
end
14+
15+
416
def set_cache(command, output, repo_id)
517
raise NotImplementedError
618
end
@@ -11,7 +23,12 @@ def get_cache(command)
1123
end
1224

1325

14-
def clear_obsolete_cache_entries(limit)
26+
def flush_cache!
27+
raise NotImplementedError
28+
end
29+
30+
31+
def clear_obsolete_cache_entries
1532
raise NotImplementedError
1633
end
1734

@@ -21,7 +38,7 @@ def clear_cache_for_repository(repo_id)
2138
end
2239

2340

24-
def apply_cache_limit(max_cache_elements)
41+
def apply_cache_limit
2542
raise NotImplementedError
2643
end
2744

@@ -33,5 +50,19 @@ def logger
3350
RedmineGitHosting.logger
3451
end
3552

53+
54+
def time_limit
55+
return if max_cache_time < 0 # No expiration needed
56+
current_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
57+
limit = current_time - max_cache_time
58+
end
59+
60+
61+
def valid_cache_entry?(cached_entry_date)
62+
current_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
63+
expired = (current_time.to_i - cached_entry_date.to_i > max_cache_time)
64+
(!expired || max_cache_time < 0) ? true : false
65+
end
66+
3667
end
3768
end

Diff for: lib/redmine_git_hosting/cache/adapter.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ class Adapter
44
class << self
55

66
def factory
7-
Database.new
7+
case RedmineGitHosting::Config.gitolite_cache_adapter
8+
when 'database'
9+
Database.new
10+
when 'memcached'
11+
Memcached.new
12+
end
813
end
914

1015
end

Diff for: lib/redmine_git_hosting/cache/database.rb

+26-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module RedmineGitHosting::Cache
22
class Database < AbstractCache
33

44
def set_cache(command, output, repo_id)
5-
logger.debug("Inserting cache entry for repository '#{repo_id}'")
5+
logger.debug("DB Adapter : inserting cache entry for repository '#{repo_id}'")
66
begin
77
GitCache.create(
88
command: command,
@@ -11,30 +11,48 @@ def set_cache(command, output, repo_id)
1111
)
1212
true
1313
rescue => e
14-
logger.error("Could not insert in cache, this is the error : '#{e.message}'")
14+
logger.error("DB Adapter : could not insert in cache, this is the error : '#{e.message}'")
1515
false
1616
end
1717
end
1818

1919

2020
def get_cache(command)
21-
GitCache.find_by_command(command)
21+
cached = GitCache.find_by_command(command)
22+
if cached
23+
if valid_cache_entry?(cached.created_at)
24+
# Update updated_at flag
25+
cached.touch unless cached.command_output.nil?
26+
out = cached.command_output
27+
else
28+
cached.destroy
29+
out = nil
30+
end
31+
else
32+
out = nil
33+
end
34+
out
35+
end
36+
37+
38+
def flush_cache!
39+
ActiveRecord::Base.connection.execute('TRUNCATE git_caches')
2240
end
2341

2442

25-
def clear_obsolete_cache_entries(limit)
26-
deleted = GitCache.delete_all(['created_at < ?', limit])
27-
logger.info("Removed '#{deleted}' expired cache entries among all repositories")
43+
def clear_obsolete_cache_entries
44+
deleted = GitCache.delete_all(['created_at < ?', time_limit])
45+
logger.info("DB Adapter : removed '#{deleted}' expired cache entries among all repositories")
2846
end
2947

3048

3149
def clear_cache_for_repository(repo_id)
3250
deleted = GitCache.delete_all(['repo_identifier = ?', repo_id])
33-
logger.info("Removed '#{deleted}' expired cache entries for repository '#{repo_id}'")
51+
logger.info("DB Adapter : removed '#{deleted}' expired cache entries for repository '#{repo_id}'")
3452
end
3553

3654

37-
def apply_cache_limit(max_cache_elements)
55+
def apply_cache_limit
3856
GitCache.find(:last, order: 'created_at DESC').destroy if max_cache_elements >= 0 && GitCache.count > max_cache_elements
3957
end
4058

Diff for: lib/redmine_git_hosting/cache/memcached.rb

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
require 'dalli'
2+
require 'digest/sha1'
3+
4+
module RedmineGitHosting::Cache
5+
class Memcached < AbstractCache
6+
7+
def set_cache(command, output, repo_id)
8+
logger.debug("Memcached Adapter : inserting cache entry for repository '#{repo_id}'")
9+
10+
# Create a SHA256 of the Git command as key id
11+
hashed_command = hash_key(command)
12+
13+
begin
14+
create_or_update_repo_references(repo_id, hashed_command)
15+
client.set(hashed_command, output)
16+
true
17+
rescue => e
18+
logger.error("Memcached Adapter : could not insert in cache, this is the error : '#{e.message}'")
19+
false
20+
end
21+
end
22+
23+
24+
def get_cache(command)
25+
client.get(hash_key(command))
26+
end
27+
28+
29+
def flush_cache!
30+
client.flush
31+
end
32+
33+
34+
# Return true, this is done automatically by Memcached with the
35+
# *max_cache_time* params (see below)
36+
#
37+
def clear_obsolete_cache_entries
38+
true
39+
end
40+
41+
42+
def clear_cache_for_repository(repo_id)
43+
# Create a SHA256 of the repo_id as key id
44+
hashed_repo_id = hash_key(repo_id)
45+
# Find repository references in Memcached
46+
repo_references = client.get(hashed_repo_id)
47+
return true if repo_references.nil?
48+
# Delete reference keys
49+
repo_references = repo_references.split(',').select { |r| !r.empty? }
50+
repo_references.map { |key| client.delete(key) }
51+
logger.info("Memcached Adapter : removed '#{repo_references.size}' expired cache entries for repository '#{repo_id}'")
52+
# Reset references count
53+
client.set(hashed_repo_id, '', max_cache_time, raw: true)
54+
end
55+
56+
57+
# Return true. If cache is full, Memcached drop the oldest objects to add new ones.
58+
#
59+
def apply_cache_limit
60+
true
61+
end
62+
63+
64+
private
65+
66+
67+
def create_or_update_repo_references(repo_id, reference)
68+
# Create a SHA256 of the repo_id as key id
69+
hashed_repo_id = hash_key(repo_id)
70+
# Find it in Memcached
71+
repo_references = client.get(hashed_repo_id)
72+
if repo_references.nil?
73+
client.set(hashed_repo_id, reference, max_cache_time, raw: true)
74+
else
75+
client.append(hashed_repo_id, ',' + reference)
76+
end
77+
end
78+
79+
80+
def hash_key(key)
81+
Digest::SHA256.hexdigest(key)
82+
end
83+
84+
85+
def client
86+
@client ||= Dalli::Client.new('localhost:11211', memcached_options)
87+
end
88+
89+
90+
def memcached_options
91+
{ namespace: 'redmine_git_hosting', compress: true, expires_in: max_cache_time, value_max_bytes: max_cache_size }
92+
end
93+
94+
end
95+
end

0 commit comments

Comments
 (0)