jruby-memcache-client-thoughtworks 1.8.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/MIT-LICENSE +24 -0
- data/README +84 -0
- data/Rakefile +30 -0
- data/VERSION.yml +4 -0
- data/jruby-memcache-client-thoughtworks.gemspec +42 -0
- data/lib/java/java_memcached-release_2.5.1.jar +0 -0
- data/lib/memcache.rb +345 -0
- data/spec/jruby_memcache_spec.rb +272 -0
- metadata +62 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2008 Abhi Yerra <abhi@traytwo.com>
|
2
|
+
Copyright (c) 2009 Ikai Lan <ikai.lan@gmail.com>
|
3
|
+
Copyright (c) 2009 Tiago Bastos <comechao@gmail.com>
|
4
|
+
Copyright (c) 2009 Frederic Jean <frederic.jean@sun.com>
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
data/README
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
This is forked version of jruby-memcached-client gem. It was forked and published as its own gem because the original owners have not published an updated gem for 3 years. Also the published gem depends on the development version of the jar and throws ugly exceptions when connection is lost.
|
2
|
+
- Sudhindra Rao
|
3
|
+
_____________________________________________________________________________________________________________________
|
4
|
+
|
5
|
+
This projects provides memcached based stores for JRuby. It is a gem based on Ikai Lan's jruby-memcache-client project hosted at http://github.com/ikai/jruby-memcache-client/tree
|
6
|
+
|
7
|
+
This project is a JRuby wrapper of the Java MemCache library by Greg Whalin
|
8
|
+
|
9
|
+
http://www.whalin.com/memcached/
|
10
|
+
|
11
|
+
In production, the standard Ruby MemCache client can cause a thread to hang. In the Glassfish application server, by default there are 5 Grizzly connectors that handle incoming requests. A site that uses MemCache heavily can quickly cause all Grizzly connectors to block and take down a site. I'm hoping that this work I am doing here will help others adopt JRuby as a production platform for their Ruby on Rails applications.
|
12
|
+
|
13
|
+
The Ruby MemCache library was never written with threaded applications in mind. All threads use the same socket, and multithreaded mode basically wraps the IO with a Mutex. The Java library provides several features that are not available in the Ruby MemCache library:
|
14
|
+
|
15
|
+
- connection pooling
|
16
|
+
- socket timeouts. In forks of the Ruby MemCache library this is achieved either with a Mongrel timeout or use of the Ruby Timeout class, which, at least at the writing of this README, will work unpredictably when being used as a failsafe against hanging IO.
|
17
|
+
|
18
|
+
As of right now this code only provides a very minimal amount of functionality, but it is enough to use with the Rails cache and cache_fu.
|
19
|
+
|
20
|
+
Installation
|
21
|
+
------------
|
22
|
+
This is a ruby gem that can be installed from gemcutter.org.
|
23
|
+
|
24
|
+
You will first need to install the gemcutter gem and configure your system to use it:
|
25
|
+
|
26
|
+
jruby -S gem install gemcutter
|
27
|
+
jruby -S gem tumble
|
28
|
+
|
29
|
+
You will then be able to install the JRuby Memcache Client gem:
|
30
|
+
|
31
|
+
jruby -S gem install jruby-memcache-client
|
32
|
+
|
33
|
+
Java Requirements
|
34
|
+
-----------------
|
35
|
+
|
36
|
+
The underlying library used for this project was compiled using Java 6. You will not be able to run this gem under Java 5 out of the box. You can however clone http://github.com/gwhalin/Memcached-Java-Client and attempt to build it under Java 5 if you must. (You really should consider upgrading to Java 6 since Oracle no longer supports Java 5.)
|
37
|
+
|
38
|
+
Replacing Rail's MemCache Client
|
39
|
+
--------------------------------
|
40
|
+
|
41
|
+
Rails ships with a bundled copy of the MemCache client. This client will prevent you from using this gem instead. Adding the following code into your environment.rb file:
|
42
|
+
|
43
|
+
if RUBY_PLATFORM =~ /java/i
|
44
|
+
# Based on instructions from http://www.mikeperham.com/2009/03/03/using-memcache-client-16x-in-rails-23/
|
45
|
+
# Brain surgery to use our own version of memcache-client without
|
46
|
+
# having to modify activesupport directly.
|
47
|
+
# Unload any previous instance of the class
|
48
|
+
if Object.const_defined? :MemCache
|
49
|
+
Object.instance_eval { remove_const :MemCache }
|
50
|
+
end
|
51
|
+
# Pull in the exact version we want
|
52
|
+
gem 'jruby-memcache-client', '1.6.1'
|
53
|
+
|
54
|
+
# Ensure that the memcache-client path is at the front of the loadpath
|
55
|
+
$LOAD_PATH.each do |path|
|
56
|
+
if path =~ /jruby-memcache-client/
|
57
|
+
$LOAD_PATH.delete(path)
|
58
|
+
$LOAD_PATH.unshift(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# If Ruby thinks it's already loaded memcache.rb, force
|
62
|
+
# a reload otherwise just require.
|
63
|
+
if $".find { |file| file =~ /\Amemcache.rb\Z/ }
|
64
|
+
load 'memcache.rb'
|
65
|
+
else
|
66
|
+
require 'memcache'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
This will remove the original MemCache client and load our version of the MemCache class instead.
|
71
|
+
|
72
|
+
Configuration
|
73
|
+
-------------
|
74
|
+
The JRuby MemCache client uses the same configuration options as the regular MemCache client. Here is how to build the configuration in your environment.rb file:
|
75
|
+
|
76
|
+
memcache_options = {
|
77
|
+
:namespace => 'fortaleza:production_live:',
|
78
|
+
}
|
79
|
+
memcached_servers = [ ENV['MEMCACHED_LOCATION'] || '0.0.0.0:11211']
|
80
|
+
|
81
|
+
# Constant used by libs
|
82
|
+
CACHE = MemCache.new memcached_servers, memcache_options if RUBY_PLATFORM =~ /java/
|
83
|
+
|
84
|
+
Note that this may vary based on your particular configuration method and environment.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.version = "1.8.0"
|
5
|
+
gemspec.name = "jruby-memcache-client-thoughtworks"
|
6
|
+
gemspec.summary = "A drop in replacement for Ruby's memcache-client. Now with a stable jruby-memcached-release jar."
|
7
|
+
gemspec.email = "sudhindra.r.rao@gmail.com"
|
8
|
+
gemspec.homepage = "http://github.com/ThoughtWorksStudios/jruby-memcache-client"
|
9
|
+
gemspec.description = "A drop in replacement for Ruby's memcache-client.Now with a stable jruby-memcached-release jar."
|
10
|
+
gemspec.authors = ["Abhi Yerra", "Ikai Lan", "Frederic Jean", "Lennon Day-Reynolds",
|
11
|
+
"slyphon", "Brayn Helmkamp", "Travis Tilley", "Sudhindra Rao(ThoughtWorksStudios)"]
|
12
|
+
end
|
13
|
+
rescue LoadError
|
14
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
15
|
+
end
|
16
|
+
|
17
|
+
if RUBY_PLATFORM =~ /java/i
|
18
|
+
begin
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
|
21
|
+
task :default => :spec
|
22
|
+
|
23
|
+
desc "Run the specs for the jruby-memcache-client gem"
|
24
|
+
Spec::Rake::SpecTask.new
|
25
|
+
rescue LoadError
|
26
|
+
puts "You must have rspec installed in order to run the tests."
|
27
|
+
end
|
28
|
+
else
|
29
|
+
puts "You must run rake under JRuby."
|
30
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "jruby-memcache-client-thoughtworks"
|
8
|
+
s.version = "1.8.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Abhi Yerra", "Ikai Lan", "Frederic Jean", "Lennon Day-Reynolds", "slyphon", "Brayn Helmkamp", "Travis Tilley", "Sudhindra Rao(ThoughtWorksStudios)"]
|
12
|
+
s.date = "2013-01-24"
|
13
|
+
s.description = "A drop in replacement for Ruby's memcache-client.Now with a stable jruby-memcached-release jar."
|
14
|
+
s.email = "sudhindra.r.rao@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"MIT-LICENSE",
|
20
|
+
"README",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION.yml",
|
23
|
+
"jruby-memcache-client-thoughtworks.gemspec",
|
24
|
+
"lib/java/java_memcached-release_2.5.1.jar",
|
25
|
+
"lib/memcache.rb",
|
26
|
+
"spec/jruby_memcache_spec.rb"
|
27
|
+
]
|
28
|
+
s.homepage = "http://github.com/ThoughtWorksStudios/jruby-memcache-client"
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
s.rubygems_version = "1.8.10"
|
31
|
+
s.summary = "A drop in replacement for Ruby's memcache-client. Now with a stable jruby-memcached-release jar."
|
32
|
+
|
33
|
+
if s.respond_to? :specification_version then
|
34
|
+
s.specification_version = 3
|
35
|
+
|
36
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
37
|
+
else
|
38
|
+
end
|
39
|
+
else
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
Binary file
|
data/lib/memcache.rb
ADDED
@@ -0,0 +1,345 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/java/java_memcached-release_2.5.1.jar'
|
5
|
+
|
6
|
+
class MemCache
|
7
|
+
include_class 'com.danga.MemCached.MemCachedClient'
|
8
|
+
include_class 'com.danga.MemCached.SockIOPool'
|
9
|
+
include_class 'com.danga.MemCached.Logger'
|
10
|
+
|
11
|
+
VERSION = '1.7.0'
|
12
|
+
|
13
|
+
##
|
14
|
+
# Default options for the cache object.
|
15
|
+
|
16
|
+
DEFAULT_OPTIONS = {
|
17
|
+
:namespace => nil,
|
18
|
+
:readonly => false,
|
19
|
+
:multithread => true,
|
20
|
+
:pool_initial_size => 10,
|
21
|
+
:pool_min_size => 5,
|
22
|
+
:pool_max_size => 100,
|
23
|
+
:pool_max_idle => (1000 * 60 * 5),
|
24
|
+
:pool_max_busy => (1000 * 30),
|
25
|
+
:pool_maintenance_thread_sleep => (1000 * 30),
|
26
|
+
:pool_socket_timeout => (1000 * 3),
|
27
|
+
:pool_socket_connect_timeout => (1000 * 3),
|
28
|
+
:pool_use_alive => false,
|
29
|
+
:pool_use_failover => true,
|
30
|
+
:pool_use_failback => true,
|
31
|
+
:pool_use_nagle => false,
|
32
|
+
:pool_name => 'default',
|
33
|
+
:log_level => 2
|
34
|
+
}
|
35
|
+
|
36
|
+
## CHARSET for Marshalling
|
37
|
+
MARSHALLING_CHARSET = 'UTF-8'
|
38
|
+
|
39
|
+
##
|
40
|
+
# Default memcached port.
|
41
|
+
|
42
|
+
DEFAULT_PORT = 11211
|
43
|
+
|
44
|
+
##
|
45
|
+
# Default memcached server weight.
|
46
|
+
|
47
|
+
DEFAULT_WEIGHT = 1
|
48
|
+
|
49
|
+
attr_accessor :request_timeout
|
50
|
+
|
51
|
+
##
|
52
|
+
# The namespace for this instance
|
53
|
+
|
54
|
+
attr_reader :namespace
|
55
|
+
|
56
|
+
##
|
57
|
+
# The multithread setting for this instance
|
58
|
+
|
59
|
+
attr_reader :multithread
|
60
|
+
|
61
|
+
##
|
62
|
+
# The configured socket pool name for this client.
|
63
|
+
attr_reader :pool_name
|
64
|
+
|
65
|
+
##
|
66
|
+
# Configures the client
|
67
|
+
def initialize(*args)
|
68
|
+
@servers = []
|
69
|
+
opts = {}
|
70
|
+
|
71
|
+
case args.length
|
72
|
+
when 0 then # NOP
|
73
|
+
when 1 then
|
74
|
+
arg = args.shift
|
75
|
+
case arg
|
76
|
+
when Hash then opts = arg
|
77
|
+
when Array then @servers = arg
|
78
|
+
when String then @servers = [arg]
|
79
|
+
else raise ArgumentError, 'first argument must be Array, Hash or String'
|
80
|
+
end
|
81
|
+
when 2 then
|
82
|
+
@servers, opts = args
|
83
|
+
@servers = [@servers].flatten
|
84
|
+
else
|
85
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for 2)"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Normalizing the server(s) so they all have a port number.
|
89
|
+
|
90
|
+
@servers = @servers.map do |server|
|
91
|
+
server =~ /(.+):(\d+)/ ? server : "#{server}:#{DEFAULT_PORT}"
|
92
|
+
end
|
93
|
+
|
94
|
+
Logger.getLogger('com.meetup.memcached.MemcachedClient').setLevel(opts[:log_level])
|
95
|
+
Logger.getLogger('com.meetup.memcached.SockIOPool').setLevel(opts[:log_level])
|
96
|
+
|
97
|
+
opts = DEFAULT_OPTIONS.merge opts
|
98
|
+
|
99
|
+
@namespace = opts[:namespace] || opts["namespace"]
|
100
|
+
@pool_name = opts[:pool_name] || opts["pool_name"]
|
101
|
+
@readonly = opts[:readonly] || opts["readonly"]
|
102
|
+
|
103
|
+
@client = MemCachedClient.new(@pool_name)
|
104
|
+
|
105
|
+
@client.error_handler = opts[:error_handler] if opts[:error_handler]
|
106
|
+
@client.primitiveAsString = true
|
107
|
+
@client.sanitizeKeys = false
|
108
|
+
|
109
|
+
weights = Array.new(@servers.size, DEFAULT_WEIGHT)
|
110
|
+
|
111
|
+
@pool = SockIOPool.getInstance(@pool_name)
|
112
|
+
unless @pool.initialized?
|
113
|
+
@pool.servers = @servers.to_java(:string)
|
114
|
+
@pool.weights = weights.to_java(:Integer)
|
115
|
+
|
116
|
+
@pool.initConn = opts[:pool_initial_size]
|
117
|
+
@pool.minConn = opts[:pool_min_size]
|
118
|
+
@pool.maxConn = opts[:pool_max_size]
|
119
|
+
|
120
|
+
@pool.maxIdle = opts[:pool_max_idle]
|
121
|
+
@pool.maxBusyTime = opts[:pool_max_busy]
|
122
|
+
@pool.maintSleep = opts[:pool_maintenance_thread_sleep]
|
123
|
+
@pool.socketTO = opts[:pool_socket_timeout]
|
124
|
+
@pool.socketConnectTO = opts[:pool_socket_connect_timeout]
|
125
|
+
|
126
|
+
@pool.failover = opts[:pool_use_failover]
|
127
|
+
@pool.failback = opts[:pool_use_failback]
|
128
|
+
@pool.aliveCheck = opts[:pool_use_alive]
|
129
|
+
@pool.nagle = opts[:pool_use_nagle]
|
130
|
+
|
131
|
+
# public static final int NATIVE_HASH = 0;
|
132
|
+
# // native String.hashCode();
|
133
|
+
# public static final int OLD_COMPAT_HASH = 1;
|
134
|
+
# // original compatibility hashing algorithm (works with other clients)
|
135
|
+
# public static final int NEW_COMPAT_HASH = 2;
|
136
|
+
# // new CRC32 based compatibility hashing algorithm (works with other clients)
|
137
|
+
# public static final int CONSISTENT_HASH = 3;
|
138
|
+
# // MD5 Based -- Stops thrashing when a server added or removed
|
139
|
+
@pool.hashingAlg = opts[:pool_hashing_algorithm]
|
140
|
+
|
141
|
+
# __method methods have been removed in jruby 1.5
|
142
|
+
@pool.java_send :initialize rescue @pool.initialize__method
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
def reset
|
148
|
+
@pool.shut_down
|
149
|
+
@pool.java_send :initialize rescue @pool.initialize__method
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Returns the servers that the client has been configured to
|
154
|
+
# use. Injects an alive? method into the string so it works with the
|
155
|
+
# updated Rails MemCacheStore session store class.
|
156
|
+
def servers
|
157
|
+
@pool.servers.to_a.collect do |s|
|
158
|
+
s.instance_eval(<<-EOIE)
|
159
|
+
def alive?
|
160
|
+
#{!!stats[s]}
|
161
|
+
end
|
162
|
+
EOIE
|
163
|
+
s
|
164
|
+
end rescue []
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Determines whether any of the connections to the servers is
|
169
|
+
# alive. We are alive if it is the case.
|
170
|
+
def alive?
|
171
|
+
servers.to_a.any? { |s| s.alive? }
|
172
|
+
end
|
173
|
+
|
174
|
+
alias :active? :alive?
|
175
|
+
|
176
|
+
##
|
177
|
+
# Retrieves a value associated with the key from the
|
178
|
+
# cache. Retrieves the raw value if the raw parameter is set.
|
179
|
+
def get(key, raw = false)
|
180
|
+
value = @client.get(make_cache_key(key))
|
181
|
+
return nil if value.nil?
|
182
|
+
unless raw
|
183
|
+
begin
|
184
|
+
marshal_bytes = java.lang.String.new(value).getBytes(MARSHALLING_CHARSET)
|
185
|
+
decoded = Base64.decode64(String.from_java_bytes(marshal_bytes))
|
186
|
+
value = Marshal.load(decoded)
|
187
|
+
rescue
|
188
|
+
value = case value
|
189
|
+
when /^\d+\.\d+$/ then value.to_f
|
190
|
+
when /^\d+$/ then value.to_i
|
191
|
+
else value
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
value
|
196
|
+
end
|
197
|
+
|
198
|
+
alias :[] :get
|
199
|
+
|
200
|
+
##
|
201
|
+
# Retrieves the values associated with the keys parameter.
|
202
|
+
def get_multi(keys, raw = false)
|
203
|
+
keys = keys.map {|k| make_cache_key(k)}
|
204
|
+
keys = keys.to_java :String
|
205
|
+
values = {}
|
206
|
+
values_j = @client.getMulti(keys)
|
207
|
+
values_j.to_a.each {|kv|
|
208
|
+
k,v = kv
|
209
|
+
next if v.nil?
|
210
|
+
unless raw
|
211
|
+
begin
|
212
|
+
marshal_bytes = java.lang.String.new(v).getBytes(MARSHALLING_CHARSET)
|
213
|
+
decoded = Base64.decode64(String.from_java_bytes(marshal_bytes))
|
214
|
+
v = Marshal.load(decoded)
|
215
|
+
rescue
|
216
|
+
v = case v
|
217
|
+
when /^\d+\.\d+$/ then v.to_f
|
218
|
+
when /^\d+$/ then v.to_i
|
219
|
+
else v
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
values[k] = v
|
224
|
+
}
|
225
|
+
values
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Associates a value with a key in the cache. MemCached will expire
|
230
|
+
# the value if an expiration is provided. The raw parameter allows
|
231
|
+
# us to store a value without marshalling it first.
|
232
|
+
def set(key, value, expiry = 0, raw = false)
|
233
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
234
|
+
value = marshal_value(value) unless raw
|
235
|
+
key = make_cache_key(key)
|
236
|
+
if expiry == 0
|
237
|
+
@client.set key, value
|
238
|
+
else
|
239
|
+
@client.set key, value, expiration(expiry)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
alias :[]= :set
|
244
|
+
|
245
|
+
##
|
246
|
+
# Add a new value to the cache following the same conventions that
|
247
|
+
# are used in the set method.
|
248
|
+
def add(key, value, expiry = 0, raw = false)
|
249
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
250
|
+
value = marshal_value(value) unless raw
|
251
|
+
if expiry == 0
|
252
|
+
@client.add make_cache_key(key), value
|
253
|
+
else
|
254
|
+
@client.add make_cache_key(key), value, expiration(expiry)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
##
|
259
|
+
# Removes the value associated with the key from the cache. This
|
260
|
+
# will ignore values that are not already present in the cache,
|
261
|
+
# which makes this safe to use without first checking for the
|
262
|
+
# existance of the key in the cache first.
|
263
|
+
def delete(key, expires = 0)
|
264
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
265
|
+
@client.delete(make_cache_key(key))
|
266
|
+
end
|
267
|
+
|
268
|
+
##
|
269
|
+
# Replaces the value associated with a key in the cache if it
|
270
|
+
# already is stored. It will not add the value to the cache if it
|
271
|
+
# isn't already present.
|
272
|
+
def replace(key, value, expiry = 0, raw = false)
|
273
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
274
|
+
value = marshal_value(value) unless raw
|
275
|
+
if expiry == 0
|
276
|
+
@client.replace make_cache_key(key), value
|
277
|
+
else
|
278
|
+
@client.replace make_cache_key(key), value
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
##
|
283
|
+
# Increments the value associated with the key by a certain amount.
|
284
|
+
def incr(key, amount = 1)
|
285
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
286
|
+
value = @client.incr(make_cache_key(key), amount)
|
287
|
+
return nil if value == "NOT_FOUND\r\n"
|
288
|
+
return value.to_i
|
289
|
+
end
|
290
|
+
|
291
|
+
##
|
292
|
+
# Decrements the value associated with the key by a certain amount.
|
293
|
+
def decr(key, amount = 1)
|
294
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
295
|
+
value = @client.decr(make_cache_key(key),amount)
|
296
|
+
return nil if value == "NOT_FOUND\r\n"
|
297
|
+
return value.to_i
|
298
|
+
end
|
299
|
+
|
300
|
+
##
|
301
|
+
# Clears the cache.
|
302
|
+
def flush_all
|
303
|
+
@client.flush_all
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Reports statistics on the cache.
|
308
|
+
def stats
|
309
|
+
stats_hash = {}
|
310
|
+
@client.stats.each do |server, stats|
|
311
|
+
stats_hash[server] = Hash.new
|
312
|
+
stats.each do |key, value|
|
313
|
+
unless key == 'version'
|
314
|
+
value = value.to_f
|
315
|
+
value = value.to_i if value == value.ceil
|
316
|
+
end
|
317
|
+
stats_hash[server][key] = value
|
318
|
+
end
|
319
|
+
end
|
320
|
+
stats_hash
|
321
|
+
end
|
322
|
+
|
323
|
+
class MemCacheError < RuntimeError; end
|
324
|
+
|
325
|
+
protected
|
326
|
+
def make_cache_key(key)
|
327
|
+
if namespace.nil? then
|
328
|
+
key
|
329
|
+
else
|
330
|
+
"#{@namespace}:#{key}"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def expiration(expiry)
|
335
|
+
java.util.Date.new((Time.now.to_i + expiry) * 1000)
|
336
|
+
end
|
337
|
+
|
338
|
+
def marshal_value(value)
|
339
|
+
return value if value.kind_of?(Numeric)
|
340
|
+
encoded = Base64.encode64(Marshal.dump(value))
|
341
|
+
marshal_bytes = encoded.to_java_bytes
|
342
|
+
java.lang.String.new(marshal_bytes, MARSHALLING_CHARSET)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'java'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/memcache'
|
3
|
+
|
4
|
+
describe MemCache do
|
5
|
+
|
6
|
+
hostname = `hostname`.strip
|
7
|
+
|
8
|
+
["127.0.0.1:11211", "127.0.0.1", hostname, "#{hostname}:11211"].each do |server|
|
9
|
+
before :all do
|
10
|
+
@server = server
|
11
|
+
@normalized_server = @server =~ /(.+):(\d+)/ ? @server : "#{@server}:11211"
|
12
|
+
end
|
13
|
+
|
14
|
+
before :each do
|
15
|
+
@client = MemCache.new @server
|
16
|
+
@client.should_not be_nil
|
17
|
+
@client.flush_all
|
18
|
+
end
|
19
|
+
|
20
|
+
after :each do
|
21
|
+
@client.flush_all
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return nil for a non-existent key" do
|
25
|
+
@client.get('non-existent-key').should be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "setting servers" do
|
29
|
+
it "should work if the instance is created with a single String argument" do
|
30
|
+
@client = MemCache.new @server
|
31
|
+
@client.servers.should == [@normalized_server]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should work if the instance is created with an Array" do
|
35
|
+
@client = MemCache.new [ @server ]
|
36
|
+
@client.servers.should == [@normalized_server]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should work if the instance is created with a Hash" do
|
40
|
+
@client = MemCache.new [ @server ], :namespace => 'test'
|
41
|
+
@client.servers.should == [@normalized_server]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should work with an explicit pool name" do
|
45
|
+
@client = MemCache.new([@server], :pool_name => 'new_pool')
|
46
|
+
@client.pool_name.should == 'new_pool'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should work with an error handler" do
|
50
|
+
include_class 'com.danga.MemCached.MemCachedClient'
|
51
|
+
java_memcache_client = mock.as_null_object
|
52
|
+
MemCachedClient.stub!(:new => java_memcache_client)
|
53
|
+
error_handler = Object.new
|
54
|
+
java_memcache_client.should_receive(:error_handler=).with(error_handler)
|
55
|
+
@client = MemCache.new([@server], :error_handler => error_handler)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "namespacing" do
|
60
|
+
before(:each) do
|
61
|
+
@ns = 'namespace'
|
62
|
+
@nsclient = MemCache.new [ @server ] , :namespace => @ns
|
63
|
+
@nsclient.flush_all
|
64
|
+
@nsclient.set "test", 333, 0
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should set and get values transparently" do
|
68
|
+
@nsclient.get("test").should == 333
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should set values to the given namespace" do
|
72
|
+
@nsclient.get("test").to_i.should == 333
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not set a value without the given namespace" do
|
76
|
+
@client.get("test").to_i.should_not == 333
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should delete values in the given namespace" do
|
80
|
+
@nsclient.delete "test"
|
81
|
+
@nsclient.get("test").should be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should increment in the given namespace" do
|
85
|
+
@nsclient.incr("test").to_i.should == 334
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should decrement values in the given namespace" do
|
89
|
+
@nsclient.decr("test").should == 332
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "after setting a value to MemCache" do
|
94
|
+
before(:each) do
|
95
|
+
@client.set 'key', 'value'
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should be able to retrieve the value" do
|
99
|
+
@client.get('key').should == 'value'
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should not be able to retrieve the value after deleting" do
|
103
|
+
@client.delete('key')
|
104
|
+
@client.get('key').should be_nil
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should not be able to retrieve the value after flushing everything" do
|
108
|
+
@client.flush_all
|
109
|
+
@client.get("key").should be_nil
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should work exactly the same if the []= operator were used" do
|
113
|
+
@client['key'] = 'val'
|
114
|
+
@client.get('key').should == 'val'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "replacing values from the cache." do
|
119
|
+
before :each do
|
120
|
+
@client['key'] = 'value'
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should be able to replace the stored value." do
|
124
|
+
@client.replace('key', 'new value').should be_true
|
125
|
+
@client['key'].should == 'new value'
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should not replace values that are not in the cache." do
|
129
|
+
@client.replace('notthere', 'value').should be_false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "using the Hash notation" do
|
134
|
+
before :each do
|
135
|
+
@client['key'] = 'value'
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should be able to retrieve the value using []" do
|
139
|
+
@client['key'].should == 'value'
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should be able to retrieve the value using get" do
|
143
|
+
@client.get('key').should == 'value'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#stats" do
|
148
|
+
it "should return a hash" do
|
149
|
+
@client.stats.should be_instance_of(Hash)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should return a float for rusage_system and rusage_user" do
|
153
|
+
@client.stats[@normalized_server]['rusage_system'].should be_instance_of(Float)
|
154
|
+
@client.stats[@normalized_server]['rusage_user'].should be_instance_of(Float)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should return a String for version" do
|
158
|
+
@client.stats[@normalized_server]['version'].should be_instance_of(String)
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#incr" do
|
164
|
+
|
165
|
+
it "should increment a value by 1 without a second parameter" do
|
166
|
+
@client.set 'incr', 100, 0
|
167
|
+
@client.incr 'incr'
|
168
|
+
@client.get('incr').to_i.should == 101
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should increment a value by a given second parameter" do
|
172
|
+
@client.set 'incr', 100, 0
|
173
|
+
@client.incr 'incr', 20
|
174
|
+
@client.get('incr').to_i.should == 120
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "#decr" do
|
179
|
+
|
180
|
+
it "should decrement a value by 1 without a second parameter" do
|
181
|
+
@client.set 'decr', 100, 0
|
182
|
+
@client.decr 'decr'
|
183
|
+
@client.get('decr').to_i.should == 99
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should decrement a value by a given second parameter" do
|
187
|
+
@client.set 'decr', 100, 0
|
188
|
+
@client.decr 'decr', 20
|
189
|
+
@client.get('decr').to_i.should == 80
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "with Ruby Objects" do
|
194
|
+
it "should be able to transparently set and get equivalent Ruby objects" do
|
195
|
+
obj = { :test => :hi }
|
196
|
+
@client.set('obj', obj)
|
197
|
+
@client.get('obj').should == obj
|
198
|
+
end
|
199
|
+
|
200
|
+
it %[should work with those whose marshalled stream contains invalid UTF8 byte sequences] do
|
201
|
+
# this test fails w/o the Base64 encoding step
|
202
|
+
obj = { :foo => 900 }
|
203
|
+
@client.set('obj', obj)
|
204
|
+
@client.get('obj').should == obj
|
205
|
+
end
|
206
|
+
|
207
|
+
it %[should work with binary blobs] do
|
208
|
+
# this test fails w/o the Base64 encoding step
|
209
|
+
blob = "\377\330\377\340\000\020JFIF\000\001\001\000\000\001\000\001\000\000\377"
|
210
|
+
@client.set('blob', blob)
|
211
|
+
@client.get('blob').should == blob
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe "using set with an expiration" do
|
216
|
+
it "should make a value unretrievable if the expiry is set to a negative value" do
|
217
|
+
@client.set('key', 'val', -1)
|
218
|
+
@client.get('key').should be_nil
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should make a value retrievable for only the amount of time if a value is given" do
|
222
|
+
@client.set('key', 'val', 2)
|
223
|
+
@client.get('key').should == 'val'
|
224
|
+
sleep(3)
|
225
|
+
@client.get('key').should be_nil
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "#get_multi" do
|
230
|
+
it "should get 2 keys" do
|
231
|
+
@client.set('key', 'val')
|
232
|
+
@client.set('key2', 'val2')
|
233
|
+
@client.get_multi(%w/key key2/).should == {'key' => 'val', 'key2' => 'val2'}
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should ignore nil values" do
|
237
|
+
@client.set('key', 'val')
|
238
|
+
@client.set('key2', 'val2')
|
239
|
+
@client.get_multi(%w/key key2 key3/).should == {'key' => 'val', 'key2' => 'val2'}
|
240
|
+
end
|
241
|
+
|
242
|
+
it "should not marshall if requested" do
|
243
|
+
@client.set('key', 'val', 0, true)
|
244
|
+
@client.set('key2', 'val2', 0, true)
|
245
|
+
@client.get_multi(%w/key key2/, true).should == {'key' => 'val', 'key2' => 'val2'}
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe "aliveness of the MemCache server." do
|
250
|
+
before :each do
|
251
|
+
@servers = ["localhost:11211", "localhost:11212", {:pool_name => "test"}]
|
252
|
+
@client = MemCache.new @servers
|
253
|
+
@client.flush_all
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should report the client as being alive." do
|
257
|
+
@client.should be_alive
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should report localhost:11211 as being alive." do
|
261
|
+
servers = @client.servers
|
262
|
+
servers.first.should be_alive
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should report localhost:11212 as not being alive." do
|
266
|
+
servers = @client.servers
|
267
|
+
servers.find {|s| s.to_s == "localhost:11212"}.should be_nil
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jruby-memcache-client-thoughtworks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.8.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Abhi Yerra
|
9
|
+
- Ikai Lan
|
10
|
+
- Frederic Jean
|
11
|
+
- Lennon Day-Reynolds
|
12
|
+
- slyphon
|
13
|
+
- Brayn Helmkamp
|
14
|
+
- Travis Tilley
|
15
|
+
- Sudhindra Rao(ThoughtWorksStudios)
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
date: 2013-01-24 00:00:00.000000000 Z
|
20
|
+
dependencies: []
|
21
|
+
description: A drop in replacement for Ruby's memcache-client.Now with a stable jruby-memcached-release
|
22
|
+
jar.
|
23
|
+
email: sudhindra.r.rao@gmail.com
|
24
|
+
executables: []
|
25
|
+
extensions: []
|
26
|
+
extra_rdoc_files:
|
27
|
+
- README
|
28
|
+
files:
|
29
|
+
- MIT-LICENSE
|
30
|
+
- README
|
31
|
+
- Rakefile
|
32
|
+
- VERSION.yml
|
33
|
+
- jruby-memcache-client-thoughtworks.gemspec
|
34
|
+
- lib/java/java_memcached-release_2.5.1.jar
|
35
|
+
- lib/memcache.rb
|
36
|
+
- spec/jruby_memcache_spec.rb
|
37
|
+
homepage: http://github.com/ThoughtWorksStudios/jruby-memcache-client
|
38
|
+
licenses: []
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.8.10
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: A drop in replacement for Ruby's memcache-client. Now with a stable jruby-memcached-release
|
61
|
+
jar.
|
62
|
+
test_files: []
|