abhiyerra-jruby-memcache-client 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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 abhiyerra-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.
@@ -0,0 +1,13 @@
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"]
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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 5
3
+ :major: 1
4
+ :patch: 0
@@ -0,0 +1,225 @@
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
+ def set(key, value, expiry = 0, raw = false)
140
+ value = marshal_value(value) unless raw
141
+ key = make_cache_key(key)
142
+ if expiry == 0
143
+ @client.set key, value
144
+ else
145
+ @client.set key, value, expiration(expiry)
146
+ end
147
+ end
148
+
149
+ def add(key, value, expiry = 0, raw = false)
150
+ value = marshal_value(value) unless raw
151
+ if expiry == 0
152
+ @client.add make_cache_key(key), value
153
+ else
154
+ @client.add make_cache_key(key), value, expiration(expiry)
155
+ end
156
+ end
157
+
158
+ def delete(key, expiry = 0)
159
+ @client.delete(make_cache_key(key))
160
+ end
161
+
162
+ def incr(key, amount = 1)
163
+ value = get(key) || 0
164
+ value += amount
165
+ set key, value
166
+ value
167
+ end
168
+
169
+ def decr(key, amount = 1)
170
+ value = get(key) || 0
171
+ value -= amount
172
+ set key, value
173
+ value
174
+ end
175
+
176
+ def []=(key, value)
177
+ set key, value
178
+ end
179
+
180
+ def [](key)
181
+ get key
182
+ end
183
+
184
+
185
+ def flush_all
186
+ @client.flushAll
187
+ end
188
+
189
+ def stats
190
+ stats_hash = {}
191
+ @client.stats.each do |server, stats|
192
+ stats_hash[server] = Hash.new
193
+ stats.each do |key, value|
194
+ unless key == 'version'
195
+ value = value.to_f
196
+ value = value.to_i if value == value.ceil
197
+ end
198
+ stats_hash[server][key] = value
199
+ end
200
+ end
201
+ stats_hash
202
+ end
203
+
204
+ protected
205
+ def make_cache_key(key)
206
+ if namespace.nil? then
207
+ key
208
+ else
209
+ "#{@namespace}:#{key}"
210
+ end
211
+ end
212
+
213
+ def expiration(expiry)
214
+ java.util.Date.new((Time.now.to_i + expiry) * 1000)
215
+ end
216
+
217
+ def marshal_value(value)
218
+ marshal_bytes = Marshal.dump(value).to_java_bytes
219
+ java.lang.String.new(marshal_bytes, MARSHALLING_CHARSET)
220
+ end
221
+
222
+ class MemCacheError < RuntimeError; end
223
+
224
+ end
225
+
@@ -0,0 +1,179 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require File.dirname(__FILE__) + '/../lib/j_mem_cache'
4
+
5
+ describe JMemCache do
6
+ before(:all) do
7
+ @server = "127.0.0.1:11211"
8
+ @client = JMemCache.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 = JMemCache.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 = JMemCache.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 = JMemCache.new [ @server ], :namespace => 'test'
33
+ @client.servers.should == [ @server ]
34
+ end
35
+
36
+ it "should work with an explicit pool name" do
37
+ @client = JMemCache.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 = JMemCache.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
+
100
+ end
101
+
102
+
103
+ describe "#stats" do
104
+ it "should return a hash" do
105
+ @client.stats.should be_instance_of(Hash)
106
+ end
107
+
108
+ # it "should return 0 for curr_items" do
109
+ # @client.stats[@server]['curr_items'].should == 0
110
+ # end
111
+
112
+ it "should return a float for rusage_system and rusage_user" do
113
+ @client.stats[@server]['rusage_system'].should be_instance_of(Float)
114
+ @client.stats[@server]['rusage_user'].should be_instance_of(Float)
115
+ end
116
+
117
+ it "should return a String for version" do
118
+ @client.stats[@server]['version'].should be_instance_of(String)
119
+ end
120
+
121
+ end
122
+
123
+ describe "#incr" do
124
+
125
+ it "should increment a value by 1 without a second parameter" do
126
+ @client.set 'incr', 100, 0
127
+ @client.incr 'incr'
128
+ @client.get('incr').to_i.should == 101
129
+ end
130
+
131
+ it "should increment a value by a given second parameter" do
132
+ @client.set 'incr', 100, 0
133
+ @client.incr 'incr', 20
134
+ @client.get('incr').to_i.should == 120
135
+ end
136
+ end
137
+
138
+ describe "#decr" do
139
+
140
+ it "should decrement a value by 1 without a second parameter" do
141
+ @client.set 'decr', 100, 0
142
+ @client.decr 'decr'
143
+ @client.get('decr').to_i.should == 99
144
+ end
145
+
146
+ it "should decrement a value by a given second parameter" do
147
+ @client.set 'decr', 100, 0
148
+ @client.decr 'decr', 20
149
+ @client.get('decr').to_i.should == 80
150
+ end
151
+ end
152
+
153
+ describe "with Ruby Objects" do
154
+ it "should be able to transparently set and get equivalent Ruby objects" do
155
+ obj = { :test => :hi }
156
+ @client.set('obj', obj)
157
+ @client.get('obj').should == obj
158
+ end
159
+ end
160
+
161
+ describe "using set with an expiration" do
162
+ it "should make a value unretrievable if the expiry is set to a negative value" do
163
+ @client.set('key', 'val', -1)
164
+ @client.get('key').should be_nil
165
+ end
166
+
167
+ it "should make a value retrievable for only the amount of time if a value is given" do
168
+ @client.set('key', 'val', 2)
169
+ @client.get('key').should == 'val'
170
+ sleep(3)
171
+ @client.get('key').should be_nil
172
+ end
173
+ end
174
+
175
+ describe "#get_multi" do
176
+ it "should be implemented"
177
+ end
178
+ end
179
+
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: abhiyerra-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
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-04-13 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: A drop in replacement for Ruby's memcache-client.
18
+ email: abhi@traytwo.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README
25
+ files:
26
+ - Rakefile
27
+ - VERSION.yml
28
+ - lib/java/java_memcached-release_2.0.1.jar
29
+ - lib/memcache.rb
30
+ - spec/jruby_memcache_spec.rb
31
+ - README
32
+ has_rdoc: true
33
+ homepage: http://github.com/abhiyerra/jruby-memcache-client
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --charset=UTF-8
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: A drop in replacement for Ruby's memcache-client.
58
+ test_files:
59
+ - spec/jruby_memcache_spec.rb