dalli 2.0.3 → 2.0.4

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/History.md CHANGED
@@ -1,6 +1,16 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 2.0.4
5
+ =======
6
+
7
+ - Dalli no longer needs to be reset after Unicorn/Passenger fork [#208]
8
+ - Add option to re-raise errors rescued in the session and cache stores. [pitr, #200]
9
+ - DalliStore#fetch called the block if the cached value == false [#205]
10
+ - DalliStore should have accessible options [#195]
11
+ - Add silence and mute support for DalliStore [#207]
12
+ - Tracked down and fixed socket corruption due to Timeout [#146]
13
+
4
14
  2.0.3
5
15
  =======
6
16
 
data/README.md CHANGED
@@ -86,38 +86,9 @@ To use Dalli for Rails session storage, in `config/initializers/session_store.rb
86
86
  require 'action_dispatch/middleware/session/dalli_store'
87
87
  Rails.application.config.session_store :dalli_store, :memcache_server => ['host1', 'host2'], :namespace => 'sessions', :key => '_foundation_session', :expire_after => 30.minutes
88
88
 
89
- Dalli does not support Rails 2.x any longer.
90
-
91
-
92
- Usage with Passenger
93
- ------------------------
94
-
95
- Put this at the bottom of `config/environment.rb`:
96
-
97
- if defined?(PhusionPassenger)
98
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
99
- # Reset Rails's object cache
100
- # Only works with DalliStore
101
- Rails.cache.reset if forked
89
+ Both cache and session stores support `:raise_errors` parameter, which propagates exceptions (e.g. if all memcache servers are down) instead of silently hiding errors.
102
90
 
103
- # Reset Rails's session store
104
- # If you know a cleaner way to find the session store instance, please let me know
105
- ObjectSpace.each_object(ActionDispatch::Session::DalliStore) { |obj| obj.reset }
106
- end
107
- end
108
-
109
- Usage with Unicorn
110
- -----------------------
111
-
112
- Modify the `after_fork` block in your unicorn config file:
113
-
114
- after\_fork do |server, worker|
115
- Rails.cache.reset if Rails.cache.respond_to?(:reset)
116
-
117
- # Reset Rails's session store
118
- # If you know a cleaner way to find the session store instance, please let me know
119
- ObjectSpace.each_object(ActionDispatch::Session::DalliStore) { |obj| obj.reset }
120
- end
91
+ Dalli does not support Rails 2.x any longer.
121
92
 
122
93
 
123
94
  Configuration
@@ -151,6 +122,10 @@ Features and Changes
151
122
 
152
123
  By default, Dalli is thread-safe. Disable thread-safety at your own peril.
153
124
 
125
+ Dalli does not need anything special in Unicorn/Passenger since 2.0.4.
126
+ It will detect sockets shared with child processes and gracefully reopen the
127
+ socket.
128
+
154
129
  Note that Dalli does not require ActiveSupport or Rails. You can safely use it in your own Ruby projects.
155
130
 
156
131
 
@@ -20,6 +20,8 @@ module ActionDispatch
20
20
  end
21
21
  @namespace = @default_options[:namespace]
22
22
 
23
+ @raise_errors = !!@default_options[:raise_errors]
24
+
23
25
  super
24
26
  end
25
27
 
@@ -49,6 +51,7 @@ module ActionDispatch
49
51
  sid
50
52
  rescue Dalli::DalliError
51
53
  Rails.logger.warn("Session::DalliStore#set: #{$!.message}")
54
+ raise if @raise_errors
52
55
  false
53
56
  end
54
57
 
@@ -57,6 +60,7 @@ module ActionDispatch
57
60
  @pool.delete(session_id)
58
61
  rescue Dalli::DalliError
59
62
  Rails.logger.warn("Session::DalliStore#destroy_session: #{$!.message}")
63
+ raise if @raise_errors
60
64
  end
61
65
  return nil if options[:drop]
62
66
  generate_sid
@@ -68,6 +72,7 @@ module ActionDispatch
68
72
  end
69
73
  rescue Dalli::DalliError
70
74
  Rails.logger.warn("Session::DalliStore#destroy: #{$!.message}")
75
+ raise if @raise_errors
71
76
  false
72
77
  end
73
78
 
@@ -6,6 +6,23 @@ module ActiveSupport
6
6
  module Cache
7
7
  class DalliStore
8
8
 
9
+ attr_reader :silence, :options
10
+ alias_method :silence?, :silence
11
+
12
+ # Silence the logger.
13
+ def silence!
14
+ @silence = true
15
+ self
16
+ end
17
+
18
+ # Silence the logger within a block.
19
+ def mute
20
+ previous_silence, @silence = defined?(@silence) && @silence, true
21
+ yield
22
+ ensure
23
+ @silence = previous_silence
24
+ end
25
+
9
26
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/
10
27
 
11
28
  # Creates a new DalliStore object, with the given memcached server
@@ -20,9 +37,11 @@ module ActiveSupport
20
37
  def initialize(*addresses)
21
38
  addresses = addresses.flatten
22
39
  options = addresses.extract_options!
23
- options[:compress] ||= options[:compression]
40
+ @options = options.dup
41
+ @options[:compress] ||= @options[:compression]
42
+ @raise_errors = !!@options[:raise_errors]
24
43
  addresses << 'localhost:11211' if addresses.empty?
25
- @data = Dalli::Client.new(addresses, options)
44
+ @data = Dalli::Client.new(addresses, @options)
26
45
  end
27
46
 
28
47
  def fetch(name, options=nil)
@@ -35,7 +54,7 @@ module ActiveSupport
35
54
  end
36
55
  end
37
56
 
38
- if entry
57
+ if !entry.nil?
39
58
  instrument(:fetch_hit, name, options) { |payload| }
40
59
  entry
41
60
  else
@@ -68,7 +87,7 @@ module ActiveSupport
68
87
 
69
88
  def exist?(name, options=nil)
70
89
  options ||= {}
71
- !!read_entry(name, options)
90
+ !read_entry(name, options).nil?
72
91
  end
73
92
 
74
93
  def delete(name, options=nil)
@@ -107,6 +126,7 @@ module ActiveSupport
107
126
  end
108
127
  rescue Dalli::DalliError => e
109
128
  logger.error("DalliError: #{e.message}") if logger
129
+ raise if @raise_errors
110
130
  nil
111
131
  end
112
132
 
@@ -124,6 +144,7 @@ module ActiveSupport
124
144
  end
125
145
  rescue Dalli::DalliError => e
126
146
  logger.error("DalliError: #{e.message}") if logger
147
+ raise if @raise_errors
127
148
  nil
128
149
  end
129
150
 
@@ -151,6 +172,7 @@ module ActiveSupport
151
172
  entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry
152
173
  rescue Dalli::DalliError => e
153
174
  logger.error("DalliError: #{e.message}") if logger
175
+ raise if @raise_errors
154
176
  nil
155
177
  end
156
178
 
@@ -161,6 +183,7 @@ module ActiveSupport
161
183
  @data.send(method, escape(key), value, expires_in, options)
162
184
  rescue Dalli::DalliError => e
163
185
  logger.error("DalliError: #{e.message}") if logger
186
+ raise if @raise_errors
164
187
  false
165
188
  end
166
189
 
@@ -169,6 +192,7 @@ module ActiveSupport
169
192
  @data.delete(escape(key))
170
193
  rescue Dalli::DalliError => e
171
194
  logger.error("DalliError: #{e.message}") if logger
195
+ raise if @raise_errors
172
196
  false
173
197
  end
174
198
 
@@ -195,7 +219,7 @@ module ActiveSupport
195
219
  end
196
220
 
197
221
  def log(operation, key, options=nil)
198
- return unless logger && logger.debug?
222
+ return unless logger && logger.debug? && !silence?
199
223
  logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
200
224
  end
201
225
 
data/lib/dalli/server.rb CHANGED
@@ -37,10 +37,12 @@ module Dalli
37
37
  @options = DEFAULTS.merge(options)
38
38
  @sock = nil
39
39
  @msg = nil
40
+ @pid = nil
40
41
  end
41
42
 
42
43
  # Chokepoint method for instrumentation
43
44
  def request(op, *args)
45
+ verify_state
44
46
  raise Dalli::NetworkError, "#{hostname}:#{port} is down: #{@error} #{@msg}" unless alive?
45
47
  begin
46
48
  send(op, *args)
@@ -80,6 +82,8 @@ module Dalli
80
82
  return unless @sock
81
83
  @sock.close rescue nil
82
84
  @sock = nil
85
+ @pid = nil
86
+ @inprogress = false
83
87
  end
84
88
 
85
89
  def lock!
@@ -92,6 +96,11 @@ module Dalli
92
96
 
93
97
  private
94
98
 
99
+ def verify_state
100
+ failure! if @inprogress
101
+ failure! if @pid && @pid != Process.pid
102
+ end
103
+
95
104
  def failure!
96
105
  Dalli.logger.info { "#{hostname}:#{port} failed (count: #{@fail_count})" }
97
106
 
@@ -368,19 +377,23 @@ module Dalli
368
377
 
369
378
  def write(bytes)
370
379
  begin
371
- @sock.write(bytes)
380
+ @inprogress = true
381
+ result = @sock.write(bytes)
382
+ @inprogress = false
383
+ result
372
384
  rescue SystemCallError, Timeout::Error
373
385
  failure!
374
- retry
375
386
  end
376
387
  end
377
388
 
378
389
  def read(count)
379
390
  begin
380
- @sock.readfull(count)
391
+ @inprogress = true
392
+ data = @sock.readfull(count)
393
+ @inprogress = false
394
+ data
381
395
  rescue SystemCallError, Timeout::Error, EOFError
382
396
  failure!
383
- retry
384
397
  end
385
398
  end
386
399
 
@@ -388,6 +401,7 @@ module Dalli
388
401
  Dalli.logger.debug { "Dalli::Server#connect #{hostname}:#{port}" }
389
402
 
390
403
  begin
404
+ @pid = Process.pid
391
405
  @sock = KSocket.open(hostname, port, options)
392
406
  @version = version # trigger actual connect
393
407
  sasl_authentication if need_auth?
@@ -397,7 +411,6 @@ module Dalli
397
411
  rescue SystemCallError, Timeout::Error, EOFError, SocketError
398
412
  # SocketError = DNS resolution failure
399
413
  failure!
400
- retry
401
414
  end
402
415
  end
403
416
 
data/lib/dalli/socket.rb CHANGED
@@ -16,6 +16,7 @@ begin
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::IPPROTO_TCP, Socket::TCP_NODELAY, true)
19
20
  sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
20
21
  sock.options = options
21
22
  sock.kgio_wait_writable
@@ -49,6 +50,7 @@ rescue LoadError
49
50
  def self.open(host, port, options = {})
50
51
  Timeout.timeout(options[:socket_timeout]) do
51
52
  sock = new(host, port)
53
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
52
54
  sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
53
55
  sock.options = { :host => host, :port => port }.merge(options)
54
56
  sock
data/lib/dalli/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '2.0.3'
2
+ VERSION = '2.0.4'
3
3
  end
@@ -4,7 +4,23 @@ require 'helper'
4
4
  describe 'ActiveSupport' do
5
5
  context 'active_support caching' do
6
6
 
7
- should 'dalli_store operations should handle nil options' do
7
+ should 'have accessible options' do
8
+ @dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:19122', :expires_in => 5.minutes, :frob => 'baz')
9
+ assert_equal 'baz', @dalli.options[:frob]
10
+ end
11
+
12
+ should 'allow mute and silence' do
13
+ @dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:19122')
14
+ @dalli.mute do
15
+ assert_equal true, @dalli.write('foo', 'bar', nil)
16
+ assert_equal 'bar', @dalli.read('foo', nil)
17
+ end
18
+ refute @dalli.silence?
19
+ @dalli.silence!
20
+ assert_equal true, @dalli.silence?
21
+ end
22
+
23
+ should 'handle nil options' do
8
24
  @dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:19122')
9
25
  assert_equal true, @dalli.write('foo', 'bar', nil)
10
26
  assert_equal 'bar', @dalli.read('foo', nil)
@@ -30,6 +46,10 @@ describe 'ActiveSupport' do
30
46
 
31
47
  dvalue = @dalli.fetch(rand_key) { o }
32
48
  assert_equal o, dvalue
49
+
50
+ @dalli.write('false', false)
51
+ dvalue = @dalli.fetch('false') { flunk }
52
+ assert_equal false, dvalue
33
53
  end
34
54
  end
35
55
  end
@@ -133,6 +153,21 @@ describe 'ActiveSupport' do
133
153
  end
134
154
  end
135
155
 
156
+ should 'support exist command' do
157
+ with_activesupport do
158
+ memcached do
159
+ connect
160
+ @dalli.write(:foo, 'a')
161
+ @dalli.write(:false_value, false)
162
+
163
+ assert_equal true, @dalli.exist?(:foo)
164
+ assert_equal true, @dalli.exist?(:false_value)
165
+
166
+ assert_equal false, @dalli.exist?(:bar)
167
+ end
168
+ end
169
+ end
170
+
136
171
  should 'support other esoteric commands' do
137
172
  with_activesupport do
138
173
  memcached do
@@ -141,14 +176,39 @@ describe 'ActiveSupport' do
141
176
  assert_equal 1, ds.keys.size
142
177
  assert ds[ds.keys.first].keys.size > 0
143
178
 
144
- assert_equal true, @dalli.write(:foo, 'a')
145
- assert_equal true, @dalli.exist?(:foo)
146
- assert_equal false, @dalli.exist?(:bar)
147
-
148
179
  @dalli.reset
149
180
  end
150
181
  end
151
182
  end
183
+
184
+ should 'respect "raise_errors" option' do
185
+ with_activesupport do
186
+ memcached(29125) do
187
+ @dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:29125')
188
+ @dalli.write 'foo', 'bar'
189
+ assert_equal @dalli.read('foo'), 'bar'
190
+
191
+ memcached_kill(29125)
192
+
193
+ assert_equal @dalli.read('foo'), nil
194
+
195
+ @dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:29125', :raise_errors => true)
196
+
197
+ exception = [Dalli::RingError, { :message => "No server available" }]
198
+
199
+ assert_raises(*exception) { @dalli.read 'foo' }
200
+ assert_raises(*exception) { @dalli.read 'foo', :raw => true }
201
+ assert_raises(*exception) { @dalli.write 'foo', 'bar' }
202
+ assert_raises(*exception) { @dalli.exist? 'foo' }
203
+ assert_raises(*exception) { @dalli.increment 'foo' }
204
+ assert_raises(*exception) { @dalli.decrement 'foo' }
205
+ assert_raises(*exception) { @dalli.delete 'foo' }
206
+ assert_equal @dalli.read_multi('foo', 'bar'), {}
207
+ assert_raises(*exception) { @dalli.delete 'foo' }
208
+ assert_raises(*exception) { @dalli.fetch('foo') { 42 } }
209
+ end
210
+ end
211
+ end
152
212
  end
153
213
 
154
214
  should 'handle crazy characters from far-away lands' do
data/test/test_dalli.rb CHANGED
@@ -127,6 +127,20 @@ describe 'Dalli' do
127
127
  end
128
128
  end
129
129
 
130
+ should "support the fetch operation with falsey values" do
131
+ memcached do |dc|
132
+ dc.flush
133
+
134
+ dc.set("fetch_key", false)
135
+ res = dc.fetch("fetch_key") { flunk "fetch block called" }
136
+ assert_equal false, res
137
+
138
+ dc.set("fetch_key", nil)
139
+ res = dc.fetch("fetch_key") { "bob" }
140
+ assert_equal 'bob', res
141
+ end
142
+ end
143
+
130
144
  should "support the cas operation" do
131
145
  memcached do |dc|
132
146
  dc.flush
@@ -1,6 +1,27 @@
1
1
  require 'helper'
2
2
 
3
- describe 'FailOver' do
3
+ describe 'failover' do
4
+
5
+ describe 'timeouts' do
6
+ should 'not lead to corrupt sockets' do
7
+ memcached(29125) do
8
+ dc = Dalli::Client.new ['localhost:29125']
9
+ begin
10
+ Timeout.timeout 0.01 do
11
+ 1_000.times do
12
+ dc.set("test_123", {:test => "123"})
13
+ end
14
+ flunk("Did not timeout")
15
+ end
16
+ rescue Timeout::Error => e
17
+ end
18
+
19
+ assert_equal({:test => '123'}, dc.get("test_123"))
20
+ end
21
+ end
22
+ end
23
+
24
+
4
25
  context 'assuming some bad servers' do
5
26
 
6
27
  should 'silently reconnect if server hiccups' do
@@ -12,6 +12,8 @@ class Foo
12
12
  end
13
13
 
14
14
  class TestSessionStore < ActionController::IntegrationTest
15
+ include MemcachedMock::Helper
16
+
15
17
  class TestController < ActionController::Base
16
18
  def no_session_access
17
19
  head :ok
@@ -201,6 +203,42 @@ class TestSessionStore < ActionController::IntegrationTest
201
203
  assert_equal 'foo: nil', response.body
202
204
  end
203
205
  end
206
+
207
+ def test_without_raise_errors_option
208
+ memcached(29125) do
209
+ with_test_route_set(:memcache_server => '127.0.0.1:29125') do
210
+ get '/set_session_value'
211
+ assert_response :success
212
+
213
+ get '/get_session_value'
214
+ assert_response :success
215
+ assert_equal 'foo: "bar"', response.body
216
+
217
+ memcached_kill(29125)
218
+
219
+ get '/get_session_value'
220
+ assert_response :success
221
+ assert_equal 'foo: nil', response.body
222
+ end
223
+ end
224
+ end
225
+
226
+ def test_with_raise_errors_option
227
+ memcached(29125) do
228
+ with_test_route_set(:memcache_server => '127.0.0.1:29125', :raise_errors => true) do
229
+ get '/set_session_value'
230
+ assert_response :success
231
+
232
+ memcached_kill(29125)
233
+
234
+ exception = [Dalli::RingError, { :message => "No server available" }]
235
+
236
+ assert_raises(*exception) { get '/get_session_value' }
237
+ assert_raises(*exception) { get '/set_session_value' }
238
+ assert_raises(*exception) { get '/call_reset_session' }
239
+ end
240
+ end
241
+ end
204
242
  rescue LoadError, RuntimeError
205
243
  $stderr.puts "Skipping SessionStore tests. Start memcached and try again: #{$!.message}"
206
244
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-14 00:00:00.000000000 Z
12
+ date: 2012-05-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mini_shoulda
16
- requirement: &70360909279380 !ruby/object:Gem::Requirement
16
+ requirement: &70263471981140 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70360909279380
24
+ version_requirements: *70263471981140
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: mocha
27
- requirement: &70360909278440 !ruby/object:Gem::Requirement
27
+ requirement: &70263471980400 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70360909278440
35
+ version_requirements: *70263471980400
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rails
38
- requirement: &70360909277240 !ruby/object:Gem::Requirement
38
+ requirement: &70263471979400 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '3'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70360909277240
46
+ version_requirements: *70263471979400
47
47
  description: High performance memcached client for Ruby
48
48
  email: mperham@gmail.com
49
49
  executables: []