abhiyerra-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 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