fredjean-jruby-memcache-client 1.5.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/README +35 -0
- data/Rakefile +28 -0
- data/VERSION.yml +4 -0
- data/lib/java/java_memcached-release_2.0.1.jar +0 -0
- data/lib/memcache.rb +220 -0
- data/spec/jruby_memcache_spec.rb +191 -0
- metadata +61 -0
data/README
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
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
|
2
|
+
|
3
|
+
This project is a JRuby wrapper of the Java MemCache library by Greg Whalin
|
4
|
+
|
5
|
+
http://www.whalin.com/memcached/
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
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:
|
10
|
+
|
11
|
+
- connection pooling
|
12
|
+
- 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.
|
13
|
+
|
14
|
+
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.
|
15
|
+
|
16
|
+
Installation
|
17
|
+
------------
|
18
|
+
This is a ruby gem that can be installed.
|
19
|
+
|
20
|
+
gem sources -a http://gems.github.com
|
21
|
+
gem install fredjean-jruby-memcache-client --remote
|
22
|
+
|
23
|
+
Configuration
|
24
|
+
-------------
|
25
|
+
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:
|
26
|
+
|
27
|
+
memcache_options = {
|
28
|
+
:namespace => 'fortaleza:production_live:',
|
29
|
+
}
|
30
|
+
memcached_servers = [ ENV['MEMCACHED_LOCATION'] || '0.0.0.0:11211']
|
31
|
+
|
32
|
+
# Constant used by libs
|
33
|
+
CACHE = MemCache.new memcached_servers, memcache_options if RUBY_PLATFORM =~ /java/
|
34
|
+
|
35
|
+
Note that this may vary based on your particular configuration method and environment.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "jruby-memcache-client"
|
5
|
+
gemspec.summary = "A drop in replacement for Ruby's memcache-client."
|
6
|
+
gemspec.email = "abhi@traytwo.com"
|
7
|
+
gemspec.homepage = "http://github.com/abhiyerra/jruby-memcache-client"
|
8
|
+
gemspec.description = "A drop in replacement for Ruby's memcache-client."
|
9
|
+
gemspec.authors = ["Abhi Yerra", "Ikai Lan", "Frederic Jean", "Lennon Day-Reynolds"]
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
13
|
+
end
|
14
|
+
|
15
|
+
if RUBY_PLATFORM =~ /java/i
|
16
|
+
begin
|
17
|
+
require 'spec/rake/spectask'
|
18
|
+
|
19
|
+
task :default => :spec
|
20
|
+
|
21
|
+
desc "Run the specs for the jruby-memcache-client gem"
|
22
|
+
Spec::Rake::SpecTask.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "You must have rspec installed in order to run the tests."
|
25
|
+
end
|
26
|
+
else
|
27
|
+
puts "You must run rake under JRuby."
|
28
|
+
end
|
data/VERSION.yml
ADDED
Binary file
|
data/lib/memcache.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/java/java_memcached-release_2.0.1.jar'
|
2
|
+
|
3
|
+
class MemCache
|
4
|
+
include_class 'com.danga.MemCached.MemCachedClient'
|
5
|
+
include_class 'com.danga.MemCached.SockIOPool'
|
6
|
+
|
7
|
+
VERSION = '1.5.0'
|
8
|
+
|
9
|
+
##
|
10
|
+
# Default options for the cache object.
|
11
|
+
|
12
|
+
DEFAULT_OPTIONS = {
|
13
|
+
:namespace => nil,
|
14
|
+
:readonly => false,
|
15
|
+
:multithread => true,
|
16
|
+
:pool_initial_size => 5,
|
17
|
+
:pool_min_size => 5,
|
18
|
+
:pool_max_size => 250,
|
19
|
+
:pool_max_idle => (1000 * 60 * 60 * 6),
|
20
|
+
:pool_maintenance_thread_sleep => 30,
|
21
|
+
:pool_use_nagle => false,
|
22
|
+
:pool_socket_timeout => 3000,
|
23
|
+
:pool_socket_connect_timeout => 3000,
|
24
|
+
:pool_name => 'default'
|
25
|
+
}
|
26
|
+
|
27
|
+
## CHARSET for Marshalling
|
28
|
+
MARSHALLING_CHARSET = 'ISO-8859-1'
|
29
|
+
|
30
|
+
##
|
31
|
+
# Default memcached port.
|
32
|
+
|
33
|
+
DEFAULT_PORT = 11211
|
34
|
+
|
35
|
+
##
|
36
|
+
# Default memcached server weight.
|
37
|
+
|
38
|
+
DEFAULT_WEIGHT = 1
|
39
|
+
|
40
|
+
attr_accessor :request_timeout
|
41
|
+
|
42
|
+
##
|
43
|
+
# The namespace for this instance
|
44
|
+
|
45
|
+
attr_reader :namespace
|
46
|
+
|
47
|
+
##
|
48
|
+
# The multithread setting for this instance
|
49
|
+
|
50
|
+
attr_reader :multithread
|
51
|
+
|
52
|
+
##
|
53
|
+
# The configured socket pool name for this client.
|
54
|
+
attr_reader :pool_name
|
55
|
+
|
56
|
+
def initialize(*args)
|
57
|
+
@servers = []
|
58
|
+
opts = {}
|
59
|
+
|
60
|
+
case args.length
|
61
|
+
when 0 then # NOP
|
62
|
+
when 1 then
|
63
|
+
arg = args.shift
|
64
|
+
case arg
|
65
|
+
when Hash then opts = arg
|
66
|
+
when Array then @servers = arg
|
67
|
+
when String then @servers = [arg]
|
68
|
+
else raise ArgumentError, 'first argument must be Array, Hash or String'
|
69
|
+
end
|
70
|
+
when 2 then
|
71
|
+
@servers, opts = args
|
72
|
+
@servers = [@servers].flatten
|
73
|
+
else
|
74
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for 2)"
|
75
|
+
end
|
76
|
+
|
77
|
+
opts = DEFAULT_OPTIONS.merge opts
|
78
|
+
|
79
|
+
@namespace = opts[:namespace] || opts["namespace"]
|
80
|
+
@pool_name = opts[:pool_name] || opts["pool_name"]
|
81
|
+
|
82
|
+
@client = MemCachedClient.new(@pool_name)
|
83
|
+
|
84
|
+
@client.primitiveAsString = true
|
85
|
+
@client.sanitizeKeys = false
|
86
|
+
|
87
|
+
weights = Array.new(@servers.size, DEFAULT_WEIGHT)
|
88
|
+
|
89
|
+
@pool = SockIOPool.getInstance(@pool_name)
|
90
|
+
unless @pool.initialized?
|
91
|
+
# // set the servers and the weights
|
92
|
+
@pool.servers = @servers.to_java(:string)
|
93
|
+
@pool.weights = weights.to_java(:Integer)
|
94
|
+
|
95
|
+
# // set some basic pool settings
|
96
|
+
# // 5 initial, 5 min, and 250 max conns
|
97
|
+
# // and set the max idle time for a conn
|
98
|
+
# // to 6 hours
|
99
|
+
@pool.initConn = opts[:pool_initial_size]
|
100
|
+
@pool.minConn = opts[:pool_min_size]
|
101
|
+
@pool.maxConn = opts[:pool_max_size]
|
102
|
+
@pool.maxIdle = opts[:pool_max_idle]
|
103
|
+
|
104
|
+
# // set the sleep for the maint thread
|
105
|
+
# // it will wake up every x seconds and
|
106
|
+
# // maintain the pool size
|
107
|
+
@pool.maintSleep = opts[:pool_maintenance_thread_sleep]
|
108
|
+
#
|
109
|
+
# // set some TCP settings
|
110
|
+
# // disable nagle
|
111
|
+
# // set the read timeout to 3 secs
|
112
|
+
# // and don't set a connect timeout
|
113
|
+
@pool.nagle = opts[:pool_use_nagle]
|
114
|
+
@pool.socketTO = opts[:pool_socket_timeout]
|
115
|
+
@pool.socketConnectTO = opts[:pool_socket_connect_timeout]
|
116
|
+
@pool.aliveCheck = true
|
117
|
+
@pool.initialize__method
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def servers
|
122
|
+
@pool.get_servers.to_a rescue []
|
123
|
+
end
|
124
|
+
|
125
|
+
def alive?
|
126
|
+
@pool.servers.to_a.any?
|
127
|
+
end
|
128
|
+
|
129
|
+
def get(key, raw = false)
|
130
|
+
value = @client.get(make_cache_key(key))
|
131
|
+
return nil if value.nil?
|
132
|
+
unless raw
|
133
|
+
marshal_bytes = java.lang.String.new(value).getBytes(MARSHALLING_CHARSET)
|
134
|
+
value = Marshal.load(String.from_java_bytes(marshal_bytes))
|
135
|
+
end
|
136
|
+
value
|
137
|
+
end
|
138
|
+
|
139
|
+
alias :[] :get
|
140
|
+
|
141
|
+
def set(key, value, expiry = 0, raw = false)
|
142
|
+
value = marshal_value(value) unless raw
|
143
|
+
key = make_cache_key(key)
|
144
|
+
if expiry == 0
|
145
|
+
@client.set key, value
|
146
|
+
else
|
147
|
+
@client.set key, value, expiration(expiry)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
alias :[]= :set
|
152
|
+
|
153
|
+
def add(key, value, expiry = 0, raw = false)
|
154
|
+
value = marshal_value(value) unless raw
|
155
|
+
if expiry == 0
|
156
|
+
@client.add make_cache_key(key), value
|
157
|
+
else
|
158
|
+
@client.add make_cache_key(key), value, expiration(expiry)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def delete(key, expiry = 0)
|
163
|
+
@client.delete(make_cache_key(key))
|
164
|
+
end
|
165
|
+
|
166
|
+
def incr(key, amount = 1)
|
167
|
+
value = get(key) || 0
|
168
|
+
value += amount
|
169
|
+
set key, value
|
170
|
+
value
|
171
|
+
end
|
172
|
+
|
173
|
+
def decr(key, amount = 1)
|
174
|
+
value = get(key) || 0
|
175
|
+
value -= amount
|
176
|
+
set key, value
|
177
|
+
value
|
178
|
+
end
|
179
|
+
|
180
|
+
def flush_all
|
181
|
+
@client.flushAll
|
182
|
+
end
|
183
|
+
|
184
|
+
def stats
|
185
|
+
stats_hash = {}
|
186
|
+
@client.stats.each do |server, stats|
|
187
|
+
stats_hash[server] = Hash.new
|
188
|
+
stats.each do |key, value|
|
189
|
+
unless key == 'version'
|
190
|
+
value = value.to_f
|
191
|
+
value = value.to_i if value == value.ceil
|
192
|
+
end
|
193
|
+
stats_hash[server][key] = value
|
194
|
+
end
|
195
|
+
end
|
196
|
+
stats_hash
|
197
|
+
end
|
198
|
+
|
199
|
+
protected
|
200
|
+
def make_cache_key(key)
|
201
|
+
if namespace.nil? then
|
202
|
+
key
|
203
|
+
else
|
204
|
+
"#{@namespace}:#{key}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def expiration(expiry)
|
209
|
+
java.util.Date.new((Time.now.to_i + expiry) * 1000)
|
210
|
+
end
|
211
|
+
|
212
|
+
def marshal_value(value)
|
213
|
+
marshal_bytes = Marshal.dump(value).to_java_bytes
|
214
|
+
java.lang.String.new(marshal_bytes, MARSHALLING_CHARSET)
|
215
|
+
end
|
216
|
+
|
217
|
+
class MemCacheError < RuntimeError; end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/memcache'
|
4
|
+
|
5
|
+
describe MemCache do
|
6
|
+
before(:all) do
|
7
|
+
@server = "127.0.0.1:11211"
|
8
|
+
@client = MemCache.new @server
|
9
|
+
@client.flush_all
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:each) do
|
13
|
+
@client.flush_all
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return nil for a non-existent key" do
|
17
|
+
@client.get('non-existent-key').should be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "setting servers" do
|
21
|
+
it "should work if the instance is created with a single String argument" do
|
22
|
+
@client = MemCache.new @server
|
23
|
+
@client.servers.should == [@server]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should work if the instance is created with an Array" do
|
27
|
+
@client = MemCache.new [ @server ]
|
28
|
+
@client.servers.should == [ @server ]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should work if the instance is created with a Hash" do
|
32
|
+
@client = MemCache.new [ @server ], :namespace => 'test'
|
33
|
+
@client.servers.should == [ @server ]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should work with an explicit pool name" do
|
37
|
+
@client = MemCache.new([@server], :pool_name => 'new_pool')
|
38
|
+
@client.pool_name.should == 'new_pool'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "namespacing" do
|
43
|
+
before(:each) do
|
44
|
+
@ns = 'namespace'
|
45
|
+
@nsclient = MemCache.new [ @server ] , :namespace => @ns
|
46
|
+
@nsclient.flush_all
|
47
|
+
@nsclient.set "test", 333, 0
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should set and get values transparently" do
|
51
|
+
@nsclient.get("test").to_i.should == 333
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should set values to the given namespace" do
|
55
|
+
@client.get("#{@ns}:test").to_i.should == 333
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should not set a value without the given namespace" do
|
59
|
+
@client.get("test").to_i.should_not == 333
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should delete values in the given namespace" do
|
63
|
+
@nsclient.delete "test"
|
64
|
+
@nsclient.get("test").should be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should increment in the given namespace" do
|
68
|
+
@nsclient.incr("test").to_i.should == 334
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should decrement values in the given namespace" do
|
72
|
+
@nsclient.decr("test").should == 332
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "after setting a value to MemCache" do
|
77
|
+
before(:each) do
|
78
|
+
@client.set 'key', 'value'
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be able to retrieve the value" do
|
82
|
+
@client.get('key').should == 'value'
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should not be able to retrieve the value after deleting" do
|
86
|
+
@client.delete('key')
|
87
|
+
@client.get('key').should be_nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should not be able to retrieve the value after flushing everything" do
|
91
|
+
@client.flush_all
|
92
|
+
@client.get("key").should be_nil
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should work exactly the same if the []= operator were used" do
|
96
|
+
@client['key'] = 'val'
|
97
|
+
@client.get('key').should == 'val'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "using the Hash notation" do
|
102
|
+
before :each do
|
103
|
+
@client['key'] = 'value'
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should be able to retrieve the value using []" do
|
107
|
+
@client['key'].should == 'value'
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should be able to retrieve the value using get" do
|
111
|
+
@client.get('key').should == 'value'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#stats" do
|
116
|
+
it "should return a hash" do
|
117
|
+
@client.stats.should be_instance_of(Hash)
|
118
|
+
end
|
119
|
+
|
120
|
+
# it "should return 0 for curr_items" do
|
121
|
+
# @client.stats[@server]['curr_items'].should == 0
|
122
|
+
# end
|
123
|
+
|
124
|
+
it "should return a float for rusage_system and rusage_user" do
|
125
|
+
@client.stats[@server]['rusage_system'].should be_instance_of(Float)
|
126
|
+
@client.stats[@server]['rusage_user'].should be_instance_of(Float)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should return a String for version" do
|
130
|
+
@client.stats[@server]['version'].should be_instance_of(String)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#incr" do
|
136
|
+
|
137
|
+
it "should increment a value by 1 without a second parameter" do
|
138
|
+
@client.set 'incr', 100, 0
|
139
|
+
@client.incr 'incr'
|
140
|
+
@client.get('incr').to_i.should == 101
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should increment a value by a given second parameter" do
|
144
|
+
@client.set 'incr', 100, 0
|
145
|
+
@client.incr 'incr', 20
|
146
|
+
@client.get('incr').to_i.should == 120
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "#decr" do
|
151
|
+
|
152
|
+
it "should decrement a value by 1 without a second parameter" do
|
153
|
+
@client.set 'decr', 100, 0
|
154
|
+
@client.decr 'decr'
|
155
|
+
@client.get('decr').to_i.should == 99
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should decrement a value by a given second parameter" do
|
159
|
+
@client.set 'decr', 100, 0
|
160
|
+
@client.decr 'decr', 20
|
161
|
+
@client.get('decr').to_i.should == 80
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "with Ruby Objects" do
|
166
|
+
it "should be able to transparently set and get equivalent Ruby objects" do
|
167
|
+
obj = { :test => :hi }
|
168
|
+
@client.set('obj', obj)
|
169
|
+
@client.get('obj').should == obj
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe "using set with an expiration" do
|
174
|
+
it "should make a value unretrievable if the expiry is set to a negative value" do
|
175
|
+
@client.set('key', 'val', -1)
|
176
|
+
@client.get('key').should be_nil
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should make a value retrievable for only the amount of time if a value is given" do
|
180
|
+
@client.set('key', 'val', 2)
|
181
|
+
@client.get('key').should == 'val'
|
182
|
+
sleep(3)
|
183
|
+
@client.get('key').should be_nil
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "#get_multi" do
|
188
|
+
it "should be implemented"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fredjean-jruby-memcache-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Abhi Yerra
|
8
|
+
- Ikai Lan
|
9
|
+
- Frederic Jean
|
10
|
+
- Lenny Day-Reynolds
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
|
15
|
+
date: 2009-04-13 00:00:00 -07:00
|
16
|
+
default_executable:
|
17
|
+
dependencies: []
|
18
|
+
|
19
|
+
description: A drop in replacement for Ruby's memcache-client.
|
20
|
+
email: abhi@traytwo.com
|
21
|
+
executables: []
|
22
|
+
|
23
|
+
extensions: []
|
24
|
+
|
25
|
+
extra_rdoc_files:
|
26
|
+
- README
|
27
|
+
files:
|
28
|
+
- Rakefile
|
29
|
+
- VERSION.yml
|
30
|
+
- lib/java/java_memcached-release_2.0.1.jar
|
31
|
+
- lib/memcache.rb
|
32
|
+
- spec/jruby_memcache_spec.rb
|
33
|
+
- README
|
34
|
+
has_rdoc: true
|
35
|
+
homepage: http://github.com/abhiyerra/jruby-memcache-client
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options:
|
38
|
+
- --charset=UTF-8
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.2.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: A drop in replacement for Ruby's memcache-client.
|
60
|
+
test_files:
|
61
|
+
- spec/jruby_memcache_spec.rb
|