hybrid_memcache 0.1.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 +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/hybrid_memcache.gemspec +58 -0
- data/lib/hybrid_memcache.rb +362 -0
- data/test/helper.rb +10 -0
- data/test/memcache_test.rb +233 -0
- data/test/test_helper.rb +22 -0
- data/test/test_hybrid_memcache.rb +7 -0
- metadata +79 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Matthew Knopp
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= hybrid-memcache
|
2
|
+
|
3
|
+
ninjudd-memcache compatible interface on top of fauna-memcached
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Matthew Knopp. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "hybrid_memcache"
|
8
|
+
gem.summary = %Q{ninjudd-memcached compatible interface to fauna-memcached}
|
9
|
+
gem.description = %Q{the summary says it all.}
|
10
|
+
gem.email = "mknopp@yammer-inc.com"
|
11
|
+
gem.homepage = "http://github.com/mhat/hybrid_memcache"
|
12
|
+
gem.authors = ["Matthew Knopp"]
|
13
|
+
|
14
|
+
gem.add_dependency "memcached", ">= 0.18.0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "hybrid_memcache #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{hybrid_memcache}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Matthew Knopp"]
|
12
|
+
s.date = %q{2010-02-17}
|
13
|
+
s.description = %q{the summary says it all.}
|
14
|
+
s.email = %q{mknopp@yammer-inc.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"hybrid_memcache.gemspec",
|
27
|
+
"lib/hybrid_memcache.rb",
|
28
|
+
"test/helper.rb",
|
29
|
+
"test/memcache_test.rb",
|
30
|
+
"test/test_helper.rb",
|
31
|
+
"test/test_hybrid_memcache.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/mhat/hybrid_memcache}
|
34
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.3.5}
|
37
|
+
s.summary = %q{ninjudd-memcached compatible interface to fauna-memcached}
|
38
|
+
s.test_files = [
|
39
|
+
"test/helper.rb",
|
40
|
+
"test/memcache_test.rb",
|
41
|
+
"test/test_helper.rb",
|
42
|
+
"test/test_hybrid_memcache.rb"
|
43
|
+
]
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<memcached>, [">= 0.18.0"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<memcached>, [">= 0.18.0"])
|
53
|
+
end
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<memcached>, [">= 0.18.0"])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,362 @@
|
|
1
|
+
require 'memcached'
|
2
|
+
|
3
|
+
class Memcache < Memcached
|
4
|
+
WRITE_LOCK_WAIT = 1
|
5
|
+
LOCK_TIMEOUT = 5
|
6
|
+
|
7
|
+
def initialize (opts={})
|
8
|
+
servers = opts[:servers] && opts.delete(:servers)
|
9
|
+
@namespace = ""
|
10
|
+
|
11
|
+
super(servers, {
|
12
|
+
:prefix_key => '',
|
13
|
+
:prefix_delimiter => '',
|
14
|
+
:support_cas => true,
|
15
|
+
|
16
|
+
:hash => :fnv1_32,
|
17
|
+
:distribution => :consistent_ketama,
|
18
|
+
:ketama_weighted => true,
|
19
|
+
:server_failure_limit => 2,
|
20
|
+
:retry_timeout => 30,
|
21
|
+
|
22
|
+
:default_ttl => 604800,
|
23
|
+
}.merge!(opts))
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def clone
|
28
|
+
klone = self.class.new({ :servers => servers }.merge(options))
|
29
|
+
klone.set_namespace @namespace
|
30
|
+
klone
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def add(key, value, opts={})
|
35
|
+
super(normalize_keys(key), value, *opts_to_params(opts))
|
36
|
+
return value
|
37
|
+
rescue Memcached::NotStored
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def inspect
|
43
|
+
"<Memcache: %d servers, ns: %p" % [ servers.length, namespace ]
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def in_namespace(ns)
|
48
|
+
# Temporarily change the namespace for convenience.
|
49
|
+
begin
|
50
|
+
old_namespace = @namespace
|
51
|
+
self.set_namespace("#{old_namespace}#{ns}")
|
52
|
+
yield
|
53
|
+
ensure
|
54
|
+
self.set_namespace(old_namespace)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def namespace
|
60
|
+
@namespace
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def set_namespace(ns)
|
65
|
+
@namespace = ns
|
66
|
+
end
|
67
|
+
alias namespace= set_namespace
|
68
|
+
alias set_namespace= set_namespace
|
69
|
+
|
70
|
+
|
71
|
+
def get(keys, opts={})
|
72
|
+
marshal = !opts[:raw]
|
73
|
+
cas = opts[:cas]
|
74
|
+
|
75
|
+
unless keys.is_a?(Array)
|
76
|
+
## ninjudd-memcache has a weird behaviour where get can be called with an
|
77
|
+
## expiry and that will transform the get into get+cas. this in turn has
|
78
|
+
## the effect of extending the expiry of the object.
|
79
|
+
if opts[:expiry]
|
80
|
+
value = get(keys, :cas => true)
|
81
|
+
value = cas(keys, value, :cas => value.memcache_cas, :expiry => opts[:expiry])
|
82
|
+
return value
|
83
|
+
end
|
84
|
+
|
85
|
+
## Single get
|
86
|
+
value, flags, ret = Lib.memcached_get_rvalue(@struct, normalize_keys(keys))
|
87
|
+
|
88
|
+
## ninjudd-memcache treats broken servers as cache missis, so return nil
|
89
|
+
check_return_code(ret, keys)
|
90
|
+
return nil unless ret == 0
|
91
|
+
|
92
|
+
if marshal
|
93
|
+
value = Marshal.load(value)
|
94
|
+
end
|
95
|
+
|
96
|
+
value.memcache_cas = cas ? @struct.result.cas : false
|
97
|
+
value.memcache_flags = flags
|
98
|
+
return value
|
99
|
+
else
|
100
|
+
## Multi get
|
101
|
+
return {} if keys.empty?
|
102
|
+
|
103
|
+
## ninjudd-memcache normalizes keys into the form namespace:index:key
|
104
|
+
## but it hides this form from the caller, so the caller expects to
|
105
|
+
## get a hash with the keys in their denormalized form. That's what
|
106
|
+
## the norm_to_std hash is all about.
|
107
|
+
normalized = normalize_keys(keys)
|
108
|
+
norm_to_std = {}
|
109
|
+
|
110
|
+
## but note, the keys have to be transformed into strings, even if they
|
111
|
+
## started out as fixnums
|
112
|
+
keys.each_with_index {|k,idx| norm_to_std[normalized[idx]] = keys[idx].to_s }
|
113
|
+
|
114
|
+
ret = Lib.memcached_mget(@struct, normalized)
|
115
|
+
|
116
|
+
## once again: potentiall braken server == cache miss
|
117
|
+
check_return_code(ret, normalized)
|
118
|
+
return {} unless ret == 0
|
119
|
+
|
120
|
+
hash = {}
|
121
|
+
keys.each do
|
122
|
+
value, key, flags, ret = Lib.memcached_fetch_rvalue(@struct)
|
123
|
+
if ret == Lib::MEMCACHED_END
|
124
|
+
break
|
125
|
+
end
|
126
|
+
check_return_code(ret, key)
|
127
|
+
|
128
|
+
# Assign the value
|
129
|
+
if marshal
|
130
|
+
value = Marshal.load(value)
|
131
|
+
end
|
132
|
+
value.memcache_cas = cas ? @struct.result.cas : false
|
133
|
+
value.memcache_flags = flags
|
134
|
+
|
135
|
+
hash[ norm_to_std[key] ] = value
|
136
|
+
end
|
137
|
+
return hash
|
138
|
+
end
|
139
|
+
rescue Memcached::NotFound
|
140
|
+
return nil
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def read(keys, opts={})
|
145
|
+
get(keys, opts.merge(:raw => true))
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def set(key, value, opts={})
|
150
|
+
super(normalize_keys(key), value, *opts_to_params(opts))
|
151
|
+
return value
|
152
|
+
rescue Memcache::NotFound
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def write(key, value, opts={})
|
158
|
+
set(key, value, opts.merge(:raw => true))
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
def replace(key, value, opts={})
|
163
|
+
super(normalize_keys(key), value, *opts_to_params(opts))
|
164
|
+
return value
|
165
|
+
rescue Memcache::NotStored
|
166
|
+
return nil
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def cas(key, value, opts={})
|
171
|
+
ttl, marshal, flags = opts_to_params(opts)
|
172
|
+
key = normalize_keys(key)
|
173
|
+
data = marshal ? Marshal.dump(value) : value
|
174
|
+
|
175
|
+
check_return_code(Lib.memcached_cas(@struct, key, data, ttl, flags, opts[:cas]), key)
|
176
|
+
value.memcache_cas = @struct.result.cas
|
177
|
+
value.memcache_flags = @struct.result.flags
|
178
|
+
return value
|
179
|
+
rescue Memcache::NotStored
|
180
|
+
return nil
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def prepend(key,value)
|
185
|
+
super(normalize_keys(key), value)
|
186
|
+
return true
|
187
|
+
rescue Memcache::NotStored
|
188
|
+
return false
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
def append(key, value)
|
193
|
+
super(normalize_keys(key), value)
|
194
|
+
return true
|
195
|
+
rescue Memcache::NotStored
|
196
|
+
return false
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def count(key)
|
201
|
+
get(key, :raw => true).to_i
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
def increment(key, amount=1)
|
206
|
+
super(normalize_keys(key), amount)
|
207
|
+
rescue Memcache::NotStored
|
208
|
+
return nil
|
209
|
+
end
|
210
|
+
alias incr increment
|
211
|
+
|
212
|
+
|
213
|
+
def decrement(key, amount=1)
|
214
|
+
super(normalize_keys(key), amount)
|
215
|
+
rescue Memcache::NotStored
|
216
|
+
return nil
|
217
|
+
end
|
218
|
+
alias decr decrement
|
219
|
+
|
220
|
+
|
221
|
+
def update(key, opts={})
|
222
|
+
if value = get(key, :cas => true)
|
223
|
+
cas(key, yield(value), opts.merge!(:cas => value.memcache_cas))
|
224
|
+
else
|
225
|
+
add(key, yield(value), opts)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
def get_or_add(key, *args)
|
231
|
+
if block_given?
|
232
|
+
opts = args[0] || {}
|
233
|
+
get(key) || add(key, yield, opts) || get(key)
|
234
|
+
else
|
235
|
+
opts = args[1] || {}
|
236
|
+
get(key) || add(key, args[0], opts) || get(key)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
def get_or_set(key, *args)
|
242
|
+
if block_given?
|
243
|
+
opts = args[0] || {}
|
244
|
+
get(key) || set(key, yield, opts) || get(key)
|
245
|
+
else
|
246
|
+
opts = args[1] || {}
|
247
|
+
get(key) || set(key, args[0], opts) || get(key)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
def get_some(keys, opts = {})
|
253
|
+
keys = keys.collect { |k| k.to_s }
|
254
|
+
records = opts[:disable] ? {} : self.get(keys, opts)
|
255
|
+
|
256
|
+
if opts[:validation]
|
257
|
+
records.delete_if do |key, value|
|
258
|
+
not opts[:validation].call(key, value)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
keys_to_fetch = keys - records.keys
|
263
|
+
method = opts[:overwrite] ? :set : :add
|
264
|
+
if keys_to_fetch.any?
|
265
|
+
yield(keys_to_fetch).each do |key, value|
|
266
|
+
self.send(method, key, value, opts) unless opts[:disable] or opts[:disable_write]
|
267
|
+
records[key] = value
|
268
|
+
end
|
269
|
+
end
|
270
|
+
records
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
def lock_key(key)
|
275
|
+
"lock:#{key}"
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
def lock(key, opts={})
|
280
|
+
expiry = opts[:expiry] || LOCK_TIMEOUT
|
281
|
+
add(lock_key(key), Socket.gethostname, :expiry => expiry, :raw => true)
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
def unlock(key)
|
286
|
+
delete(lock_key(key))
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
def with_lock(key, opts={})
|
291
|
+
until lock(key) do
|
292
|
+
return if opts[:ignore]
|
293
|
+
sleep(WRITE_LOCK_WAIT)
|
294
|
+
end
|
295
|
+
yield
|
296
|
+
unlock(key) unless opts[:keep]
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
def locked?(key)
|
301
|
+
get(lock_key(key), :raw => true)
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
def delete(key)
|
306
|
+
super(normalize_keys(key))
|
307
|
+
return true
|
308
|
+
rescue Memcached::NotFound
|
309
|
+
return false
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
def flush_all(opts={})
|
314
|
+
flush
|
315
|
+
end
|
316
|
+
alias clear flush_all
|
317
|
+
|
318
|
+
|
319
|
+
def [](key)
|
320
|
+
get(key)
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
def []=(key,value)
|
325
|
+
set(key,value)
|
326
|
+
end
|
327
|
+
|
328
|
+
|
329
|
+
def normalize_keys (keys)
|
330
|
+
ns = @namespace.nil? || @namespace.size == 0 ? "" : "#{@namespace}:"
|
331
|
+
|
332
|
+
unless keys.is_a?(Array)
|
333
|
+
k = "#{ns}#{keys}"
|
334
|
+
k.gsub!(/%/, '%%') if k.include?('%')
|
335
|
+
k.gsub!(/ /, '%s') if k.include?(' ')
|
336
|
+
return k
|
337
|
+
else
|
338
|
+
return keys.collect do |k|
|
339
|
+
k = "#{ns}#{k}"
|
340
|
+
k.gsub!(/%/, '%%') if k.include?('%')
|
341
|
+
k.gsub!(/ /, '%s') if k.include?(' ')
|
342
|
+
k
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
|
348
|
+
def opts_to_params (opts={})
|
349
|
+
## - if no :expiry, use @default_ttl
|
350
|
+
## - fauna-memcached uses marshal which has the opposite meaning of
|
351
|
+
## ninjudd's :raw. that is, :raw means DO NOT marshal, so invert
|
352
|
+
## :raw via ! ...
|
353
|
+
## - if no :flags, use FLAGS
|
354
|
+
return opts[:expiry] || @default_ttl,
|
355
|
+
!opts[:raw],
|
356
|
+
opts[:flags] || FLAGS
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
class Object
|
361
|
+
attr_accessor :memcache_flags, :memcache_cas
|
362
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
|
4
|
+
class MemcacheTest < Test::Unit::TestCase
|
5
|
+
PORTS = [11212, 11213, 11214, 11215, 11216, 11217]
|
6
|
+
|
7
|
+
def m
|
8
|
+
@memcache
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup
|
12
|
+
start_memcache(*PORTS)
|
13
|
+
@memcache = Memcache.new(:servers => PORTS.collect {|p| "localhost:#{p}"})
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
stop_memcache(*PORTS)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_get_and_set
|
21
|
+
100.times do |i|
|
22
|
+
m.set(i.to_s, i)
|
23
|
+
assert_equal i, m.get(i.to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
keys = (0..200).to_a
|
27
|
+
results = m.get(keys)
|
28
|
+
assert_equal 100, results.size
|
29
|
+
results.each do |key, value|
|
30
|
+
assert_equal key.to_i, value
|
31
|
+
end
|
32
|
+
|
33
|
+
100.times do |i|
|
34
|
+
m.set(i.to_s, i.to_s, :raw => true)
|
35
|
+
assert_equal i.to_s, m.get(i.to_s, :raw => true)
|
36
|
+
end
|
37
|
+
|
38
|
+
results = m.get(keys ,:raw => true)
|
39
|
+
assert_equal 100, results.size
|
40
|
+
results.each do |key, value|
|
41
|
+
assert_equal key, value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_alternate_accessors
|
46
|
+
m['baz'] = 24
|
47
|
+
assert_equal 24, m['baz']
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_add_and_replace
|
51
|
+
100.times do |i|
|
52
|
+
m.replace(i.to_s, [:foo, i])
|
53
|
+
assert_equal nil, m.get(i.to_s)
|
54
|
+
|
55
|
+
m.add(i.to_s, [:bar, i])
|
56
|
+
assert_equal [:bar, i], m.get(i.to_s)
|
57
|
+
|
58
|
+
m.replace(i.to_s, [:foo, i])
|
59
|
+
assert_equal [:foo, i], m.get(i.to_s)
|
60
|
+
|
61
|
+
m.add(i.to_s, [:baz, i])
|
62
|
+
assert_equal [:foo, i], m.get(i.to_s)
|
63
|
+
|
64
|
+
m.replace(i.to_s, 'blah', :raw => true)
|
65
|
+
assert_equal 'blah', m.get(i.to_s, :raw => true)
|
66
|
+
|
67
|
+
m.delete(i.to_s)
|
68
|
+
assert_equal nil, m.get(i.to_s, :raw => true)
|
69
|
+
|
70
|
+
m.add(i.to_s, 'homerun', :raw => true)
|
71
|
+
assert_equal 'homerun', m.get(i.to_s, :raw => true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_append_and_prepend
|
76
|
+
100.times do |i|
|
77
|
+
m.append(i.to_s, 'doh!')
|
78
|
+
assert_equal nil, m.read(i.to_s)
|
79
|
+
|
80
|
+
m.write(i.to_s, 'bar')
|
81
|
+
m.prepend(i.to_s, 'foo')
|
82
|
+
assert_equal 'foobar', m.read(i.to_s)
|
83
|
+
|
84
|
+
m.append(i.to_s, i.to_s)
|
85
|
+
assert_equal "foobar#{i}", m.read(i.to_s)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_get_or_set
|
90
|
+
100.times do |i|
|
91
|
+
m.get_or_set("foo#{i}", [i, :foo])
|
92
|
+
assert_equal [i, :foo], m["foo#{i}"]
|
93
|
+
|
94
|
+
m.get_or_set("foo#{i}") {raise}
|
95
|
+
assert_equal [i, :foo], m["foo#{i}"]
|
96
|
+
|
97
|
+
# Overwrite if changed.
|
98
|
+
m.get_or_set("bar#{i}") do
|
99
|
+
m.set("bar#{i}", [i, :foo])
|
100
|
+
[i, :bar]
|
101
|
+
end
|
102
|
+
assert_equal [i, :bar], m["bar#{i}"]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_get_or_add
|
107
|
+
100.times do |i|
|
108
|
+
m.get_or_add("foo#{i}", [:foo, i])
|
109
|
+
assert_equal [:foo, i], m["foo#{i}"]
|
110
|
+
|
111
|
+
m.get_or_add("foo#{i}") {raise}
|
112
|
+
assert_equal [:foo, i], m["foo#{i}"]
|
113
|
+
|
114
|
+
# Don't overwrite if changed.
|
115
|
+
m.get_or_add("bar#{i}") do
|
116
|
+
m.set("bar#{i}", [:foo, i])
|
117
|
+
:bar
|
118
|
+
end
|
119
|
+
assert_equal [:foo, i], m["bar#{i}"]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_update
|
124
|
+
100.times do |i|
|
125
|
+
m.set("foo#{i}", [:foo, i])
|
126
|
+
assert_equal [:foo, i], m["foo#{i}"]
|
127
|
+
|
128
|
+
m.update("foo#{i}") do |list|
|
129
|
+
list << i.to_s
|
130
|
+
list << :bar
|
131
|
+
list
|
132
|
+
end
|
133
|
+
assert_equal [:foo, i, i.to_s, :bar], m["foo#{i}"]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_get_some
|
138
|
+
100.times do |i|
|
139
|
+
i = i * 2
|
140
|
+
m.set(i.to_s, i)
|
141
|
+
assert_equal i, m.get(i.to_s)
|
142
|
+
end
|
143
|
+
|
144
|
+
keys = (0...200).to_a
|
145
|
+
results = m.get_some(keys) do |missing_keys|
|
146
|
+
assert_equal 100, missing_keys.size
|
147
|
+
r = {}
|
148
|
+
missing_keys.each do |key|
|
149
|
+
r[key] = key.to_i
|
150
|
+
end
|
151
|
+
r
|
152
|
+
end
|
153
|
+
|
154
|
+
assert_equal 200, results.size
|
155
|
+
results.each do |key, value|
|
156
|
+
assert_equal key.to_i, value
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_get_with_reset_expiry
|
161
|
+
m.add('foo', 'quick brown fox', :expiry => 1)
|
162
|
+
assert_equal 'quick brown fox', m.get('foo', :expiry => 2)
|
163
|
+
sleep(1)
|
164
|
+
assert_equal 'quick brown fox', m.get('foo')
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_expiry
|
168
|
+
100.times do |i|
|
169
|
+
m.set("int#{i}", i, :expiry => 1)
|
170
|
+
assert_equal i, m.get("int#{i}")
|
171
|
+
|
172
|
+
m.set("time#{i}", i, :expiry => Time.now + 1)
|
173
|
+
assert_equal i, m.get("time#{i}")
|
174
|
+
end
|
175
|
+
|
176
|
+
sleep 2
|
177
|
+
|
178
|
+
100.times do |i|
|
179
|
+
assert_equal nil, m.get("int#{i}")
|
180
|
+
assert_equal nil, m.get("time#{i}")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_in_namespace
|
185
|
+
threads = []
|
186
|
+
10.times do |i|
|
187
|
+
m.in_namespace("_#{i}_") do
|
188
|
+
10.times do |j|
|
189
|
+
m.in_namespace("_#{j}_") do
|
190
|
+
assert_equal nil, m.get('foo')
|
191
|
+
m.set('foo', 'bar')
|
192
|
+
assert_equal 'bar', m.get('foo')
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_incr_and_decr
|
200
|
+
m.write('foo', 0)
|
201
|
+
|
202
|
+
m.incr('foo', 100)
|
203
|
+
assert_equal 100, m.count('foo')
|
204
|
+
|
205
|
+
m.decr('foo', 100)
|
206
|
+
assert_equal 0, m.count('foo')
|
207
|
+
|
208
|
+
m.incr('foo', 500)
|
209
|
+
assert_equal 500, m.count('foo')
|
210
|
+
|
211
|
+
m.decr('foo', 300)
|
212
|
+
assert_equal 200, m.count('foo')
|
213
|
+
|
214
|
+
m.decr('foo', 300)
|
215
|
+
assert_equal 0, m.count('foo')
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_flags
|
219
|
+
m.set('foo', :foo, :flags => 43)
|
220
|
+
assert_equal 43, m.get('foo').memcache_flags
|
221
|
+
|
222
|
+
m.set('foo', 'foo', :raw => true, :flags => 43)
|
223
|
+
assert_equal 43, m.get('foo', :raw => true).memcache_flags
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_clone
|
227
|
+
m.set('foo', 1)
|
228
|
+
c = m.clone
|
229
|
+
|
230
|
+
#assert_not_equal m.servers.collect {|s| s.send(:socket)},
|
231
|
+
# c.servers.collect {|s| s.send(:socket)}
|
232
|
+
end
|
233
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'hybrid_memcache'
|
4
|
+
|
5
|
+
system 'killall memcached 2> /dev/null'
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
def start_memcache(*ports)
|
9
|
+
ports.each do |port|
|
10
|
+
system("memcached -p #{port} -U 0 -d -P /tmp/memcached_#{port}.pid")
|
11
|
+
end
|
12
|
+
sleep 0.1
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop_memcache(*ports)
|
16
|
+
ports.each do |port|
|
17
|
+
pid = File.read("/tmp/memcached_#{port}.pid").to_i
|
18
|
+
Process.kill('TERM', pid)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hybrid_memcache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Knopp
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-17 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: memcached
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.18.0
|
24
|
+
version:
|
25
|
+
description: the summary says it all.
|
26
|
+
email: mknopp@yammer-inc.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- hybrid_memcache.gemspec
|
42
|
+
- lib/hybrid_memcache.rb
|
43
|
+
- test/helper.rb
|
44
|
+
- test/memcache_test.rb
|
45
|
+
- test/test_helper.rb
|
46
|
+
- test/test_hybrid_memcache.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/mhat/hybrid_memcache
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.3.5
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: ninjudd-memcached compatible interface to fauna-memcached
|
75
|
+
test_files:
|
76
|
+
- test/helper.rb
|
77
|
+
- test/memcache_test.rb
|
78
|
+
- test/test_helper.rb
|
79
|
+
- test/test_hybrid_memcache.rb
|