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.

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[:timeout]) || raise(Timeout::Error, "IO timeout")
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[:timeout]) || raise(Timeout::Error, "IO timeout")
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
- loop do
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
- if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
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
- loop do
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[:timeout])
105
+ if IO.select([self], nil, nil, options[:socket_timeout])
67
106
  retry
68
107
  else
69
- raise Timeout::Error, "IO timeout: #{options.inspect}"
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
- 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])
115
+ def read_available
116
+ value = String.new('')
117
+ while true
91
118
  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}"
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
- 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
- end
134
+ class Dalli::Server::KSocket::TCP < TCPSocket
135
+ extend Dalli::Server::TCPSocketOptions
136
+ include Dalli::Server::KSocket
136
137
 
137
- def readfull(count)
138
- value = ''
139
- loop do
140
- value << read(count - value.bytesize)
141
- break if value.bytesize == count
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
- 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."
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
- 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
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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dalli
2
- VERSION = '1.1.4'
3
+ VERSION = '2.7.11'
3
4
  end
@@ -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
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
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
- def generate_sid
21
- loop do
22
- sid = super
23
- break sid unless @pool.get(sid)
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
- unless sid and session = @pool.get(sid)
29
- sid, session = generate_sid, {}
30
- unless @pool.add(sid, session)
31
- raise "Session collision on '#{sid.inspect}'"
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
- expiry = options[:expire_after]
39
- expiry = expiry.nil? ? 0 : expiry + 1
109
+ return false unless session_id
40
110
 
41
- @pool.set session_id, new_session, expiry
42
- session_id
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
- @pool.delete(session_id)
47
- generate_sid unless options[:drop]
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