tokyo_store 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|