dalli 1.1.4 → 2.7.11
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.
- checksums.yaml +7 -0
- data/Gemfile +8 -4
- data/History.md +267 -1
- data/LICENSE +1 -1
- data/README.md +129 -85
- data/lib/action_dispatch/middleware/session/dalli_store.rb +7 -1
- data/lib/active_support/cache/dalli_store.rb +361 -104
- data/lib/dalli.rb +9 -7
- data/lib/dalli/cas/client.rb +59 -0
- data/lib/dalli/client.rb +298 -103
- data/lib/dalli/compressor.rb +30 -0
- data/lib/dalli/options.rb +19 -0
- data/lib/dalli/railtie.rb +8 -0
- data/lib/dalli/ring.rb +59 -22
- data/lib/dalli/server.rb +346 -125
- data/lib/dalli/socket.rb +105 -105
- data/lib/dalli/version.rb +2 -1
- data/lib/rack/session/dalli.rb +156 -22
- metadata +29 -104
- data/Performance.md +0 -85
- data/Rakefile +0 -39
- data/Upgrade.md +0 -45
- data/dalli.gemspec +0 -31
- data/lib/active_support/cache/dalli_store23.rb +0 -172
- data/lib/dalli/compatibility.rb +0 -52
- data/lib/dalli/memcache-client.rb +0 -1
- data/test/abstract_unit.rb +0 -282
- data/test/benchmark_test.rb +0 -170
- data/test/helper.rb +0 -39
- data/test/memcached_mock.rb +0 -126
- data/test/test_active_support.rb +0 -201
- data/test/test_compatibility.rb +0 -33
- data/test/test_dalli.rb +0 -450
- data/test/test_encoding.rb +0 -43
- data/test/test_failover.rb +0 -107
- data/test/test_network.rb +0 -54
- data/test/test_ring.rb +0 -85
- data/test/test_sasl.rb +0 -83
- data/test/test_session_store.rb +0 -225
- data/test/test_synchrony.rb +0 -175
data/lib/dalli/socket.rb
CHANGED
@@ -1,40 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
module Dalli::Server::TCPSocketOptions
|
5
|
+
def setsockopts(sock, options)
|
6
|
+
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
7
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
|
8
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
|
9
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
1
13
|
begin
|
2
14
|
require 'kgio'
|
3
15
|
puts "Using kgio socket IO" if defined?($TESTING) && $TESTING
|
4
16
|
|
5
17
|
class Dalli::Server::KSocket < Kgio::Socket
|
6
|
-
attr_accessor :options
|
18
|
+
attr_accessor :options, :server
|
7
19
|
|
8
20
|
def kgio_wait_readable
|
9
|
-
IO.select([self], nil, nil, options[:
|
21
|
+
IO.select([self], nil, nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
|
10
22
|
end
|
11
23
|
|
12
24
|
def kgio_wait_writable
|
13
|
-
IO.select(nil, [self], nil, options[:
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.open(host, port, options = {})
|
17
|
-
addr = Socket.pack_sockaddr_in(port, host)
|
18
|
-
sock = start(addr)
|
19
|
-
sock.options = options
|
20
|
-
sock.kgio_wait_writable
|
21
|
-
sock
|
25
|
+
IO.select(nil, [self], nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
|
22
26
|
end
|
23
27
|
|
24
28
|
alias :write :kgio_write
|
25
29
|
|
26
30
|
def readfull(count)
|
27
|
-
value = ''
|
28
|
-
|
31
|
+
value = String.new('')
|
32
|
+
while true
|
29
33
|
value << kgio_read!(count - value.bytesize)
|
30
34
|
break if value.bytesize == count
|
31
35
|
end
|
32
36
|
value
|
33
37
|
end
|
34
38
|
|
39
|
+
def read_available
|
40
|
+
value = String.new('')
|
41
|
+
while true
|
42
|
+
ret = kgio_tryread(8196)
|
43
|
+
case ret
|
44
|
+
when nil
|
45
|
+
raise EOFError, 'end of stream'
|
46
|
+
when :wait_readable
|
47
|
+
break
|
48
|
+
else
|
49
|
+
value << ret
|
50
|
+
end
|
51
|
+
end
|
52
|
+
value
|
53
|
+
end
|
35
54
|
end
|
36
55
|
|
56
|
+
class Dalli::Server::KSocket::TCP < Dalli::Server::KSocket
|
57
|
+
extend Dalli::Server::TCPSocketOptions
|
37
58
|
|
59
|
+
def self.open(host, port, server, options = {})
|
60
|
+
addr = Socket.pack_sockaddr_in(port, host)
|
61
|
+
sock = start(addr)
|
62
|
+
setsockopts(sock, options)
|
63
|
+
sock.options = options
|
64
|
+
sock.server = server
|
65
|
+
sock.kgio_wait_writable
|
66
|
+
sock
|
67
|
+
rescue Timeout::Error
|
68
|
+
sock.close if sock
|
69
|
+
raise
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Dalli::Server::KSocket::UNIX < Dalli::Server::KSocket
|
74
|
+
def self.open(path, server, options = {})
|
75
|
+
addr = Socket.pack_sockaddr_un(path)
|
76
|
+
sock = start(addr)
|
77
|
+
sock.options = options
|
78
|
+
sock.server = server
|
79
|
+
sock.kgio_wait_writable
|
80
|
+
sock
|
81
|
+
rescue Timeout::Error
|
82
|
+
sock.close if sock
|
83
|
+
raise
|
84
|
+
end
|
85
|
+
end
|
38
86
|
|
39
87
|
if ::Kgio.respond_to?(:wait_readable=)
|
40
88
|
::Kgio.wait_readable = :kgio_wait_readable
|
@@ -44,127 +92,79 @@ begin
|
|
44
92
|
rescue LoadError
|
45
93
|
|
46
94
|
puts "Using standard socket IO (#{RUBY_DESCRIPTION})" if defined?($TESTING) && $TESTING
|
47
|
-
|
48
|
-
|
49
|
-
class Dalli::Server::KSocket < TCPSocket
|
50
|
-
attr_accessor :options
|
51
|
-
|
52
|
-
def self.open(host, port, options = {})
|
53
|
-
sock = new(host, port)
|
54
|
-
sock.options = { :host => host, :port => port }.merge(options)
|
55
|
-
sock
|
56
|
-
end
|
57
|
-
|
95
|
+
module Dalli::Server::KSocket
|
96
|
+
module InstanceMethods
|
58
97
|
def readfull(count)
|
59
|
-
value = ''
|
98
|
+
value = String.new('')
|
60
99
|
begin
|
61
|
-
|
100
|
+
while true
|
62
101
|
value << read_nonblock(count - value.bytesize)
|
63
102
|
break if value.bytesize == count
|
64
103
|
end
|
65
104
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
66
|
-
if IO.select([self], nil, nil, options[:
|
105
|
+
if IO.select([self], nil, nil, options[:socket_timeout])
|
67
106
|
retry
|
68
107
|
else
|
69
|
-
|
108
|
+
safe_options = options.reject{|k,v| [:username, :password].include? k}
|
109
|
+
raise Timeout::Error, "IO timeout: #{safe_options.inspect}"
|
70
110
|
end
|
71
111
|
end
|
72
112
|
value
|
73
113
|
end
|
74
114
|
|
75
|
-
|
76
|
-
|
77
|
-
|
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])
|
115
|
+
def read_available
|
116
|
+
value = String.new('')
|
117
|
+
while true
|
91
118
|
begin
|
92
|
-
|
93
|
-
rescue Errno::
|
94
|
-
|
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}"
|
119
|
+
value << read_nonblock(8196)
|
120
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
121
|
+
break
|
111
122
|
end
|
112
123
|
end
|
113
124
|
value
|
114
125
|
end
|
115
126
|
end
|
116
127
|
|
128
|
+
def self.included(receiver)
|
129
|
+
receiver.send(:attr_accessor, :options, :server)
|
130
|
+
receiver.send(:include, InstanceMethods)
|
131
|
+
end
|
117
132
|
end
|
118
|
-
end
|
119
133
|
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
end
|
134
|
+
class Dalli::Server::KSocket::TCP < TCPSocket
|
135
|
+
extend Dalli::Server::TCPSocketOptions
|
136
|
+
include Dalli::Server::KSocket
|
136
137
|
|
137
|
-
def
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
138
|
+
def self.open(host, port, server, options = {})
|
139
|
+
Timeout.timeout(options[:socket_timeout]) do
|
140
|
+
sock = new(host, port)
|
141
|
+
setsockopts(sock, options)
|
142
|
+
sock.options = {:host => host, :port => port}.merge(options)
|
143
|
+
sock.server = server
|
144
|
+
sock
|
142
145
|
end
|
143
|
-
value
|
144
146
|
end
|
145
|
-
|
146
147
|
end
|
147
148
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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."
|
149
|
+
if RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
|
150
|
+
class Dalli::Server::KSocket::UNIX
|
151
|
+
def initialize(*args)
|
152
|
+
raise Dalli::DalliError, "Unix sockets are not supported on Windows platform."
|
153
|
+
end
|
157
154
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
155
|
+
else
|
156
|
+
class Dalli::Server::KSocket::UNIX < UNIXSocket
|
157
|
+
include Dalli::Server::KSocket
|
158
|
+
|
159
|
+
def self.open(path, server, options = {})
|
160
|
+
Timeout.timeout(options[:socket_timeout]) do
|
161
|
+
sock = new(path)
|
162
|
+
sock.options = {:path => path}.merge(options)
|
163
|
+
sock.server = server
|
164
|
+
sock
|
165
|
+
end
|
166
166
|
end
|
167
|
-
value
|
168
167
|
end
|
168
|
+
|
169
169
|
end
|
170
170
|
end
|
data/lib/dalli/version.rb
CHANGED
data/lib/rack/session/dalli.rb
CHANGED
@@ -1,52 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'rack/session/abstract/id'
|
2
3
|
require 'dalli'
|
3
4
|
|
4
5
|
module Rack
|
5
6
|
module Session
|
6
|
-
class Dalli < Abstract::ID
|
7
|
-
attr_reader :pool
|
7
|
+
class Dalli < defined?(Abstract::Persisted) ? Abstract::Persisted : Abstract::ID
|
8
|
+
attr_reader :pool, :mutex
|
8
9
|
|
9
|
-
|
10
|
+
DEFAULT_DALLI_OPTIONS = {
|
10
11
|
:namespace => 'rack:session',
|
11
12
|
:memcache_server => 'localhost:11211'
|
13
|
+
}
|
12
14
|
|
15
|
+
# Brings in a new Rack::Session::Dalli middleware with the given
|
16
|
+
# `:memcache_server`. The server is either a hostname, or a
|
17
|
+
# host-with-port string in the form of "host_name:port", or an array of
|
18
|
+
# such strings. For example:
|
19
|
+
#
|
20
|
+
# use Rack::Session::Dalli,
|
21
|
+
# :memcache_server => "mc.example.com:1234"
|
22
|
+
#
|
23
|
+
# If no `:memcache_server` option is specified, Rack::Session::Dalli will
|
24
|
+
# connect to localhost, port 11211 (the default memcached port). If
|
25
|
+
# `:memcache_server` is set to nil, Dalli::Client will look for
|
26
|
+
# ENV['MEMCACHE_SERVERS'] and use that value if it is available, or fall
|
27
|
+
# back to the same default behavior described above.
|
28
|
+
#
|
29
|
+
# Rack::Session::Dalli is intended to be a drop-in replacement for
|
30
|
+
# Rack::Session::Memcache. It accepts additional options that control the
|
31
|
+
# behavior of Rack::Session, Dalli::Client, and an optional
|
32
|
+
# ConnectionPool. First and foremost, if you wish to instantiate your own
|
33
|
+
# Dalli::Client (or ConnectionPool) and use that instead of letting
|
34
|
+
# Rack::Session::Dalli instantiate it on your behalf, simply pass it in
|
35
|
+
# as the `:cache` option. Please note that you will be responsible for
|
36
|
+
# setting the namespace and any other options on Dalli::Client.
|
37
|
+
#
|
38
|
+
# Secondly, if you're not using the `:cache` option, Rack::Session::Dalli
|
39
|
+
# accepts the same options as Dalli::Client, so it's worth reviewing its
|
40
|
+
# documentation. Perhaps most importantly, if you don't specify a
|
41
|
+
# `:namespace` option, Rack::Session::Dalli will default to using
|
42
|
+
# "rack:session".
|
43
|
+
#
|
44
|
+
# Whether you are using the `:cache` option or not, it is not recommend
|
45
|
+
# to set `:expires_in`. Instead, use `:expire_after`, which will control
|
46
|
+
# both the expiration of the client cookie as well as the expiration of
|
47
|
+
# the corresponding entry in memcached.
|
48
|
+
#
|
49
|
+
# Rack::Session::Dalli also accepts a host of options that control how
|
50
|
+
# the sessions and session cookies are managed, including the
|
51
|
+
# aforementioned `:expire_after` option. Please see the documentation for
|
52
|
+
# Rack::Session::Abstract::Persisted for a detailed explanation of these
|
53
|
+
# options and their default values.
|
54
|
+
#
|
55
|
+
# Finally, if your web application is multithreaded, the
|
56
|
+
# Rack::Session::Dalli middleware can become a source of contention. You
|
57
|
+
# can use a connection pool of Dalli clients by passing in the
|
58
|
+
# `:pool_size` and/or `:pool_timeout` options. For example:
|
59
|
+
#
|
60
|
+
# use Rack::Session::Dalli,
|
61
|
+
# :memcache_server => "mc.example.com:1234",
|
62
|
+
# :pool_size => 10
|
63
|
+
#
|
64
|
+
# You must include the `connection_pool` gem in your project if you wish
|
65
|
+
# to use pool support. Please see the documentation for ConnectionPool
|
66
|
+
# for more information about it and its default options (which would only
|
67
|
+
# be applicable if you supplied one of the two options, but not both).
|
68
|
+
#
|
13
69
|
def initialize(app, options={})
|
70
|
+
# Parent uses DEFAULT_OPTIONS to build @default_options for Rack::Session
|
14
71
|
super
|
15
|
-
mserv = @default_options[:memcache_server]
|
16
|
-
mopts = @default_options.reject{|k,v| !DEFAULT_OPTIONS.include? k }
|
17
|
-
@pool = options[:cache] || Dalli::Client.new(mserv, mopts)
|
18
|
-
end
|
19
72
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
73
|
+
# Determine the default TTL for newly-created sessions
|
74
|
+
@default_ttl = ttl @default_options[:expire_after]
|
75
|
+
|
76
|
+
# Normalize and validate passed options
|
77
|
+
cache, mserv, mopts, popts = extract_dalli_options options
|
78
|
+
|
79
|
+
@pool =
|
80
|
+
if cache # caller passed a Dalli::Client or ConnectionPool instance
|
81
|
+
cache
|
82
|
+
elsif popts # caller passed ConnectionPool options
|
83
|
+
ConnectionPool.new(popts) { ::Dalli::Client.new(mserv, mopts) }
|
84
|
+
else
|
85
|
+
::Dalli::Client.new(mserv, mopts)
|
86
|
+
end
|
87
|
+
|
88
|
+
if @pool.respond_to?(:alive!) # is a Dalli::Client
|
89
|
+
@mutex = Mutex.new
|
90
|
+
|
91
|
+
@pool.alive!
|
24
92
|
end
|
25
93
|
end
|
26
94
|
|
27
95
|
def get_session(env, sid)
|
28
|
-
|
29
|
-
sid
|
30
|
-
|
31
|
-
|
96
|
+
with_block(env, [nil, {}]) do |dc|
|
97
|
+
unless sid and !sid.empty? and session = dc.get(sid)
|
98
|
+
old_sid, sid, session = sid, generate_sid_with(dc), {}
|
99
|
+
unless dc.add(sid, session, @default_ttl)
|
100
|
+
sid = old_sid
|
101
|
+
redo # generate a new sid and try again
|
102
|
+
end
|
32
103
|
end
|
104
|
+
[sid, session]
|
33
105
|
end
|
34
|
-
[sid, session]
|
35
106
|
end
|
36
107
|
|
37
108
|
def set_session(env, session_id, new_session, options)
|
38
|
-
|
39
|
-
expiry = expiry.nil? ? 0 : expiry + 1
|
109
|
+
return false unless session_id
|
40
110
|
|
41
|
-
|
42
|
-
|
111
|
+
with_block(env, false) do |dc|
|
112
|
+
dc.set(session_id, new_session, ttl(options[:expire_after]))
|
113
|
+
session_id
|
114
|
+
end
|
43
115
|
end
|
44
116
|
|
45
117
|
def destroy_session(env, session_id, options)
|
46
|
-
|
47
|
-
|
118
|
+
with_block(env) do |dc|
|
119
|
+
dc.delete(session_id)
|
120
|
+
generate_sid_with(dc) unless options[:drop]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if defined?(Abstract::Persisted)
|
125
|
+
def find_session(req, sid)
|
126
|
+
get_session req.env, sid
|
127
|
+
end
|
128
|
+
|
129
|
+
def write_session(req, sid, session, options)
|
130
|
+
set_session req.env, sid, session, options
|
131
|
+
end
|
132
|
+
|
133
|
+
def delete_session(req, sid, options)
|
134
|
+
destroy_session req.env, sid, options
|
135
|
+
end
|
48
136
|
end
|
49
137
|
|
138
|
+
private
|
139
|
+
|
140
|
+
def extract_dalli_options(options)
|
141
|
+
return [options[:cache]] if options[:cache]
|
142
|
+
|
143
|
+
# Filter out Rack::Session-specific options and apply our defaults
|
144
|
+
mopts = DEFAULT_DALLI_OPTIONS.merge \
|
145
|
+
options.reject {|k, _| DEFAULT_OPTIONS.key? k }
|
146
|
+
mserv = mopts.delete :memcache_server
|
147
|
+
|
148
|
+
if mopts[:pool_size] || mopts[:pool_timeout]
|
149
|
+
popts = {}
|
150
|
+
popts[:size] = mopts.delete :pool_size if mopts[:pool_size]
|
151
|
+
popts[:timeout] = mopts.delete :pool_timeout if mopts[:pool_timeout]
|
152
|
+
|
153
|
+
# For a connection pool, locking is handled at the pool level
|
154
|
+
mopts[:threadsafe] = false unless mopts.key? :threadsafe
|
155
|
+
end
|
156
|
+
|
157
|
+
[nil, mserv, mopts, popts]
|
158
|
+
end
|
159
|
+
|
160
|
+
def generate_sid_with(dc)
|
161
|
+
while true
|
162
|
+
sid = generate_sid
|
163
|
+
break sid unless dc.get(sid)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def with_block(env, default=nil, &block)
|
168
|
+
@mutex.lock if @mutex and env['rack.multithread']
|
169
|
+
@pool.with(&block)
|
170
|
+
rescue ::Dalli::DalliError, Errno::ECONNREFUSED
|
171
|
+
raise if $!.message =~ /undefined class/
|
172
|
+
if $VERBOSE
|
173
|
+
warn "#{self} is unable to find memcached server."
|
174
|
+
warn $!.inspect
|
175
|
+
end
|
176
|
+
default
|
177
|
+
ensure
|
178
|
+
@mutex.unlock if @mutex and @mutex.locked?
|
179
|
+
end
|
180
|
+
|
181
|
+
def ttl(expire_after)
|
182
|
+
expire_after.nil? ? 0 : expire_after + 1
|
183
|
+
end
|
50
184
|
end
|
51
185
|
end
|
52
186
|
end
|