segmented-memcache 1.2.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/lib/memcache.rb ADDED
@@ -0,0 +1,409 @@
1
+ require 'zlib'
2
+
3
+ $:.unshift(File.dirname(__FILE__))
4
+ require 'memcache/server'
5
+ require 'memcache/local_server'
6
+ require 'memcache/segmented_server'
7
+
8
+ class Memcache
9
+ DEFAULT_EXPIRY = 0
10
+ LOCK_TIMEOUT = 5
11
+ WRITE_LOCK_WAIT = 1
12
+
13
+ attr_reader :default_expiry, :default_namespace, :servers
14
+
15
+ def initialize(opts)
16
+ @default_expiry = opts[:default_expiry] || DEFAULT_EXPIRY
17
+ @default_namespace = opts[:namespace]
18
+ default_server = opts[:segment_large_values] ? SegmentedServer : Server
19
+
20
+ @servers = (opts[:servers] || [ opts[:server] ]).collect do |server|
21
+ case server
22
+ when Hash
23
+ server = default_server.new(opts.merge(server))
24
+ when String
25
+ host, port = server.split(':')
26
+ server = default_server.new(opts.merge(:host => host, :port => port))
27
+ when Class
28
+ server = server.new
29
+ when :local
30
+ server = Memcache::LocalServer.new
31
+ end
32
+ server
33
+ end
34
+ end
35
+
36
+ def clone
37
+ self.class.new(
38
+ :default_expiry => default_expiry,
39
+ :default_namespace => default_namespace,
40
+ :servers => servers.collect {|s| s.clone}
41
+ )
42
+ end
43
+
44
+ def inspect
45
+ "<Memcache: %d servers, ns: %p>" % [@servers.length, namespace]
46
+ end
47
+
48
+ def namespace
49
+ @namespace || default_namespace
50
+ end
51
+
52
+ def namespace=(namespace)
53
+ if default_namespace == namespace
54
+ @namespace = nil
55
+ else
56
+ @namespace = namespace
57
+ end
58
+ end
59
+
60
+ def in_namespace(namespace)
61
+ # Temporarily change the namespace for convenience.
62
+ begin
63
+ old_namespace = self.namespace
64
+ self.namespace = "#{old_namespace}#{namespace}"
65
+ yield
66
+ ensure
67
+ self.namespace = old_namespace
68
+ end
69
+ end
70
+
71
+ def get(keys, opts = {})
72
+ raise 'opts must be hash' unless opts.kind_of?(Hash)
73
+
74
+ if keys.kind_of?(Array)
75
+ multi_get(keys, opts)
76
+ else
77
+ key = cache_key(keys)
78
+
79
+ if opts[:expiry]
80
+ value = server(key).gets(key)
81
+ server(key).cas(key, value, value.memcache_cas, opts[:expiry]) if value
82
+ else
83
+ value = server(key).get(key, opts[:cas])
84
+ end
85
+ opts[:raw] ? value : unmarshal(value, key)
86
+ end
87
+ end
88
+
89
+ def read(keys, opts = {})
90
+ get(keys, opts.merge(:raw => true))
91
+ end
92
+
93
+ def set(key, value, opts = {})
94
+ opts = compatible_opts(opts)
95
+
96
+ expiry = opts[:expiry] || default_expiry
97
+ flags = opts[:flags] || 0
98
+ key = cache_key(key)
99
+ data = marshal(value, opts)
100
+ server(key).set(key, data, expiry, flags)
101
+ value
102
+ end
103
+
104
+ def write(key, value, opts = {})
105
+ set(key, value, opts.merge(:raw => true))
106
+ end
107
+
108
+ def add(key, value, opts = {})
109
+ opts = compatible_opts(opts)
110
+
111
+ expiry = opts[:expiry] || default_expiry
112
+ flags = opts[:flags] || 0
113
+ key = cache_key(key)
114
+ data = marshal(value, opts)
115
+ server(key).add(key, data, expiry, flags) && value
116
+ end
117
+
118
+ def replace(key, value, opts = {})
119
+ opts = compatible_opts(opts)
120
+
121
+ expiry = opts[:expiry] || default_expiry
122
+ flags = opts[:flags] || 0
123
+ key = cache_key(key)
124
+ data = marshal(value, opts)
125
+ server(key).replace(key, data, expiry, flags) && value
126
+ end
127
+
128
+ def cas(key, value, opts = {})
129
+ raise 'opts must be hash' unless opts.kind_of?(Hash)
130
+
131
+ expiry = opts[:expiry] || default_expiry
132
+ flags = opts[:flags] || 0
133
+ key = cache_key(key)
134
+ data = marshal(value, opts)
135
+ server(key).cas(key, data, opts[:cas], expiry, flags) && value
136
+ end
137
+
138
+ def append(key, value)
139
+ key = cache_key(key)
140
+ server(key).append(key, value)
141
+ end
142
+
143
+ def prepend(key, value)
144
+ key = cache_key(key)
145
+ server(key).prepend(key, value)
146
+ end
147
+
148
+ def count(key)
149
+ key = cache_key(key)
150
+ server(key).get(key).to_i
151
+ end
152
+
153
+ def incr(key, amount = 1)
154
+ key = cache_key(key)
155
+ server(key).incr(key, amount)
156
+ end
157
+
158
+ def decr(key, amount = 1)
159
+ key = cache_key(key)
160
+ server(key).decr(key, amount)
161
+ end
162
+
163
+ def update(key, opts = {})
164
+ value = get(key, :cas => true)
165
+ if value
166
+ cas(key, yield(value), opts.merge!(:cas => value.memcache_cas))
167
+ else
168
+ add(key, yield(value), opts)
169
+ end
170
+ end
171
+
172
+ def get_or_add(key, *args)
173
+ # Pseudo-atomic get and update.
174
+ if block_given?
175
+ opts = args[0] || {}
176
+ get(key) || add(key, yield, opts) || get(key)
177
+ else
178
+ opts = args[1] || {}
179
+ get(key) || add(key, args[0], opts) || get(key)
180
+ end
181
+ end
182
+
183
+ def get_or_set(key, *args)
184
+ if block_given?
185
+ opts = args[0] || {}
186
+ get(key) || set(key, yield, opts)
187
+ else
188
+ opts = args[1] || {}
189
+ get(key) || set(key, args[0], opts)
190
+ end
191
+ end
192
+
193
+ def get_some(keys, opts = {})
194
+ keys = keys.collect {|key| key.to_s}
195
+
196
+ records = opts[:disable] ? {} : self.get(keys, opts)
197
+ if opts[:validation]
198
+ records.delete_if do |key, value|
199
+ not opts[:validation].call(key, value)
200
+ end
201
+ end
202
+
203
+ keys_to_fetch = keys - records.keys
204
+ method = opts[:overwrite] ? :set : :add
205
+ if keys_to_fetch.any?
206
+ yield(keys_to_fetch).each do |key, value|
207
+ self.send(method, key, value, opts) unless opts[:disable] or opts[:disable_write]
208
+ records[key] = value
209
+ end
210
+ end
211
+ records
212
+ end
213
+
214
+ def lock(key, opts = {})
215
+ # Returns false if the lock already exists.
216
+ expiry = opts[:expiry] || LOCK_TIMEOUT
217
+ add(lock_key(key), Socket.gethostname, :expiry => expiry, :raw => true)
218
+ end
219
+
220
+ def unlock(key)
221
+ delete(lock_key(key))
222
+ end
223
+
224
+ def with_lock(key, opts = {})
225
+ until lock(key) do
226
+ return if opts[:ignore]
227
+ sleep(WRITE_LOCK_WAIT) # just wait
228
+ end
229
+ yield
230
+ unlock(key) unless opts[:keep]
231
+ end
232
+
233
+ def lock_key(key)
234
+ "lock:#{key}"
235
+ end
236
+
237
+ def locked?(key)
238
+ get(lock_key(key), :raw => true)
239
+ end
240
+
241
+ def delete(key)
242
+ key = cache_key(key)
243
+ server(key).delete(key)
244
+ end
245
+
246
+ def flush_all(opts = {})
247
+ delay = opts[:delay].to_i
248
+ interval = opts[:interval].to_i
249
+
250
+ servers.each do |server|
251
+ server.flush_all(delay)
252
+ delay += interval
253
+ end
254
+ end
255
+
256
+ def reset
257
+ servers.each {|server| server.close if server.respond_to?(:close)}
258
+ end
259
+
260
+ def stats(field = nil)
261
+ if field
262
+ servers.collect do |server|
263
+ server.stats[field]
264
+ end
265
+ else
266
+ stats = {}
267
+ servers.each do |server|
268
+ stats[server.name] = server.stats
269
+ end
270
+ stats
271
+ end
272
+ end
273
+
274
+ alias clear flush_all
275
+
276
+ def [](key)
277
+ get(key)
278
+ end
279
+
280
+ def []=(key, value)
281
+ set(key, value)
282
+ end
283
+
284
+ def self.init(yaml_file = nil)
285
+ yaml_file = File.join(Rails.root, 'config', 'memcached.yml')
286
+
287
+ if File.exists?(yaml_file)
288
+ yaml = YAML.load_file(yaml_file)
289
+ defaults = (yaml.delete('defaults') || {}).symbolize_keys
290
+ config = (yaml[Rails.env] || {}).symbolize_keys
291
+
292
+ if not config.empty? and not config[:disabled]
293
+ if config[:servers]
294
+ opts = defaults.merge(config.symbolize_keys)
295
+ Object.const_set('CACHE', Memcache.new(opts))
296
+ else
297
+ config.each do |connection, opts|
298
+ opts = defaults.merge(opts.symbolize_keys)
299
+ Memcache.pool[connection] = Memcache.new(opts)
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
305
+
306
+ protected
307
+
308
+ def compatible_opts(opts)
309
+ # Support passing expiry instead of opts. This may be deprecated in the future.
310
+ opts.kind_of?(Hash) ? opts : {:expiry => opts}
311
+ end
312
+
313
+ def multi_get(keys, opts = {})
314
+ return {} if keys.empty?
315
+
316
+ key_to_input_key = {}
317
+ keys_by_server = Hash.new { |h,k| h[k] = [] }
318
+
319
+ # Store keys by servers. Also store a mapping from cache key to input key.
320
+ keys.each do |input_key|
321
+ key = cache_key(input_key)
322
+ server = server(key)
323
+ key_to_input_key[key] = input_key.to_s
324
+ keys_by_server[server] << key
325
+ end
326
+
327
+ # Fetch and combine the results. Also, map the cache keys back to the input keys.
328
+ results = {}
329
+ keys_by_server.each do |server, keys|
330
+ server.get(keys, opts[:cas]).each do |key, value|
331
+ input_key = key_to_input_key[key]
332
+ results[input_key] = opts[:raw] ? value : unmarshal(value, key)
333
+ end
334
+ end
335
+ results
336
+ end
337
+
338
+ def cache_key(key)
339
+ safe_key = key ? key.to_s.gsub(/%/, '%%').gsub(/ /, '%s') : key
340
+ if namespace.nil? then
341
+ safe_key
342
+ else
343
+ "#{namespace}:#{safe_key}"
344
+ end
345
+ end
346
+
347
+ def marshal(value, opts = {})
348
+ opts[:raw] ? value : Marshal.dump(value)
349
+ end
350
+
351
+ def unmarshal(value, key = nil)
352
+ return value if value.nil?
353
+
354
+ object = Marshal.load(value)
355
+ object.memcache_flags = value.memcache_flags
356
+ object.memcache_cas = value.memcache_cas
357
+ object
358
+ rescue Exception => e
359
+ puts "Memcache read error: #{e.class} #{e.to_s} on key '#{key}' while unmarshalling value: #{value}"
360
+ nil
361
+ end
362
+
363
+ def server(key)
364
+ raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
365
+ return servers.first if servers.length == 1
366
+
367
+ hash = (Zlib.crc32(key) >> 16) & 0x7fff
368
+ servers[hash % servers.length]
369
+ end
370
+
371
+ class Pool
372
+ attr_reader :fallback
373
+
374
+ def initialize
375
+ @cache_by_scope = {}
376
+ @cache_by_scope[:default] = Memcache.new(:server => Memcache::LocalServer)
377
+ @fallback = :default
378
+ end
379
+
380
+ def include?(scope)
381
+ @cache_by_scope.include?(scope.to_sym)
382
+ end
383
+
384
+ def fallback=(scope)
385
+ @fallback = scope.to_sym
386
+ end
387
+
388
+ def [](scope)
389
+ @cache_by_scope[scope.to_sym] || @cache_by_scope[fallback]
390
+ end
391
+
392
+ def []=(scope, cache)
393
+ @cache_by_scope[scope.to_sym] = cache
394
+ end
395
+
396
+ def reset
397
+ @cache_by_scope.values.each {|c| c.reset}
398
+ end
399
+ end
400
+
401
+ def self.pool
402
+ @@cache_pool ||= Pool.new
403
+ end
404
+ end
405
+
406
+ # Add flags and cas
407
+ class Object
408
+ attr_accessor :memcache_flags, :memcache_cas
409
+ end
@@ -0,0 +1,68 @@
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{segmented-memcache}
8
+ s.version = "1.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Justin Balthrop, Alessandro Dal Grande, Fabio Segalla"]
12
+ s.date = %q{2009-12-17}
13
+ s.description = %q{Ruby client for memcached supporting advanced protocol features and pluggable architecture.}
14
+ s.email = %q{lab@develon.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION.yml",
25
+ "lib/memcache.rb",
26
+ "lib/memcache/local_server.rb",
27
+ "lib/memcache/migration.rb",
28
+ "lib/memcache/null_server.rb",
29
+ "lib/memcache/pg_server.rb",
30
+ "lib/memcache/segmented_server.rb",
31
+ "lib/memcache/server.rb",
32
+ "segmented-memcache.gemspec",
33
+ "test/memcache_local_server_test.rb",
34
+ "test/memcache_null_server_test.rb",
35
+ "test/memcache_pg_server_test.rb",
36
+ "test/memcache_segmented_server_test.rb",
37
+ "test/memcache_server_test.rb",
38
+ "test/memcache_server_test_helper.rb",
39
+ "test/memcache_test.rb",
40
+ "test/test_helper.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/develon/memcache}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.5}
46
+ s.summary = %q{Advanced ruby memcache client}
47
+ s.test_files = [
48
+ "test/memcache_local_server_test.rb",
49
+ "test/memcache_null_server_test.rb",
50
+ "test/memcache_pg_server_test.rb",
51
+ "test/memcache_segmented_server_test.rb",
52
+ "test/memcache_server_test.rb",
53
+ "test/memcache_server_test_helper.rb",
54
+ "test/memcache_test.rb",
55
+ "test/test_helper.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ else
64
+ end
65
+ else
66
+ end
67
+ end
68
+