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 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.
@@ -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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 5
3
+ :major: 1
4
+ :patch: 0
@@ -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