dalli 0.11.0 → 0.11.1

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,12 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 0.11.1
5
+ ======
6
+
7
+ - Minor fixes, doc updates.
8
+ - Add optional support for kgio sockets, gives a 10-15% performance boost.
9
+
4
10
  0.11.0
5
11
  ======
6
12
 
@@ -4,6 +4,10 @@ Performance
4
4
  Caching is all about performance, so I carefully track Dalli performance to ensure no regressions.
5
5
  Times are from a Unibody MBP 2.4Ghz Core 2 Duo running Snow Leopard.
6
6
 
7
+ You can optionally use kgio to give Dalli a small, 10-20% performance boost: gem install kgio.
8
+
9
+ *memcache-client*:
10
+
7
11
  Testing 1.8.5 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
8
12
  user system total real
9
13
  set:plain:memcache-client 2.070000 0.590000 2.660000 ( 2.669744)
@@ -14,6 +18,8 @@ Times are from a Unibody MBP 2.4Ghz Core 2 Duo running Snow Leopard.
14
18
  missing:ruby:memcache-client 1.900000 0.370000 2.270000 ( 2.282264)
15
19
  mixed:ruby:memcache-client 4.430000 0.950000 5.380000 ( 5.420251)
16
20
 
21
+ *dalli*:
22
+
17
23
  Testing 0.9.0 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
18
24
  user system total real
19
25
  set:plain:dalli 1.610000 0.360000 1.970000 ( 2.032947)
@@ -33,3 +39,31 @@ Times are from a Unibody MBP 2.4Ghz Core 2 Duo running Snow Leopard.
33
39
  multiget:ruby:dalli 0.870000 0.300000 1.170000 ( 1.295592)
34
40
  missing:ruby:dalli 1.420000 0.370000 1.790000 ( 1.925094)
35
41
  mixed:ruby:dalli 2.800000 0.680000 3.480000 ( 3.820694)
42
+
43
+ Testing 0.11.1 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
44
+ Using standard socket IO
45
+ user system total real
46
+ set:plain:dalli 1.570000 0.380000 1.950000 ( 1.990252)
47
+ setq:plain:dalli 0.460000 0.140000 0.600000 ( 0.600362)
48
+ set:ruby:dalli 1.630000 0.380000 2.010000 ( 2.050056)
49
+ get:plain:dalli 1.710000 0.410000 2.120000 ( 2.156428)
50
+ get:ruby:dalli 1.680000 0.410000 2.090000 ( 2.120228)
51
+ multiget:ruby:dalli 0.860000 0.310000 1.170000 ( 1.182857)
52
+ missing:ruby:dalli 1.540000 0.390000 1.930000 ( 1.976637)
53
+ mixed:ruby:dalli 3.300000 0.810000 4.110000 ( 4.166230)
54
+ mixedq:ruby:dalli 2.530000 0.640000 3.170000 ( 3.214916)
55
+ incr:ruby:dalli 0.540000 0.140000 0.680000 ( 0.691829)
56
+
57
+ Testing 0.11.1 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
58
+ Using kgio socket IO
59
+ user system total real
60
+ set:plain:dalli 0.800000 0.370000 1.170000 ( 1.694842)
61
+ setq:plain:dalli 0.460000 0.150000 0.610000 ( 0.618146)
62
+ set:ruby:dalli 0.860000 0.370000 1.230000 ( 1.760995)
63
+ get:plain:dalli 0.910000 0.390000 1.300000 ( 1.860499)
64
+ get:ruby:dalli 0.900000 0.390000 1.290000 ( 1.809426)
65
+ multiget:ruby:dalli 0.720000 0.300000 1.020000 ( 1.044887)
66
+ missing:ruby:dalli 0.770000 0.400000 1.170000 ( 1.649516)
67
+ mixed:ruby:dalli 1.750000 0.760000 2.510000 ( 3.563845)
68
+ mixedq:ruby:dalli 1.730000 0.650000 2.380000 ( 2.883827)
69
+ incr:ruby:dalli 0.280000 0.130000 0.410000 ( 0.603845)
data/README.md CHANGED
@@ -44,6 +44,9 @@ Remember, Dalli **requires** memcached 1.4+. You can check the version with `me
44
44
 
45
45
  The test suite requires memcached 1.4.3+ with SASL enabled (./configure --enable-sasl). Currently only supports the PLAIN mechanism.
46
46
 
47
+ Dalli has no runtime dependencies and never will. You can optionally install the 'kgio' gem to
48
+ give Dalli a 10-20% performance boost.
49
+
47
50
 
48
51
  Usage with Rails 3.0
49
52
  ---------------------------
@@ -127,6 +130,8 @@ If you have a fix you wish to provide, please fork the code, fix in your local p
127
130
  Thanks
128
131
  ------------
129
132
 
133
+ Eric Wong - for help using his [kgio](http://unicorn.bogomips.org/kgio/index.html) library.
134
+
130
135
  Brian Mitchell - for his remix-stash project which was helpful when implementing and testing the binary protocol support.
131
136
 
132
137
  [NorthScale](http://northscale.com) - for their project sponsorship
@@ -14,7 +14,6 @@ Gem::Specification.new do |s|
14
14
  "LICENSE",
15
15
  "README.md",
16
16
  "History.md",
17
- "TODO.md",
18
17
  "Rakefile",
19
18
  "Gemfile",
20
19
  "dalli.gemspec",
@@ -1,6 +1,7 @@
1
1
  require 'dalli/client'
2
2
  require 'dalli/ring'
3
3
  require 'dalli/server'
4
+ require 'dalli/socket'
4
5
  require 'dalli/version'
5
6
  require 'dalli/options'
6
7
 
@@ -1,3 +1,4 @@
1
+ # encoding: ascii
1
2
  module Dalli
2
3
  class Client
3
4
 
@@ -43,9 +44,12 @@ module Dalli
43
44
 
44
45
  def get(key, options=nil)
45
46
  resp = perform(:get, key)
46
- (!resp || resp == 'Not found') ? nil : deserialize(resp, options)
47
+ (!resp || resp == 'Not found') ? nil : resp
47
48
  end
48
49
 
50
+ ##
51
+ # Fetch multiple keys efficiently.
52
+ # Returns a hash of { 'key' => 'value', 'key2' => 'value1' }
49
53
  def get_multi(*keys)
50
54
  return {} if keys.empty?
51
55
  options = nil
@@ -55,7 +59,7 @@ module Dalli
55
59
  perform(:getkq, key)
56
60
  end
57
61
  values = ring.servers.inject({}) { |hash, s| hash.merge!(s.request(:noop)); hash }
58
- values.inject(values) { |memo, (k,v)| memo[k] = deserialize(v, options); memo }
62
+ values.inject(values) { |memo, (k,v)| memo[k] = v; memo }
59
63
  end
60
64
  end
61
65
 
@@ -69,29 +73,46 @@ module Dalli
69
73
  val
70
74
  end
71
75
 
76
+ ##
77
+ # compare and swap values using optimistic locking.
78
+ # Fetch the existing value for key.
79
+ # If it exists, yield the value to the block.
80
+ # Add the block's return value as the new value for the key.
81
+ # Add will fail if someone else changed the value.
82
+ #
83
+ # Returns:
84
+ # - nil if the key did not exist.
85
+ # - false if the value was changed by someone else.
86
+ # - true if the value was successfully updated.
72
87
  def cas(key, ttl=nil, options=nil, &block)
73
88
  ttl ||= @options[:expires_in]
74
89
  (value, cas) = perform(:cas, key)
75
- value = (!value || value == 'Not found') ? nil : deserialize(value, options)
90
+ value = (!value || value == 'Not found') ? nil : value
76
91
  if value
77
92
  newvalue = block.call(value)
78
- perform(:add, key, serialize(newvalue, options), ttl, cas, options)
93
+ perform(:add, key, newvalue, ttl, cas, options)
79
94
  end
80
95
  end
81
96
 
82
97
  def set(key, value, ttl=nil, options=nil)
83
98
  ttl ||= @options[:expires_in]
84
- perform(:set, key, serialize(value, options), ttl, options)
99
+ perform(:set, key, value, ttl, options)
85
100
  end
86
-
101
+
102
+ ##
103
+ # Conditionally add a key/value pair, if the key does not already exist
104
+ # on the server. Returns true if the operation succeeded.
87
105
  def add(key, value, ttl=nil, options=nil)
88
106
  ttl ||= @options[:expires_in]
89
- perform(:add, key, serialize(value, options), ttl, 0, options)
107
+ perform(:add, key, value, ttl, 0, options)
90
108
  end
91
109
 
110
+ ##
111
+ # Conditionally add a key/value pair, only if the key already exists
112
+ # on the server. Returns true if the operation succeeded.
92
113
  def replace(key, value, ttl=nil, options=nil)
93
114
  ttl ||= @options[:expires_in]
94
- perform(:replace, key, serialize(value, options), ttl, options)
115
+ perform(:replace, key, value, ttl, options)
95
116
  end
96
117
 
97
118
  def delete(key)
@@ -148,10 +169,16 @@ module Dalli
148
169
  perform(:decr, key, amt, ttl, default)
149
170
  end
150
171
 
172
+ ##
173
+ # Collect the stats for each server.
174
+ # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
151
175
  def stats
152
176
  ring.servers.inject({}) { |memo, s| memo["#{s.hostname}:#{s.port}"] = s.request(:stats); memo }
153
177
  end
154
178
 
179
+ ##
180
+ # Close our connection to each server.
181
+ # If you perform another operation after this, the connections will be re-established.
155
182
  def close
156
183
  if @ring
157
184
  @ring.servers.map { |s| s.close }
@@ -170,18 +197,6 @@ module Dalli
170
197
  )
171
198
  end
172
199
 
173
- def serialize(value, options)
174
- value
175
- # options && options[:raw] ? value.to_s : ::Marshal.dump(value)
176
- end
177
-
178
- def deserialize(value, options)
179
- value
180
- # options && options[:raw] ? value : ::Marshal.load(value)
181
- # rescue TypeError
182
- # raise Dalli::DalliError, "Invalid marshalled data in memcached: #{value}"
183
- end
184
-
185
200
  def env_servers
186
201
  ENV['MEMCACHE_SERVERS'] ? ENV['MEMCACHE_SERVERS'].split(',') : nil
187
202
  end
@@ -91,14 +91,22 @@ module Dalli
91
91
  def down!(toss_exception=false)
92
92
  close
93
93
  @down_at = Time.now.to_i
94
- @msg = @msg || ($! && $!.message) || ''
95
- raise Dalli::NetworkError, "#{self.hostname}:#{self.port} is currently down: #{@msg}" if toss_exception
94
+ @error = $! && $!.class.name
95
+ @msg = @msg || ($! && $!.message && !$!.message.empty? && $!.message)
96
+ @trace = @trace || $!.backtrace
97
+
98
+ if toss_exception
99
+ x = Dalli::NetworkError.new("#{self.hostname}:#{self.port} is currently down: #{@error} #{@msg}")
100
+ x.set_backtrace @trace
101
+ raise x
102
+ end
96
103
  nil
97
104
  end
98
105
 
99
106
  def up!
100
107
  @down_at = nil
101
108
  @msg = nil
109
+ @trace = nil
102
110
  end
103
111
 
104
112
  def multi?
@@ -325,7 +333,21 @@ module Dalli
325
333
  end
326
334
  end
327
335
 
328
- TIMEOUT = 0.5
336
+ def write(bytes, socket=connection)
337
+ begin
338
+ socket.write(bytes)
339
+ rescue SystemCallError
340
+ down!(true)
341
+ end
342
+ end
343
+
344
+ def read(count, socket=connection)
345
+ begin
346
+ socket.readfull(count)
347
+ rescue SystemCallError, Timeout::Error, EOFError
348
+ down!(true)
349
+ end
350
+ end
329
351
 
330
352
  def connection
331
353
  @sock ||= begin
@@ -333,60 +355,17 @@ module Dalli
333
355
  raise Dalli::NetworkError, "#{self.hostname}:#{self.port} is currently down: #{@msg}"
334
356
  end
335
357
 
336
- # All this ugly code to ensure proper Socket connect timeout
337
358
  begin
338
- addr = Socket.getaddrinfo(self.hostname, nil)
339
- sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
340
- begin
341
- sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
342
- rescue Errno::EINPROGRESS
343
- resp = IO.select(nil, [sock], nil, TIMEOUT)
344
- begin
345
- sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
346
- rescue Errno::EISCONN
347
- end
348
- end
359
+ sock = KSocket.open(hostname, port)
349
360
  rescue
350
361
  down!(true)
351
362
  end
352
- # end ugly code
353
-
354
- sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
355
363
  sasl_authentication(sock) if Dalli::Server.need_auth?
356
364
  up!
357
365
  sock
358
366
  end
359
367
  end
360
368
 
361
- def write(bytes)
362
- begin
363
- connection.write(bytes)
364
- rescue SystemCallError
365
- down!(true)
366
- end
367
- end
368
-
369
- def read(count, socket=connection)
370
- begin
371
- value = ''
372
- begin
373
- loop do
374
- value << socket.sysread(count - value.bytesize)
375
- break if value.bytesize == count
376
- end
377
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
378
- if IO.select([socket], nil, nil, TIMEOUT)
379
- retry
380
- else
381
- raise Timeout::Error, "IO timeout"
382
- end
383
- end
384
- value
385
- rescue SystemCallError, Timeout::Error, EOFError
386
- down!(true)
387
- end
388
- end
389
-
390
369
  def split(n)
391
370
  [n >> 32, 0xFFFFFFFF & n]
392
371
  end
@@ -487,7 +466,7 @@ module Dalli
487
466
 
488
467
  # negotiate
489
468
  req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
490
- socket.write(req)
469
+ write(req, socket)
491
470
  header = read(24, socket)
492
471
  raise Dalli::NetworkError, 'No response' if !header
493
472
  (extras, type, status, count) = header.unpack(NORMAL_HEADER)
@@ -0,0 +1,80 @@
1
+ begin
2
+ require 'kgio'
3
+ puts "Using kgio socket IO" if $TESTING
4
+
5
+ class Dalli::Server::KSocket < Kgio::Socket
6
+ TIMEOUT = 0.5
7
+
8
+ def wait_readable
9
+ IO.select([self], nil, nil, TIMEOUT) || raise(Timeout::Error, "IO timeout")
10
+ end
11
+
12
+ def wait_writable
13
+ IO.select(nil, [self], nil, TIMEOUT) || raise(Timeout::Error, "IO timeout")
14
+ end
15
+
16
+ def self.open(host, port)
17
+ addr = Socket.pack_sockaddr_in(port, host)
18
+ sock = start(addr)
19
+ sock.wait_writable
20
+ sock
21
+ end
22
+
23
+ alias :write :kgio_write
24
+
25
+ def readfull(count)
26
+ value = ''
27
+ loop do
28
+ value << kgio_read!(count - value.bytesize)
29
+ break if value.bytesize == count
30
+ end
31
+ value
32
+ end
33
+
34
+ end
35
+
36
+ ::Kgio.wait_readable = :wait_readable
37
+ ::Kgio.wait_writable = :wait_writable
38
+
39
+ rescue LoadError
40
+ puts "Using standard socket IO" if $TESTING
41
+
42
+ class Dalli::Server::KSocket < Socket
43
+ TIMEOUT = 0.5
44
+
45
+ def self.open(host, port)
46
+ # All this ugly code to ensure proper Socket connect timeout
47
+ addr = Socket.getaddrinfo(host, nil)
48
+ sock = new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
49
+ begin
50
+ sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
51
+ rescue Errno::EINPROGRESS
52
+ resp = IO.select(nil, [sock], nil, TIMEOUT)
53
+ begin
54
+ sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
55
+ rescue Errno::EISCONN
56
+ end
57
+ end
58
+ sock
59
+ end
60
+
61
+ def readfull(count)
62
+ value = ''
63
+ begin
64
+ loop do
65
+ value << sysread(count - value.bytesize)
66
+ break if value.bytesize == count
67
+ end
68
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
69
+ if IO.select([self], nil, nil, TIMEOUT)
70
+ retry
71
+ else
72
+ raise Timeout::Error, "IO timeout"
73
+ end
74
+ end
75
+ value
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '0.11.0'
2
+ VERSION = '0.11.1'
3
3
  end
@@ -1,3 +1,4 @@
1
+ $TESTING = true
1
2
  require 'rubygems'
2
3
  # require 'simplecov'
3
4
  # SimpleCov.start
@@ -72,7 +72,7 @@ class TestActiveSupport < Test::Unit::TestCase
72
72
  connect
73
73
  @mc.write("abc", 5, :raw => true)
74
74
  @mc.write("cba", 5, :raw => true)
75
- if RAILS_VERSION < '3.0.0'
75
+ if RAILS_VERSION =~ /2\.3/
76
76
  assert_raise ArgumentError do
77
77
  @mc.read_multi("abc", "cba")
78
78
  end
@@ -82,13 +82,9 @@ class TestActiveSupport < Test::Unit::TestCase
82
82
 
83
83
  @dalli.write("abc", 5, :raw => true)
84
84
  @dalli.write("cba", 5, :raw => true)
85
- if RAILS_VERSION < '3.0.0'
86
- assert_raise ArgumentError do
87
- @dalli.read_multi("abc", "cba")
88
- end
89
- else
90
- assert_equal({'abc' => '5', 'cba' => '5' }, @dalli.read_multi("abc", "cba"))
91
- end
85
+ # XXX: API difference between m-c and dalli. Dalli is smarter about
86
+ # what it needs to unmarshal.
87
+ assert_equal({'abc' => '5', 'cba' => '5' }, @dalli.read_multi("abc", "cba"))
92
88
  end
93
89
  end
94
90
  end
@@ -18,7 +18,7 @@ class TestNetwork < Test::Unit::TestCase
18
18
 
19
19
  should 'handle connection reset' do
20
20
  memcached_mock(lambda {|sock| sock.close }) do
21
- assert_error Dalli::NetworkError, /Connection reset|end of file/ do
21
+ assert_error Dalli::NetworkError, /Connection reset|EOFError/ do
22
22
  dc = Dalli::Client.new('localhost:19123')
23
23
  dc.get('abc')
24
24
  end
@@ -27,7 +27,7 @@ class TestNetwork < Test::Unit::TestCase
27
27
 
28
28
  should 'handle malformed response' do
29
29
  memcached_mock(lambda {|sock| sock.write('123') }) do
30
- assert_error Dalli::NetworkError, /end of file/ do
30
+ assert_error Dalli::NetworkError, /EOFError/ do
31
31
  dc = Dalli::Client.new('localhost:19123')
32
32
  dc.get('abc')
33
33
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 11
8
- - 0
9
- version: 0.11.0
8
+ - 1
9
+ version: 0.11.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Mike Perham
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-03 00:00:00 -07:00
17
+ date: 2010-11-14 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -93,12 +93,12 @@ files:
93
93
  - lib/dalli/sasl/base.rb
94
94
  - lib/dalli/sasl/plain.rb
95
95
  - lib/dalli/server.rb
96
+ - lib/dalli/socket.rb
96
97
  - lib/dalli/version.rb
97
98
  - lib/dalli.rb
98
99
  - LICENSE
99
100
  - README.md
100
101
  - History.md
101
- - TODO.md
102
102
  - Rakefile
103
103
  - Gemfile
104
104
  - dalli.gemspec
data/TODO.md DELETED
@@ -1,5 +0,0 @@
1
- TODO
2
- ========
3
-
4
- * More of the memcached instruction set. This will be done based on user demand. Email me if the API is missing a feature you'd like to use.
5
- * Better API documentation