dalli 1.1.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of dalli might be problematic. Click here for more details.

data/lib/dalli.rb CHANGED
@@ -5,12 +5,6 @@ require 'dalli/socket'
5
5
  require 'dalli/version'
6
6
  require 'dalli/options'
7
7
 
8
- unless ''.respond_to?(:bytesize)
9
- class String
10
- alias_method :bytesize, :size
11
- end
12
- end
13
-
14
8
  module Dalli
15
9
  # generic error
16
10
  class DalliError < RuntimeError; end
data/lib/dalli/client.rb CHANGED
@@ -14,40 +14,20 @@ module Dalli
14
14
  # Note that the <tt>MEMCACHE_SERVERS</tt> environment variable will override the servers parameter for use
15
15
  # in managed environments like Heroku.
16
16
  #
17
- # You can also provide a Unix socket as an argument, for example:
18
- #
19
- # Dalli::Client.new("/tmp/memcached.sock")
20
- #
21
- # Initial testing shows that Unix sockets are about twice as fast as TCP sockets
22
- # but Unix sockets only work on localhost.
23
- #
24
17
  # Options:
18
+ # - :namespace - prepend each key with this value to provide simple namespacing.
25
19
  # - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
26
20
  # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
27
21
  # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults to 0 or forever
28
- # - :compression - defaults to false, if true Dalli will compress values larger than 100 bytes before
22
+ # - :compress - defaults to false, if true Dalli will compress values larger than 100 bytes before
29
23
  # sending them to memcached.
30
- # - :async - assume its running inside the EM reactor. Requires em-synchrony to be installed. Default: false.
31
24
  #
32
25
  def initialize(servers=nil, options={})
33
26
  @servers = env_servers || servers || '127.0.0.1:11211'
34
- @options = { :expires_in => 0 }.merge(options)
35
- self.extend(Dalli::Client::MemcacheClientCompatibility) if Dalli::Client.compatibility_mode
27
+ @options = normalize_options(options)
36
28
  @ring = nil
37
29
  end
38
30
 
39
- ##
40
- # Turn on compatibility mode, which mixes in methods in memcache_client_compatibility.rb
41
- # This value is set to true in memcache-client.rb.
42
- def self.compatibility_mode
43
- @compatibility_mode ||= false
44
- end
45
-
46
- def self.compatibility_mode=(compatibility_mode)
47
- require 'dalli/compatibility'
48
- @compatibility_mode = compatibility_mode
49
- end
50
-
51
31
  #
52
32
  # The standard memcached instruction set
53
33
  #
@@ -178,7 +158,6 @@ module Dalli
178
158
  ring.servers.map { |s| s.request(:flush, time += delay) }
179
159
  end
180
160
 
181
- # deprecated, please use #flush.
182
161
  alias_method :flush_all, :flush
183
162
 
184
163
  ##
@@ -229,6 +208,14 @@ module Dalli
229
208
  values
230
209
  end
231
210
 
211
+ ##
212
+ # Reset stats for each server.
213
+ def reset_stats
214
+ ring.servers.map do |server|
215
+ server.alive? ? server.request(:reset_stats) : nil
216
+ end
217
+ end
218
+
232
219
  ##
233
220
  # Close our connection to each server.
234
221
  # If you perform another operation after this, the connections will be re-established.
@@ -270,10 +257,9 @@ module Dalli
270
257
  end
271
258
 
272
259
  def validate_key(key)
260
+ raise ArgumentError, "key cannot be blank" if !key || key.length == 0
273
261
  raise ArgumentError, "illegal character in key #{key}" if key.respond_to?(:ascii_only?) && !key.ascii_only?
274
- raise ArgumentError, "illegal character in key #{key}" if key =~ /\s/
275
- raise ArgumentError, "illegal character in key #{key}" if key =~ /[\x00-\x20\x80-\xFF]/
276
- raise ArgumentError, "key cannot be blank" if key.nil? || key.strip.size == 0
262
+ raise ArgumentError, "illegal character in key #{key}" if key =~ /[\x00-\x20\x7F-\xFF]/
277
263
  raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
278
264
  end
279
265
 
@@ -282,7 +268,16 @@ module Dalli
282
268
  end
283
269
 
284
270
  def key_without_namespace(key)
285
- @options[:namespace] ? key.gsub(%r(\A#{@options[:namespace]}:), '') : key
271
+ @options[:namespace] ? key.sub(%r(\A#{@options[:namespace]}:), '') : key
272
+ end
273
+
274
+ def normalize_options(opts)
275
+ if opts[:compression]
276
+ Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
277
+ opts[:compress] = opts.delete(:compression)
278
+ end
279
+ opts[:expires_in] ||= 0
280
+ opts
286
281
  end
287
282
  end
288
283
  end
data/lib/dalli/server.rb CHANGED
@@ -22,7 +22,7 @@ module Dalli
22
22
  :value_max_bytes => 1024 * 1024,
23
23
  :username => nil,
24
24
  :password => nil,
25
- :async => false,
25
+ :keepalive => true
26
26
  }
27
27
 
28
28
  def initialize(attribs, options = {})
@@ -92,10 +92,6 @@ module Dalli
92
92
 
93
93
  private
94
94
 
95
- def is_unix_socket?(string)
96
- !!(/^\/(.+)$/ =~ string)
97
- end
98
-
99
95
  def failure!
100
96
  Dalli.logger.info { "#{hostname}:#{port} failed (count: #{@fail_count})" }
101
97
 
@@ -238,6 +234,12 @@ module Dalli
238
234
  keyvalue_response
239
235
  end
240
236
 
237
+ def reset_stats
238
+ req = [REQUEST, OPCODES[:stat], 'reset'.bytesize, 0, 0, 0, 'reset'.bytesize, 0, 0, 'reset'].pack(FORMAT[:stat])
239
+ write(req)
240
+ generic_response
241
+ end
242
+
241
243
  def cas(key)
242
244
  req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
243
245
  write(req)
@@ -275,7 +277,7 @@ module Dalli
275
277
  value.to_s
276
278
  end
277
279
  compressed = false
278
- if @options[:compression] && value.bytesize >= COMPRESSION_MIN_SIZE
280
+ if @options[:compress] && value.bytesize >= COMPRESSION_MIN_SIZE
279
281
  value = Zlib::Deflate.deflate(value)
280
282
  compressed = true
281
283
  end
@@ -386,14 +388,7 @@ module Dalli
386
388
  Dalli.logger.debug { "Dalli::Server#connect #{hostname}:#{port}" }
387
389
 
388
390
  begin
389
- if @hostname =~ /^\//
390
- @sock = USocket.new(hostname)
391
- elsif options[:async]
392
- raise Dalli::DalliError, "EM support not enabled, as em-synchrony is not installed." if not defined?(AsyncSocket)
393
- @sock = AsyncSocket.open(hostname, port, :timeout => options[:socket_timeout])
394
- else
395
- @sock = KSocket.open(hostname, port, :timeout => options[:socket_timeout])
396
- end
391
+ @sock = KSocket.open(hostname, port, options)
397
392
  @version = version # trigger actual connect
398
393
  sasl_authentication if need_auth?
399
394
  up!
data/lib/dalli/socket.rb CHANGED
@@ -6,16 +6,17 @@ begin
6
6
  attr_accessor :options
7
7
 
8
8
  def kgio_wait_readable
9
- IO.select([self], nil, nil, options[:timeout]) || raise(Timeout::Error, "IO timeout")
9
+ IO.select([self], nil, nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
10
10
  end
11
11
 
12
12
  def kgio_wait_writable
13
- IO.select(nil, [self], nil, options[:timeout]) || raise(Timeout::Error, "IO timeout")
13
+ IO.select(nil, [self], nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
14
14
  end
15
15
 
16
16
  def self.open(host, port, options = {})
17
17
  addr = Socket.pack_sockaddr_in(port, host)
18
18
  sock = start(addr)
19
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
19
20
  sock.options = options
20
21
  sock.kgio_wait_writable
21
22
  sock
@@ -34,8 +35,6 @@ begin
34
35
 
35
36
  end
36
37
 
37
-
38
-
39
38
  if ::Kgio.respond_to?(:wait_readable=)
40
39
  ::Kgio.wait_readable = :kgio_wait_readable
41
40
  ::Kgio.wait_writable = :kgio_wait_writable
@@ -44,127 +43,34 @@ begin
44
43
  rescue LoadError
45
44
 
46
45
  puts "Using standard socket IO (#{RUBY_DESCRIPTION})" if defined?($TESTING) && $TESTING
47
- if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
48
-
49
- class Dalli::Server::KSocket < TCPSocket
50
- attr_accessor :options
46
+ class Dalli::Server::KSocket < TCPSocket
47
+ attr_accessor :options
51
48
 
52
- def self.open(host, port, options = {})
49
+ def self.open(host, port, options = {})
50
+ Timeout.timeout(options[:socket_timeout]) do
53
51
  sock = new(host, port)
52
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
54
53
  sock.options = { :host => host, :port => port }.merge(options)
55
54
  sock
56
55
  end
57
-
58
- def readfull(count)
59
- value = ''
60
- begin
61
- loop do
62
- value << read_nonblock(count - value.bytesize)
63
- break if value.bytesize == count
64
- end
65
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
66
- if IO.select([self], nil, nil, options[:timeout])
67
- retry
68
- else
69
- raise Timeout::Error, "IO timeout: #{options.inspect}"
70
- end
71
- end
72
- value
73
- end
74
-
75
- end
76
-
77
- else
78
-
79
- class Dalli::Server::KSocket < Socket
80
- attr_accessor :options
81
-
82
- def self.open(host, port, options = {})
83
- # All this ugly code to ensure proper Socket connect timeout
84
- addr = Socket.getaddrinfo(host, nil)
85
- sock = new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
86
- sock.options = { :host => host, :port => port }.merge(options)
87
- begin
88
- sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
89
- rescue Errno::EINPROGRESS
90
- resp = IO.select(nil, [sock], nil, sock.options[:timeout])
91
- begin
92
- sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
93
- rescue Errno::EISCONN
94
- end
95
- end
96
- sock
97
- end
98
-
99
- def readfull(count)
100
- value = ''
101
- begin
102
- loop do
103
- value << sysread(count - value.bytesize)
104
- break if value.bytesize == count
105
- end
106
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
107
- if IO.select([self], nil, nil, options[:timeout])
108
- retry
109
- else
110
- raise Timeout::Error, "IO timeout: #{options.inspect}"
111
- end
112
- end
113
- value
114
- end
115
- end
116
-
117
- end
118
- end
119
-
120
- begin
121
- require 'em-synchrony'
122
- puts "Defining alternate em-synchrony socket IO" if defined?($TESTING) && $TESTING
123
-
124
- class Dalli::Server::AsyncSocket < EventMachine::Synchrony::TCPSocket
125
- # XXX. need to somehow implement the timeout option.
126
- # EM::Synchrony::TCPsocket does give you any timeouts. To implement it we'd have
127
- # to change that class to take a timeout parameter that it would then set on
128
- # the Deferrable objects it waits on.
129
- attr_accessor :options
130
-
131
- def self.open(host, port, options = {})
132
- sock = new(host, port)
133
- sock.options = { :host => host, :port => port }.merge(options)
134
- sock
135
56
  end
136
57
 
137
58
  def readfull(count)
138
59
  value = ''
139
- loop do
140
- value << read(count - value.bytesize)
141
- break if value.bytesize == count
60
+ begin
61
+ loop do
62
+ value << read_nonblock(count - value.bytesize)
63
+ break if value.bytesize == count
64
+ end
65
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
66
+ if IO.select([self], nil, nil, options[:socket_timeout])
67
+ retry
68
+ else
69
+ raise Timeout::Error, "IO timeout: #{options.inspect}"
70
+ end
142
71
  end
143
72
  value
144
73
  end
145
74
 
146
75
  end
147
-
148
- rescue LoadError
149
- puts "Could not define alternate em-synchrony socket IO" if defined?($TESTING) && $TESTING
150
- end
151
-
152
- require 'rbconfig'
153
- if RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
154
- class Dalli::Server::USocket
155
- def initialize(*args)
156
- raise Dalli::DalliError, "Unix sockets are not supported on Windows platform."
157
- end
158
- end
159
- else
160
- class Dalli::Server::USocket < UNIXSocket
161
- def readfull(count)
162
- value = ''
163
- loop do
164
- value << read(count - value.bytesize)
165
- break if value.bytesize == count
166
- end
167
- value
168
- end
169
- end
170
76
  end
data/lib/dalli/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '1.1.5'
2
+ VERSION = '2.0.0'
3
3
  end
data/test/helper.rb CHANGED
@@ -7,7 +7,6 @@ gem 'rails', WANT_RAILS_VERSION
7
7
  require 'rails'
8
8
  puts "Testing with Rails #{Rails.version}"
9
9
 
10
- require 'minitest/spec'
11
10
  require 'mini_shoulda'
12
11
  require 'minitest/pride'
13
12
  require 'minitest/autorun'
@@ -15,10 +15,6 @@ module MemcachedMock
15
15
  block.call server
16
16
  end
17
17
 
18
- def self.tmp_socket_path
19
- "#{Dir.pwd}/tmp.sock"
20
- end
21
-
22
18
  module Helper
23
19
  # Forks the current process and starts a new mock Memcached server on
24
20
  # port 22122.
@@ -71,14 +67,9 @@ module MemcachedMock
71
67
  end
72
68
 
73
69
  def memcached(port=19122, args='', options={})
74
- return unless supports_fork?
75
70
  Memcached.path ||= find_memcached
76
71
 
77
- cmd = if options[:unix]
78
- "#{Memcached.path}memcached #{args} -s #{MemcachedMock.tmp_socket_path}"
79
- else
80
- "#{Memcached.path}memcached #{args} -p #{port}"
81
- end
72
+ cmd = "#{Memcached.path}memcached #{args} -p #{port}"
82
73
 
83
74
  $started[port] ||= begin
84
75
  #puts "Starting: #{cmd}..."
@@ -94,11 +85,7 @@ module MemcachedMock
94
85
  sleep 0.1
95
86
  pid
96
87
  end
97
- if options[:unix]
98
- yield Dalli::Client.new(MemcachedMock.tmp_socket_path)
99
- else
100
- yield Dalli::Client.new(["localhost:#{port}", "127.0.0.1:#{port}"], options)
101
- end
88
+ yield Dalli::Client.new(["localhost:#{port}", "127.0.0.1:#{port}"], options)
102
89
  end
103
90
 
104
91
  def supports_fork?
@@ -8,21 +8,15 @@ describe 'ActiveSupport' do
8
8
  with_activesupport do
9
9
  memcached do
10
10
  connect
11
- mvalue = @mc.fetch('somekeywithoutspaces', :expires_in => 1.second) { 123 }
12
11
  dvalue = @dalli.fetch('someotherkeywithoutspaces', :expires_in => 1.second) { 123 }
13
12
  assert_equal 123, dvalue
14
- assert_equal mvalue, dvalue
15
13
 
16
14
  o = Object.new
17
15
  o.instance_variable_set :@foo, 'bar'
18
- mvalue = @mc.fetch(rand_key, :raw => true) { o }
19
16
  dvalue = @dalli.fetch(rand_key, :raw => true) { o }
20
- assert_equal mvalue, dvalue
21
- assert_equal o, mvalue
17
+ assert_equal o, dvalue
22
18
 
23
- mvalue = @mc.fetch(rand_key) { o }
24
19
  dvalue = @dalli.fetch(rand_key) { o }
25
- assert_equal mvalue, dvalue
26
20
  assert_equal o, dvalue
27
21
  end
28
22
  end
@@ -32,9 +26,8 @@ describe 'ActiveSupport' do
32
26
  with_activesupport do
33
27
  memcached do
34
28
  connect
35
- dvalue = @mc.fetch('some key with spaces', :expires_in => 1.second) { 123 }
36
- mvalue = @dalli.fetch('some other key with spaces', :expires_in => 1.second) { 123 }
37
- assert_equal mvalue, dvalue
29
+ dvalue = @dalli.fetch('some key with spaces', :expires_in => 1.second) { 123 }
30
+ assert_equal 123, dvalue
38
31
  end
39
32
  end
40
33
  end
@@ -45,14 +38,10 @@ describe 'ActiveSupport' do
45
38
  connect
46
39
  x = rand_key
47
40
  y = rand_key
48
- assert_equal({}, @mc.read_multi(x, y))
49
41
  assert_equal({}, @dalli.read_multi(x, y))
50
42
  @dalli.write(x, '123')
51
43
  @dalli.write(y, 123)
52
- @mc.write(x, '123')
53
- @mc.write(y, 123)
54
44
  assert_equal({ x => '123', y => 123 }, @dalli.read_multi(x, y))
55
- assert_equal({ x => '123', y => 123 }, @mc.read_multi(x, y))
56
45
  end
57
46
  end
58
47
  end
@@ -63,14 +52,10 @@ describe 'ActiveSupport' do
63
52
  connect
64
53
  x = rand_key
65
54
  y = rand_key
66
- assert_equal({}, @mc.read_multi([x, y]))
67
55
  assert_equal({}, @dalli.read_multi([x, y]))
68
56
  @dalli.write(x, '123')
69
57
  @dalli.write(y, 123)
70
- @mc.write(x, '123')
71
- @mc.write(y, 123)
72
58
  assert_equal({ x => '123', y => 123 }, @dalli.read_multi([x, y]))
73
- assert_equal({}, @mc.read_multi([x,y]))
74
59
  end
75
60
  end
76
61
  end
@@ -79,10 +64,6 @@ describe 'ActiveSupport' do
79
64
  with_activesupport do
80
65
  memcached do
81
66
  connect
82
- @mc.write("abc", 5, :raw => true)
83
- @mc.write("cba", 5, :raw => true)
84
- assert_equal({'abc' => '5', 'cba' => '5' }, @mc.read_multi("abc", "cba"))
85
-
86
67
  @dalli.write("abc", 5, :raw => true)
87
68
  @dalli.write("cba", 5, :raw => true)
88
69
  assert_equal({'abc' => '5', 'cba' => '5' }, @dalli.read_multi("abc", "cba"))
@@ -96,20 +77,14 @@ describe 'ActiveSupport' do
96
77
  connect
97
78
  x = rand_key
98
79
  y = rand_key
99
- assert_nil @mc.read(x)
100
80
  assert_nil @dalli.read(y)
101
- mres = @mc.write(x, 123)
102
81
  dres = @dalli.write(y, 123)
103
- assert_equal mres, dres
82
+ assert_equal true, dres
104
83
 
105
- mres = @mc.read(x)
106
84
  dres = @dalli.read(y)
107
- assert_equal mres, dres
108
85
  assert_equal 123, dres
109
86
 
110
- mres = @mc.delete(x)
111
87
  dres = @dalli.delete(y)
112
- assert_equal mres, dres
113
88
  assert_equal true, dres
114
89
  end
115
90
  end
@@ -119,22 +94,12 @@ describe 'ActiveSupport' do
119
94
  with_activesupport do
120
95
  memcached do
121
96
  connect
122
- assert_equal true, @mc.write('counter', 0, :raw => true)
123
- assert_equal 1, @mc.increment('counter')
124
- assert_equal 2, @mc.increment('counter')
125
- assert_equal 1, @mc.decrement('counter')
126
- assert_equal "1", @mc.read('counter', :raw => true)
127
-
128
97
  assert_equal true, @dalli.write('counter', 0, :raw => true)
129
98
  assert_equal 1, @dalli.increment('counter')
130
99
  assert_equal 2, @dalli.increment('counter')
131
100
  assert_equal 1, @dalli.decrement('counter')
132
101
  assert_equal "1", @dalli.read('counter', :raw => true)
133
102
 
134
- assert_equal 0, @mc.increment('counterX')
135
- assert_equal 0, @mc.increment('counterX')
136
- assert_equal nil, @mc.read('counterX')
137
-
138
103
  assert_equal 1, @dalli.increment('counterX')
139
104
  assert_equal 2, @dalli.increment('counterX')
140
105
  assert_equal 2, @dalli.read('counterX', :raw => true).to_i
@@ -146,18 +111,12 @@ describe 'ActiveSupport' do
146
111
  with_activesupport do
147
112
  memcached do
148
113
  connect
149
- ms = @mc.stats
150
114
  ds = @dalli.stats
151
- assert_equal ms.keys.sort, ds.keys.sort
152
- assert_equal ms[ms.keys.first].keys.sort, ds[ds.keys.first].keys.sort
115
+ assert_equal 1, ds.keys.size
116
+ assert ds[ds.keys.first].keys.size > 0
153
117
 
154
118
  assert_equal true, @dalli.write(:foo, 'a')
155
- assert_equal true, @mc.write(:foo, 'a')
156
-
157
- assert_equal true, @mc.exist?(:foo)
158
119
  assert_equal true, @dalli.exist?(:foo)
159
-
160
- assert_equal false, @mc.exist?(:bar)
161
120
  assert_equal false, @dalli.exist?(:bar)
162
121
 
163
122
  @dalli.reset
@@ -172,9 +131,7 @@ describe 'ActiveSupport' do
172
131
  connect
173
132
  key = "fooƒ"
174
133
  value = 'bafƒ'
175
- # assert_equal true, @mc.write(key, value)
176
134
  assert_equal true, @dalli.write(key, value)
177
- # assert_equal true, @mc.read(key, value)
178
135
  assert_equal value, @dalli.read(key)
179
136
  end
180
137
  end
@@ -183,15 +140,15 @@ describe 'ActiveSupport' do
183
140
  should 'normalize options as expected' do
184
141
  with_activesupport do
185
142
  memcached do
186
- @dalli = ActiveSupport::Cache::DalliStore.new('localhost:19122', :expires_in => 1, :race_condition_ttl => 1)
187
- assert_equal 2, @dalli.instance_variable_get(:@data).instance_variable_get(:@options)[:expires_in]
143
+ @dalli = ActiveSupport::Cache::DalliStore.new('localhost:19122', :expires_in => 1, :namespace => 'foo', :compress => true)
144
+ assert_equal 1, @dalli.instance_variable_get(:@data).instance_variable_get(:@options)[:expires_in]
145
+ assert_equal 'foo', @dalli.instance_variable_get(:@data).instance_variable_get(:@options)[:namespace]
188
146
  end
189
147
  end
190
148
  end
191
149
 
192
150
  def connect
193
151
  @dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:19122', :expires_in => 10.seconds, :namespace => 'x')
194
- @mc = ActiveSupport::Cache.lookup_store(:mem_cache_store, 'localhost:19122', :expires_in => 10.seconds, :namespace => 'a')
195
152
  @dalli.clear
196
153
  end
197
154