tokyo_store 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +51 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/benchmark/cache.rb +134 -0
- data/benchmark/session.rb +75 -0
- data/lib/cache/tokyo_store.rb +139 -0
- data/lib/rack/cache/tokyo_entitystore.rb +0 -0
- data/lib/rack/cache/tokyo_metastore.rb +0 -0
- data/lib/rack/session/cabinet.rb +70 -0
- data/lib/rack/session/rufus_tyrant.rb +81 -0
- data/lib/rack/session/tyrant.rb +70 -0
- data/lib/tokyo_store.rb +16 -0
- data/spec/cache/tokyo_store_spec.rb +256 -0
- data/spec/rack/cache/tokyo_spec.rb +0 -0
- data/spec/rack/session/cabinet_spec.rb +242 -0
- data/spec/rack/session/rufus_tyrant_spec.rb +246 -0
- data/spec/rack/session/tyrant_spec.rb +249 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/tokyo_store_spec.rb +43 -0
- data/tokyo_store.gemspec +65 -0
- metadata +85 -0
File without changes
|
File without changes
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'tokyocabinet'
|
2
|
+
module Rack
|
3
|
+
module Session
|
4
|
+
class Cabinet < Abstract::ID
|
5
|
+
include TokyoCabinet
|
6
|
+
attr_reader :mutex, :pool
|
7
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :cabinet_file => "/tmp/session.tch"
|
8
|
+
|
9
|
+
def initialize(app, options = {})
|
10
|
+
super
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@db = options[:cabinet_file] || @default_options[:cabinet_file]
|
13
|
+
tokyo_connect
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def tokyo_connect
|
18
|
+
@pool = HDB.new
|
19
|
+
unless @pool.open(@db, HDB::OREADER | HDB::OWRITER | HDB::OCREAT)
|
20
|
+
warn "Can't open db file '#{@db}', #{@pool.errmsg}."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_session(env, sid)
|
25
|
+
session = Marshal.load(@pool.get(sid)) rescue session if sid && session = @pool.get(sid)
|
26
|
+
@mutex.lock if env['rack.multithread']
|
27
|
+
unless sid && session
|
28
|
+
env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
|
29
|
+
session = {}
|
30
|
+
sid = generate_sid
|
31
|
+
ret = @pool.put(sid, Marshal.dump(session))
|
32
|
+
raise "Session collision on '#{sid.inspect}'" unless ret
|
33
|
+
end
|
34
|
+
return [sid, session]
|
35
|
+
rescue Rufus::Tokyo::TokyoError => e
|
36
|
+
return [nil, {}]
|
37
|
+
ensure
|
38
|
+
@mutex.unlock if env['rack.multithread']
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_session(env, sid, new_session, options)
|
42
|
+
@mutex.lock if env['rack.multithread']
|
43
|
+
session = Marshal.load(session) rescue session if session = @pool.get(sid)
|
44
|
+
if options[:renew] || options[:drop]
|
45
|
+
@pool.out(sid)
|
46
|
+
return false if options[:drop]
|
47
|
+
sid = generate_sid
|
48
|
+
@pool.put(sid, "")
|
49
|
+
end
|
50
|
+
@pool.put(sid, options && options[:raw] ? new_session : Marshal.dump(new_session))
|
51
|
+
return sid
|
52
|
+
rescue Rufus::Tokyo::TokyoError => e
|
53
|
+
warn "#{self} is unable to find server, error: #{e}"
|
54
|
+
warn $!.inspect
|
55
|
+
ensure
|
56
|
+
@mutex.unlock if env['rack.multithread']
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_sid
|
60
|
+
loop do
|
61
|
+
sid = super
|
62
|
+
break sid unless @pool.get(sid)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'rufus/tokyo/tyrant'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Session
|
5
|
+
class RufusTyrant < Abstract::ID
|
6
|
+
attr_reader :mutex, :pool
|
7
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :tyrant_server => "localhost:1978"
|
8
|
+
|
9
|
+
def initialize(app, options = {})
|
10
|
+
super
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@host, @port = *(options[:tyrant_server] || @default_options[:tyrant_server]).split(":") # @default_options) #options[:cache] ||
|
13
|
+
# connecting & closing on each get and put
|
14
|
+
# not sure if this is the best option, but otherwise it'll keep
|
15
|
+
# opening connections until tyrant freezes... =/
|
16
|
+
tokyo_connect
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def tokyo_connect
|
21
|
+
begin
|
22
|
+
@pool ||= Rufus::Tokyo::Tyrant.new(@host, @port.to_i)
|
23
|
+
rescue Rufus::Tokyo::TokyoError => e
|
24
|
+
warn "Can't connect to Tyrant #{e}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_session(env, sid)
|
29
|
+
# tokyo_connect
|
30
|
+
session = Marshal.load(@pool[sid]) rescue session if sid && session = @pool[sid]
|
31
|
+
@mutex.lock if env['rack.multithread']
|
32
|
+
unless sid && session
|
33
|
+
env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
|
34
|
+
session = {}
|
35
|
+
sid = generate_sid
|
36
|
+
ret = @pool[sid] = Marshal.dump(session)
|
37
|
+
raise "Session collision on '#{sid.inspect}'" unless ret
|
38
|
+
end
|
39
|
+
session.instance_variable_set('@old', {}.merge(session))
|
40
|
+
return [sid, session]
|
41
|
+
rescue Rufus::Tokyo::TokyoError => e
|
42
|
+
return [nil, {}]
|
43
|
+
ensure
|
44
|
+
@mutex.unlock if env['rack.multithread']
|
45
|
+
# @pool.close
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_session(env, sid, new_session, options)
|
49
|
+
# tokyo_connect
|
50
|
+
@mutex.lock if env['rack.multithread']
|
51
|
+
session = Marshal.load(session) rescue session if session = @pool[sid]
|
52
|
+
if options[:renew] || options[:drop]
|
53
|
+
@pool.delete sid
|
54
|
+
return false if options[:drop]
|
55
|
+
sid = generate_sid
|
56
|
+
@pool[sid] = ""
|
57
|
+
end
|
58
|
+
old_session = new_session.instance_variable_get('@old') || {}
|
59
|
+
session = new_session
|
60
|
+
@pool[sid] = options && options[:raw] ? session : Marshal.dump(session)
|
61
|
+
return sid
|
62
|
+
rescue Rufus::Tokyo::TokyoError => e
|
63
|
+
warn "#{self} is unable to find server, error: #{e}"
|
64
|
+
warn $!.inspect
|
65
|
+
ensure
|
66
|
+
@mutex.unlock if env['rack.multithread']
|
67
|
+
# @pool.close
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_sid
|
71
|
+
loop do
|
72
|
+
sid = super
|
73
|
+
break sid unless @pool[sid]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'tokyotyrant'
|
2
|
+
module Rack
|
3
|
+
module Session
|
4
|
+
class Tyrant < Abstract::ID
|
5
|
+
include TokyoTyrant
|
6
|
+
attr_reader :mutex, :pool
|
7
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :tyrant_server => "localhost:1978"
|
8
|
+
|
9
|
+
def initialize(app, options = {})
|
10
|
+
super
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@host, @port = *(options[:tyrant_server] || @default_options[:tyrant_server]).split(":")
|
13
|
+
tokyo_connect
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def tokyo_connect
|
18
|
+
@pool = RDB.new
|
19
|
+
unless @pool.open(@host, @port.to_i)
|
20
|
+
warn "Can't connect to Tyrant #{@host}:#{@port}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_session(env, sid)
|
25
|
+
session = Marshal.load(@pool.get(sid)) rescue session if sid && session = @pool.get(sid)
|
26
|
+
@mutex.lock if env['rack.multithread']
|
27
|
+
unless sid && session
|
28
|
+
env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
|
29
|
+
session = {}
|
30
|
+
sid = generate_sid
|
31
|
+
ret = @pool.put(sid, Marshal.dump(session))
|
32
|
+
raise "Session collision on '#{sid.inspect}'" unless ret
|
33
|
+
end
|
34
|
+
return [sid, session]
|
35
|
+
rescue Rufus::Tokyo::TokyoError => e
|
36
|
+
return [nil, {}]
|
37
|
+
ensure
|
38
|
+
@mutex.unlock if env['rack.multithread']
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_session(env, sid, new_session, options)
|
42
|
+
@mutex.lock if env['rack.multithread']
|
43
|
+
session = Marshal.load(session) rescue session if session = @pool.get(sid)
|
44
|
+
if options[:renew] || options[:drop]
|
45
|
+
@pool.delete sid
|
46
|
+
return false if options[:drop]
|
47
|
+
sid = generate_sid
|
48
|
+
@pool.put(sid, "")
|
49
|
+
end
|
50
|
+
@pool.put(sid, options && options[:raw] ? new_session : Marshal.dump(new_session))
|
51
|
+
return sid
|
52
|
+
rescue Rufus::Tokyo::TokyoError => e
|
53
|
+
warn "#{self} is unable to find server, error: #{e}"
|
54
|
+
warn $!.inspect
|
55
|
+
ensure
|
56
|
+
@mutex.unlock if env['rack.multithread']
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_sid
|
60
|
+
loop do
|
61
|
+
sid = super
|
62
|
+
break sid unless @pool.get(sid)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/tokyo_store.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
# Rack Session
|
3
|
+
if defined?(Rack::Session)
|
4
|
+
require "rack/session/abstract/id"
|
5
|
+
require "rack/session/tyrant"
|
6
|
+
#require "rack/session/cabinet"
|
7
|
+
#require "rack/session/rufus_tyrant"
|
8
|
+
end
|
9
|
+
|
10
|
+
# # Cache store
|
11
|
+
# if defined?(Sinatra)
|
12
|
+
# require "cache/sinatra/tokyo_store"
|
13
|
+
# elsif defined?(Rails)
|
14
|
+
# require "cache/rails/tokyo_store"
|
15
|
+
# end
|
16
|
+
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "TokyoStore" do
|
4
|
+
it "should store fragment cache" do
|
5
|
+
Rufus::Tokyo::Tyrant.should_receive(:new).and_return(@mock_tyrant = mock("Tyrant"))
|
6
|
+
store = ActiveSupport::Cache.lookup_store :tokyo_store, "data.tch"
|
7
|
+
store.should be_kind_of ActiveSupport::Cache::TokyoStore
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should fail" do
|
11
|
+
tokyo = Rufus::Tokyo::Tyrant.new('localhost', 1978)
|
12
|
+
Rufus::Tokyo::Tyrant.should_not_receive(:new)
|
13
|
+
store = ActiveSupport::Cache.lookup_store :tokyo_store, tokyo
|
14
|
+
store.should be_kind_of ActiveSupport::Cache::TokyoStore
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Similar" do
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
@cache = ActiveSupport::Cache::TokyoStore.new 'localhost:1978'
|
21
|
+
@cache.clear
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return true on success" do
|
25
|
+
@cache.write('foo', 'bar').should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should read and write strings" do
|
29
|
+
@cache.write('foo', 'bar')
|
30
|
+
@cache.read('foo').should eql('bar')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should read and write hash" do
|
34
|
+
@cache.write('foo', {:a => "b"})
|
35
|
+
@cache.read('foo').should eql({:a => "b"})
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should write integers" do
|
39
|
+
@cache.write('foo', 1)
|
40
|
+
@cache.read('foo').should eql(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should write nil" do
|
44
|
+
@cache.write('foo', nil)
|
45
|
+
@cache.read('foo').should eql(nil)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should have a cache miss block" do
|
49
|
+
@cache.write('foo', 'bar')
|
50
|
+
@cache.fetch('foo') { 'baz' }.should eql('bar')
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should have a cache miss block" do
|
54
|
+
@cache.fetch('foo') { 'baz' }.should eql('baz')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should have a forced cache miss block" do
|
58
|
+
@cache.fetch('foo', :force => true).should be_nil
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should read and write hash" do
|
62
|
+
@cache.write('foo', {:a => "b", :c => "d"})
|
63
|
+
@cache.read('foo').should eql({:a => "b", :c => "d"})
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should read and write array" do
|
67
|
+
@cache.write('foo', [1,2,3])
|
68
|
+
@cache.read('foo').should eql([1,2,3])
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should read and write obj" do
|
72
|
+
obj = City.new; obj.name = "Acapulco"; obj.pop = 717766
|
73
|
+
@cache.write('foo', obj)
|
74
|
+
@cache.read('foo').should be_instance_of City
|
75
|
+
@cache.read('foo').name.should eql("Acapulco")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should read multiples" do
|
79
|
+
@cache.write('a', 1)
|
80
|
+
@cache.write('b', 2)
|
81
|
+
@cache.read_multi('a','b').should eql({ 'a' => 1, 'b' => 2})
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should clear all" do
|
85
|
+
@cache.write("erase_me", 1).should be_true
|
86
|
+
@cache.delete("erase_me")
|
87
|
+
@cache.exist?("erase_me").should be_false
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should check if exists" do
|
91
|
+
@cache.exist?("new_one").should be_false
|
92
|
+
@cache.write("new_one", 1)
|
93
|
+
@cache.exist?("new_one").should be_true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should increment value" do
|
97
|
+
@cache.write('val', 1, :raw => true)
|
98
|
+
@cache.read("val", :raw => true).to_i.should eql 1
|
99
|
+
@cache.increment('val')
|
100
|
+
@cache.read("val", :raw => true).to_i.should eql 2
|
101
|
+
@cache.increment('val')
|
102
|
+
@cache.read("val", :raw => true).to_i.should eql 3
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should decrement value" do
|
106
|
+
@cache.write('val', 3, :raw => true)
|
107
|
+
@cache.read("val", :raw => true).to_i.should eql 3
|
108
|
+
@cache.decrement('val')
|
109
|
+
@cache.read("val", :raw => true).to_i.should eql 2
|
110
|
+
@cache.decrement('val')
|
111
|
+
@cache.read("val", :raw => true).to_i.should eql 1
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should clear all" do
|
115
|
+
@cache.increment("val")
|
116
|
+
@cache.exist?("val", :raw => true).should be_true
|
117
|
+
@cache.clear
|
118
|
+
@cache.exist?("val").should be_false
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should show some stats" do
|
122
|
+
@cache.stats.should be_instance_of Hash #== hash_including({ :type => "hash"})
|
123
|
+
end
|
124
|
+
|
125
|
+
it "store objects should be immutable" do
|
126
|
+
@cache.with_local_cache do
|
127
|
+
@cache.write('foo', 'bar')
|
128
|
+
@cache.read('foo').gsub!(/.*/, 'baz')# }.should raise_error(ActiveSupport::FrozenObjectError)
|
129
|
+
@cache.read('foo').should == 'bar'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "stored objects should not be frozen" do
|
134
|
+
pending "It's on the rails tests..."
|
135
|
+
@cache.with_local_cache do
|
136
|
+
@cache.write('foo', 'bar')
|
137
|
+
end
|
138
|
+
@cache.with_local_cache do
|
139
|
+
@cache.read('foo').should_not be_frozen
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should delete matched" do
|
144
|
+
["val", "value", "vall", "val/1", "vla", "xla", "xla/1"].each do |v|
|
145
|
+
@cache.write(v, 1)
|
146
|
+
end
|
147
|
+
|
148
|
+
@cache.delete_matched('val')
|
149
|
+
["val", "value", "vall", "val/1"].each { |v| @cache.read(v).should be_nil }
|
150
|
+
["vla", "xla", "xla/1"].each { |v| @cache.read(v).should eql(1) }
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "backed store" do
|
156
|
+
before(:each) do
|
157
|
+
@cache = ActiveSupport::Cache.lookup_store(:tokyo_store)
|
158
|
+
@data = @cache.instance_variable_get(:@data)
|
159
|
+
@cache.clear
|
160
|
+
end
|
161
|
+
|
162
|
+
it "local_writes_are_persistent_on_the_remote_cache" do
|
163
|
+
@cache.with_local_cache do
|
164
|
+
@cache.write('foo', 'bar')
|
165
|
+
end
|
166
|
+
|
167
|
+
@cache.read('foo').should eql('bar')
|
168
|
+
end
|
169
|
+
|
170
|
+
it "test_clear_also_clears_local_cache" do
|
171
|
+
@cache.with_local_cache do
|
172
|
+
@cache.write('foo', 'bar')
|
173
|
+
@cache.clear
|
174
|
+
@cache.read('foo').should be_nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it "test_local_cache_of_read_and_write" do
|
179
|
+
@cache.with_local_cache do
|
180
|
+
@cache.write('foo', 'bar')
|
181
|
+
@data.clear # Clear remote cache
|
182
|
+
@cache.read('foo').should eql('bar')
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "test_local_cache_should_read_and_write_integer" do
|
187
|
+
@cache.with_local_cache do
|
188
|
+
@cache.write('foo', 1)
|
189
|
+
@cache.read('foo').should eql(1)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
it "test_local_cache_of_delete" do
|
194
|
+
@cache.with_local_cache do
|
195
|
+
@cache.write('foo', 'bar')
|
196
|
+
@cache.delete('foo')
|
197
|
+
@data.clear # Clear remote cache
|
198
|
+
@cache.read('foo').should be_nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it "test_local_cache_of_exist" do
|
203
|
+
@cache.with_local_cache do
|
204
|
+
@cache.write('foo', 'bar')
|
205
|
+
@cache.instance_variable_set(:@data, nil)
|
206
|
+
@data.clear # Clear remote cache
|
207
|
+
@cache.exist?('foo').should be_true
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
it "test_local_cache_of_increment" do
|
212
|
+
@cache.with_local_cache do
|
213
|
+
@cache.write('foo', 1, :raw => true)
|
214
|
+
@cache.increment('foo')
|
215
|
+
@data.clear # Clear remote cache
|
216
|
+
@cache.read('foo', :raw => true).to_i.should eql(2)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
it "test_local_cache_of_decrement" do
|
221
|
+
@cache.with_local_cache do
|
222
|
+
@cache.write('foo', 1, :raw => true)
|
223
|
+
@cache.decrement('foo')
|
224
|
+
@data.clear # Clear remote cache
|
225
|
+
@cache.read('foo', :raw => true).to_i.should be_zero
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
it "test_exist_with_nulls_cached_locally" do
|
230
|
+
@cache.with_local_cache do
|
231
|
+
@cache.write('foo', 'bar')
|
232
|
+
@cache.delete('foo')
|
233
|
+
@cache.exist?('foo').should be_false
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
it "test_multi_get" do
|
238
|
+
@cache.with_local_cache do
|
239
|
+
@cache.write('foo', 1)
|
240
|
+
@cache.write('goo', 2)
|
241
|
+
@cache.read_multi('foo', 'goo').should eql({'foo' => 1, 'goo' => 2})
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
it "test_middleware" do
|
246
|
+
app = lambda { |env|
|
247
|
+
result = @cache.write('foo', 'bar')
|
248
|
+
@cache.read('foo').should eql('bar') # make sure 'foo' was written
|
249
|
+
}
|
250
|
+
app = @cache.middleware.new(app)
|
251
|
+
app.call({})
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|