dalli 5.2.6 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'uri'
4
+
3
5
  module Dalli
4
6
  ##
5
7
  # This module contains methods for validating and normalizing the servers
@@ -26,6 +28,8 @@ module Dalli
26
28
  module ServersArgNormalizer
27
29
  ENV_VAR_NAME = 'MEMCACHE_SERVERS'
28
30
  DEFAULT_SERVERS = ['127.0.0.1:11211'].freeze
31
+ URI_CREDENTIAL_WARNING = 'Dalli 5.0 removed SASL authentication. ' \
32
+ 'Credentials in memcached:// URIs are ignored.'
29
33
 
30
34
  ##
31
35
  # Normalizes the argument into an array of servers.
@@ -34,7 +38,21 @@ module Dalli
34
38
  def self.normalize_servers(arg)
35
39
  arg = apply_defaults(arg)
36
40
  validate_type(arg)
37
- Array(arg).flat_map { |s| s.split(',') }.reject(&:empty?)
41
+ Array(arg).flat_map { |s| s.split(',') }.reject(&:empty?).map { |s| without_credentials(s) }
42
+ end
43
+
44
+ # Dalli 5 doesn't use credentials in a memcached:// URI, so they are
45
+ # dropped here, with a warning, rather than kept in the client's server
46
+ # list where #inspect would show them.
47
+ def self.without_credentials(server)
48
+ return server unless server.start_with?('memcached://') && server.include?('@')
49
+
50
+ uri = URI.parse(server)
51
+ return server unless uri.userinfo
52
+
53
+ Dalli.logger.warn(URI_CREDENTIAL_WARNING)
54
+ uri.user = nil # clears the password too
55
+ uri.to_s
38
56
  end
39
57
 
40
58
  def self.apply_defaults(arg)
data/lib/dalli/socket.rb CHANGED
@@ -110,8 +110,18 @@ module Dalli
110
110
  # OpenSSL can hold decrypted bytes that IO.select won't report as readable.
111
111
  # With OpenSSL's default (no read-ahead) a short read has already used up
112
112
  # the current record, so this is normally 0; it guards read-ahead setups.
113
- def buffered_data?
114
- pending.positive?
113
+ #
114
+ # jruby-openssl can take the next record off the socket during a short
115
+ # read and hold it where neither pending nor IO.select sees it, so on
116
+ # JRuby a read continues until the socket would block.
117
+ if RUBY_ENGINE == 'jruby'
118
+ def buffered_data?
119
+ true
120
+ end
121
+ else
122
+ def buffered_data?
123
+ pending.positive?
124
+ end
115
125
  end
116
126
 
117
127
  unless method_defined?(:wait_readable)
@@ -182,6 +192,9 @@ module Dalli
182
192
  end
183
193
 
184
194
  def self.init_socket_options(sock, options)
195
+ # CRuby sets this on every socket; TruffleRuby doesn't, so a process
196
+ # the app spawns would inherit the connection and hold it open
197
+ sock.close_on_exec = true
185
198
  configure_tcp_options(sock, options)
186
199
  configure_socket_buffers(sock, options)
187
200
  configure_timeout(sock, options)
@@ -307,6 +320,8 @@ module Dalli
307
320
  end
308
321
 
309
322
  def self.init_socket_options(sock, options)
323
+ # Not inherited by spawned processes; see TCP.init_socket_options
324
+ sock.close_on_exec = true
310
325
  # https://man7.org/linux/man-pages/man7/unix.7.html
311
326
  sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
312
327
  sock.timeout = options[:socket_timeout] if options[:socket_timeout] && sock.respond_to?(:timeout=)
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '5.2.6'
4
+ VERSION = '5.3.0'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.6.27'
7
7
  end
data/lib/dalli.rb CHANGED
@@ -29,7 +29,19 @@ module Dalli
29
29
  class ServerError < DalliError; end
30
30
 
31
31
  # socket/server communication error that can be retried
32
- class RetryableNetworkError < NetworkError; end
32
+ class RetryableNetworkError < NetworkError
33
+ def initialize(message = nil, request_sent: false)
34
+ super(message)
35
+ @request_sent = request_sent
36
+ end
37
+
38
+ # True when some of the failed request was handed to the socket, so
39
+ # memcached may have applied it even though its reply was lost. Only a
40
+ # request that is safe to send twice is retried then.
41
+ def request_sent?
42
+ @request_sent
43
+ end
44
+ end
33
45
 
34
46
  # a multi-get reply named a key that wasn't requested, so the rest of that
35
47
  # server's replies can't be trusted
@@ -71,6 +83,7 @@ require_relative 'dalli/compressor'
71
83
  require_relative 'dalli/cache_result'
72
84
  require_relative 'dalli/client'
73
85
  require_relative 'dalli/key_manager'
86
+ require_relative 'dalli/key_grouping'
74
87
  require_relative 'dalli/pipelined_getter'
75
88
  require_relative 'dalli/pipelined_setter'
76
89
  require_relative 'dalli/pipelined_deleter'
@@ -46,7 +46,10 @@ module Rack
46
46
  #
47
47
  # It is not recommended to set `:expires_in`. Instead, use `:expire_after`,
48
48
  # which will control both the expiration of the client cookie as well
49
- # as the expiration of the corresponding entry in memcached.
49
+ # as the expiration of the corresponding entry in memcached. Without
50
+ # `:expire_after`, sessions are stored with no expiry: one stays in
51
+ # memcached until it's deleted (for example by a logout) or memcached
52
+ # evicts it, and a stolen session cookie keeps working until then.
50
53
  #
51
54
  # Rack::Session::Dalli also accepts a host of options that control how
52
55
  # the sessions and session cookies are managed, including the
@@ -144,6 +147,11 @@ module Rack
144
147
  sid.private_id if sid.respond_to?(:private_id)
145
148
  end
146
149
 
150
+ # A session that can't be decoded, for example after a change of
151
+ # serializer or a deploy that removed a class stored in it, is treated
152
+ # as missing, so the request gets a new session. Otherwise the session
153
+ # id couldn't be used or replaced, and the user couldn't log in until
154
+ # the cookie was cleared.
147
155
  def existing_session_for_sid(client, sid)
148
156
  return nil unless sid && !sid.empty?
149
157
 
@@ -151,6 +159,9 @@ module Rack
151
159
  return nil if key.nil?
152
160
 
153
161
  client.get(key)
162
+ rescue ::Dalli::UnmarshalError => e
163
+ ::Dalli.logger.warn("#{self.class} couldn't decode a stored session, so it started a new one: #{e.message}")
164
+ nil
154
165
  end
155
166
 
156
167
  def create_sid_with_empty_session(client)
@@ -231,8 +242,6 @@ module Rack
231
242
  def with_dalli_client(result_on_error = nil, &)
232
243
  @data.with(&)
233
244
  rescue ::Dalli::DalliError, Errno::ECONNREFUSED
234
- raise if $ERROR_INFO.message.include?('undefined class')
235
-
236
245
  if $VERBOSE
237
246
  warn "#{self} is unable to find memcached server."
238
247
  warn $ERROR_INFO.inspect
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: 5.2.6
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
@@ -44,6 +44,7 @@ files:
44
44
  - lib/dalli/flags.rb
45
45
  - lib/dalli/instrumentation.rb
46
46
  - lib/dalli/json_serializer.rb
47
+ - lib/dalli/key_grouping.rb
47
48
  - lib/dalli/key_manager.rb
48
49
  - lib/dalli/options.rb
49
50
  - lib/dalli/pipelined_deleter.rb