dalli 3.1.5 → 3.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{History.md → CHANGELOG.md} +55 -2
- data/Gemfile +9 -4
- data/README.md +8 -2
- data/lib/dalli/client.rb +10 -12
- data/lib/dalli/key_manager.rb +11 -3
- data/lib/dalli/pid_cache.rb +40 -0
- data/lib/dalli/pipelined_getter.rb +1 -1
- data/lib/dalli/protocol/base.rb +25 -9
- data/lib/dalli/protocol/binary/request_formatter.rb +3 -3
- data/lib/dalli/protocol/binary/response_processor.rb +2 -2
- data/lib/dalli/protocol/binary/sasl_authentication.rb +1 -1
- data/lib/dalli/protocol/binary.rb +0 -7
- data/lib/dalli/protocol/connection_manager.rb +29 -16
- data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
- data/lib/dalli/protocol/meta/request_formatter.rb +121 -0
- data/lib/dalli/protocol/meta/response_processor.rb +211 -0
- data/lib/dalli/protocol/meta.rb +178 -0
- data/lib/dalli/protocol/response_buffer.rb +1 -0
- data/lib/dalli/protocol/server_config_parser.rb +2 -0
- data/lib/dalli/protocol.rb +11 -0
- data/lib/dalli/ring.rb +4 -2
- data/lib/dalli/servers_arg_normalizer.rb +1 -1
- data/lib/dalli/socket.rb +1 -1
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +1 -0
- data/lib/rack/session/dalli.rb +16 -7
- metadata +11 -96
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
class Meta
|
6
|
+
##
|
7
|
+
# Class that encapsulates logic for processing meta protocol responses
|
8
|
+
# from memcached. Includes logic for pulling data from an IO source
|
9
|
+
# and parsing into local values. Handles errors on unexpected values.
|
10
|
+
##
|
11
|
+
class ResponseProcessor
|
12
|
+
EN = 'EN'
|
13
|
+
END_TOKEN = 'END'
|
14
|
+
EX = 'EX'
|
15
|
+
HD = 'HD'
|
16
|
+
MN = 'MN'
|
17
|
+
NF = 'NF'
|
18
|
+
NS = 'NS'
|
19
|
+
OK = 'OK'
|
20
|
+
RESET = 'RESET'
|
21
|
+
STAT = 'STAT'
|
22
|
+
VA = 'VA'
|
23
|
+
VERSION = 'VERSION'
|
24
|
+
|
25
|
+
def initialize(io_source, value_marshaller)
|
26
|
+
@io_source = io_source
|
27
|
+
@value_marshaller = value_marshaller
|
28
|
+
end
|
29
|
+
|
30
|
+
def meta_get_with_value(cache_nils: false)
|
31
|
+
tokens = error_on_unexpected!([VA, EN, HD])
|
32
|
+
return cache_nils ? ::Dalli::NOT_FOUND : nil if tokens.first == EN
|
33
|
+
return true unless tokens.first == VA
|
34
|
+
|
35
|
+
@value_marshaller.retrieve(read_line, bitflags_from_tokens(tokens))
|
36
|
+
end
|
37
|
+
|
38
|
+
def meta_get_with_value_and_cas
|
39
|
+
tokens = error_on_unexpected!([VA, EN, HD])
|
40
|
+
return [nil, 0] if tokens.first == EN
|
41
|
+
|
42
|
+
cas = cas_from_tokens(tokens)
|
43
|
+
return [nil, cas] unless tokens.first == VA
|
44
|
+
|
45
|
+
[@value_marshaller.retrieve(read_line, bitflags_from_tokens(tokens)), cas]
|
46
|
+
end
|
47
|
+
|
48
|
+
def meta_get_without_value
|
49
|
+
tokens = error_on_unexpected!([EN, HD])
|
50
|
+
tokens.first == EN ? nil : true
|
51
|
+
end
|
52
|
+
|
53
|
+
def meta_set_with_cas
|
54
|
+
tokens = error_on_unexpected!([HD, NS, NF, EX])
|
55
|
+
return false unless tokens.first == HD
|
56
|
+
|
57
|
+
cas_from_tokens(tokens)
|
58
|
+
end
|
59
|
+
|
60
|
+
def meta_set_append_prepend
|
61
|
+
tokens = error_on_unexpected!([HD, NS, NF, EX])
|
62
|
+
return false unless tokens.first == HD
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def meta_delete
|
68
|
+
tokens = error_on_unexpected!([HD, NF, EX])
|
69
|
+
tokens.first == HD
|
70
|
+
end
|
71
|
+
|
72
|
+
def decr_incr
|
73
|
+
tokens = error_on_unexpected!([VA, NF, NS, EX])
|
74
|
+
return false if [NS, EX].include?(tokens.first)
|
75
|
+
return nil if tokens.first == NF
|
76
|
+
|
77
|
+
read_line.to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
def stats
|
81
|
+
tokens = error_on_unexpected!([END_TOKEN, STAT])
|
82
|
+
values = {}
|
83
|
+
while tokens.first != END_TOKEN
|
84
|
+
values[tokens[1]] = tokens[2]
|
85
|
+
tokens = next_line_to_tokens
|
86
|
+
end
|
87
|
+
values
|
88
|
+
end
|
89
|
+
|
90
|
+
def flush
|
91
|
+
error_on_unexpected!([OK])
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset
|
97
|
+
error_on_unexpected!([RESET])
|
98
|
+
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def version
|
103
|
+
tokens = error_on_unexpected!([VERSION])
|
104
|
+
tokens.last
|
105
|
+
end
|
106
|
+
|
107
|
+
def consume_all_responses_until_mn
|
108
|
+
tokens = next_line_to_tokens
|
109
|
+
|
110
|
+
tokens = next_line_to_tokens while tokens.first != MN
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def tokens_from_header_buffer(buf)
|
115
|
+
header = header_from_buffer(buf)
|
116
|
+
tokens = header.split
|
117
|
+
header_len = header.bytesize + TERMINATOR.length
|
118
|
+
body_len = body_len_from_tokens(tokens)
|
119
|
+
[tokens, header_len, body_len]
|
120
|
+
end
|
121
|
+
|
122
|
+
def full_response_from_buffer(tokens, body, resp_size)
|
123
|
+
value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
|
124
|
+
[resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# This method returns an array of values used in a pipelined
|
129
|
+
# getk process. The first value is the number of bytes by
|
130
|
+
# which to advance the pointer in the buffer. If the
|
131
|
+
# complete response is found in the buffer, this will
|
132
|
+
# be the response size. Otherwise it is zero.
|
133
|
+
#
|
134
|
+
# The remaining three values in the array are the ResponseHeader,
|
135
|
+
# key, and value.
|
136
|
+
##
|
137
|
+
def getk_response_from_buffer(buf)
|
138
|
+
# There's no header in the buffer, so don't advance
|
139
|
+
return [0, nil, nil, nil, nil] unless contains_header?(buf)
|
140
|
+
|
141
|
+
tokens, header_len, body_len = tokens_from_header_buffer(buf)
|
142
|
+
|
143
|
+
# We have a complete response that has no body.
|
144
|
+
# This is either the response to the terminating
|
145
|
+
# noop or, if the status is not MN, an intermediate
|
146
|
+
# error response that needs to be discarded.
|
147
|
+
return [header_len, true, nil, nil, nil] if body_len.zero?
|
148
|
+
|
149
|
+
resp_size = header_len + body_len + TERMINATOR.length
|
150
|
+
# The header is in the buffer, but the body is not. As we don't have
|
151
|
+
# a complete response, don't advance the buffer
|
152
|
+
return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
|
153
|
+
|
154
|
+
# The full response is in our buffer, so parse it and return
|
155
|
+
# the values
|
156
|
+
body = buf.slice(header_len, body_len)
|
157
|
+
full_response_from_buffer(tokens, body, resp_size)
|
158
|
+
end
|
159
|
+
|
160
|
+
def contains_header?(buf)
|
161
|
+
buf.include?(TERMINATOR)
|
162
|
+
end
|
163
|
+
|
164
|
+
def header_from_buffer(buf)
|
165
|
+
buf.split(TERMINATOR, 2).first
|
166
|
+
end
|
167
|
+
|
168
|
+
def error_on_unexpected!(expected_codes)
|
169
|
+
tokens = next_line_to_tokens
|
170
|
+
raise Dalli::DalliError, "Response error: #{tokens.first}" unless expected_codes.include?(tokens.first)
|
171
|
+
|
172
|
+
tokens
|
173
|
+
end
|
174
|
+
|
175
|
+
def bitflags_from_tokens(tokens)
|
176
|
+
value_from_tokens(tokens, 'f')&.to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
def cas_from_tokens(tokens)
|
180
|
+
value_from_tokens(tokens, 'c')&.to_i
|
181
|
+
end
|
182
|
+
|
183
|
+
def key_from_tokens(tokens)
|
184
|
+
encoded_key = value_from_tokens(tokens, 'k')
|
185
|
+
base64_encoded = tokens.any?('b')
|
186
|
+
KeyRegularizer.decode(encoded_key, base64_encoded)
|
187
|
+
end
|
188
|
+
|
189
|
+
def body_len_from_tokens(tokens)
|
190
|
+
value_from_tokens(tokens, 's')&.to_i
|
191
|
+
end
|
192
|
+
|
193
|
+
def value_from_tokens(tokens, flag)
|
194
|
+
bitflags_token = tokens.find { |t| t.start_with?(flag) }
|
195
|
+
return 0 unless bitflags_token
|
196
|
+
|
197
|
+
bitflags_token[1..]
|
198
|
+
end
|
199
|
+
|
200
|
+
def read_line
|
201
|
+
@io_source.read_line&.chomp!(TERMINATOR)
|
202
|
+
end
|
203
|
+
|
204
|
+
def next_line_to_tokens
|
205
|
+
line = read_line
|
206
|
+
line&.split || []
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'socket'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
module Dalli
|
8
|
+
module Protocol
|
9
|
+
##
|
10
|
+
# Access point for a single Memcached server, accessed via Memcached's meta
|
11
|
+
# protocol. Contains logic for managing connection state to the server (retries, etc),
|
12
|
+
# formatting requests to the server, and unpacking responses.
|
13
|
+
##
|
14
|
+
class Meta < Base
|
15
|
+
TERMINATOR = "\r\n"
|
16
|
+
|
17
|
+
def response_processor
|
18
|
+
@response_processor ||= ResponseProcessor.new(@connection_manager, @value_marshaller)
|
19
|
+
end
|
20
|
+
|
21
|
+
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Retrieval Commands
|
26
|
+
def get(key, options = nil)
|
27
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
28
|
+
req = RequestFormatter.meta_get(key: encoded_key, base64: base64)
|
29
|
+
write(req)
|
30
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
31
|
+
end
|
32
|
+
|
33
|
+
def quiet_get_request(key)
|
34
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
35
|
+
RequestFormatter.meta_get(key: encoded_key, return_cas: true, base64: base64, quiet: true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def gat(key, ttl, options = nil)
|
39
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
40
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
41
|
+
req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, base64: base64)
|
42
|
+
write(req)
|
43
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
44
|
+
end
|
45
|
+
|
46
|
+
def touch(key, ttl)
|
47
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
48
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
49
|
+
req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, value: false, base64: base64)
|
50
|
+
write(req)
|
51
|
+
response_processor.meta_get_without_value
|
52
|
+
end
|
53
|
+
|
54
|
+
# TODO: This is confusing, as there's a cas command in memcached
|
55
|
+
# and this isn't it. Maybe rename? Maybe eliminate?
|
56
|
+
def cas(key)
|
57
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
58
|
+
req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64)
|
59
|
+
write(req)
|
60
|
+
response_processor.meta_get_with_value_and_cas
|
61
|
+
end
|
62
|
+
|
63
|
+
# Storage Commands
|
64
|
+
def set(key, value, ttl, cas, options)
|
65
|
+
write_storage_req(:set, key, value, ttl, cas, options)
|
66
|
+
response_processor.meta_set_with_cas unless quiet?
|
67
|
+
end
|
68
|
+
|
69
|
+
def add(key, value, ttl, options)
|
70
|
+
write_storage_req(:add, key, value, ttl, nil, options)
|
71
|
+
response_processor.meta_set_with_cas unless quiet?
|
72
|
+
end
|
73
|
+
|
74
|
+
def replace(key, value, ttl, cas, options)
|
75
|
+
write_storage_req(:replace, key, value, ttl, cas, options)
|
76
|
+
response_processor.meta_set_with_cas unless quiet?
|
77
|
+
end
|
78
|
+
|
79
|
+
# rubocop:disable Metrics/ParameterLists
|
80
|
+
def write_storage_req(mode, key, raw_value, ttl = nil, cas = nil, options = {})
|
81
|
+
(value, bitflags) = @value_marshaller.store(key, raw_value, options)
|
82
|
+
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
83
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
84
|
+
req = RequestFormatter.meta_set(key: encoded_key, value: value,
|
85
|
+
bitflags: bitflags, cas: cas,
|
86
|
+
ttl: ttl, mode: mode, quiet: quiet?, base64: base64)
|
87
|
+
write(req)
|
88
|
+
end
|
89
|
+
# rubocop:enable Metrics/ParameterLists
|
90
|
+
|
91
|
+
def append(key, value)
|
92
|
+
write_append_prepend_req(:append, key, value)
|
93
|
+
response_processor.meta_set_append_prepend unless quiet?
|
94
|
+
end
|
95
|
+
|
96
|
+
def prepend(key, value)
|
97
|
+
write_append_prepend_req(:prepend, key, value)
|
98
|
+
response_processor.meta_set_append_prepend unless quiet?
|
99
|
+
end
|
100
|
+
|
101
|
+
# rubocop:disable Metrics/ParameterLists
|
102
|
+
def write_append_prepend_req(mode, key, value, ttl = nil, cas = nil, _options = {})
|
103
|
+
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
104
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
105
|
+
req = RequestFormatter.meta_set(key: encoded_key, value: value, base64: base64,
|
106
|
+
cas: cas, ttl: ttl, mode: mode, quiet: quiet?)
|
107
|
+
write(req)
|
108
|
+
end
|
109
|
+
# rubocop:enable Metrics/ParameterLists
|
110
|
+
|
111
|
+
# Delete Commands
|
112
|
+
def delete(key, cas)
|
113
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
114
|
+
req = RequestFormatter.meta_delete(key: encoded_key, cas: cas,
|
115
|
+
base64: base64, quiet: quiet?)
|
116
|
+
write(req)
|
117
|
+
response_processor.meta_delete unless quiet?
|
118
|
+
end
|
119
|
+
|
120
|
+
# Arithmetic Commands
|
121
|
+
def decr(key, count, ttl, initial)
|
122
|
+
decr_incr false, key, count, ttl, initial
|
123
|
+
end
|
124
|
+
|
125
|
+
def incr(key, count, ttl, initial)
|
126
|
+
decr_incr true, key, count, ttl, initial
|
127
|
+
end
|
128
|
+
|
129
|
+
def decr_incr(incr, key, delta, ttl, initial)
|
130
|
+
ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
|
131
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
132
|
+
write(RequestFormatter.meta_arithmetic(key: encoded_key, delta: delta, initial: initial, incr: incr, ttl: ttl,
|
133
|
+
quiet: quiet?, base64: base64))
|
134
|
+
response_processor.decr_incr unless quiet?
|
135
|
+
end
|
136
|
+
|
137
|
+
# Other Commands
|
138
|
+
def flush(delay = 0)
|
139
|
+
write(RequestFormatter.flush(delay: delay))
|
140
|
+
response_processor.flush unless quiet?
|
141
|
+
end
|
142
|
+
|
143
|
+
# Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
|
144
|
+
# We need to read all the responses at once.
|
145
|
+
def noop
|
146
|
+
write_noop
|
147
|
+
response_processor.consume_all_responses_until_mn
|
148
|
+
end
|
149
|
+
|
150
|
+
def stats(info = nil)
|
151
|
+
write(RequestFormatter.stats(info))
|
152
|
+
response_processor.stats
|
153
|
+
end
|
154
|
+
|
155
|
+
def reset_stats
|
156
|
+
write(RequestFormatter.stats('reset'))
|
157
|
+
response_processor.reset
|
158
|
+
end
|
159
|
+
|
160
|
+
def version
|
161
|
+
write(RequestFormatter.version)
|
162
|
+
response_processor.version
|
163
|
+
end
|
164
|
+
|
165
|
+
def write_noop
|
166
|
+
write(RequestFormatter.meta_noop)
|
167
|
+
end
|
168
|
+
|
169
|
+
def authenticate_connection
|
170
|
+
raise Dalli::DalliError, 'Authentication not supported for the meta protocol.'
|
171
|
+
end
|
172
|
+
|
173
|
+
require_relative 'meta/key_regularizer'
|
174
|
+
require_relative 'meta/request_formatter'
|
175
|
+
require_relative 'meta/response_processor'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/lib/dalli/protocol.rb
CHANGED
@@ -1,8 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'timeout'
|
4
|
+
|
3
5
|
module Dalli
|
4
6
|
module Protocol
|
5
7
|
# Preserved for backwards compatibility. Should be removed in 4.0
|
6
8
|
NOT_FOUND = ::Dalli::NOT_FOUND
|
9
|
+
|
10
|
+
# Ruby 3.2 raises IO::TimeoutError on blocking reads/writes, but
|
11
|
+
# it is not defined in earlier Ruby versions.
|
12
|
+
TIMEOUT_ERRORS =
|
13
|
+
if defined?(IO::TimeoutError)
|
14
|
+
[Timeout::Error, IO::TimeoutError]
|
15
|
+
else
|
16
|
+
[Timeout::Error]
|
17
|
+
end
|
7
18
|
end
|
8
19
|
end
|
data/lib/dalli/ring.rb
CHANGED
@@ -23,8 +23,10 @@ module Dalli
|
|
23
23
|
|
24
24
|
attr_accessor :servers, :continuum
|
25
25
|
|
26
|
-
def initialize(
|
27
|
-
@servers =
|
26
|
+
def initialize(servers_arg, protocol_implementation, options)
|
27
|
+
@servers = servers_arg.map do |s|
|
28
|
+
protocol_implementation.new(s, options)
|
29
|
+
end
|
28
30
|
@continuum = nil
|
29
31
|
@continuum = build_continuum(servers) if servers.size > 1
|
30
32
|
|
data/lib/dalli/socket.rb
CHANGED
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
@@ -65,6 +65,7 @@ require_relative 'dalli/protocol'
|
|
65
65
|
require_relative 'dalli/protocol/base'
|
66
66
|
require_relative 'dalli/protocol/binary'
|
67
67
|
require_relative 'dalli/protocol/connection_manager'
|
68
|
+
require_relative 'dalli/protocol/meta'
|
68
69
|
require_relative 'dalli/protocol/response_buffer'
|
69
70
|
require_relative 'dalli/protocol/server_config_parser'
|
70
71
|
require_relative 'dalli/protocol/ttl_sanitizer'
|
data/lib/rack/session/dalli.rb
CHANGED
@@ -82,6 +82,9 @@ module Rack
|
|
82
82
|
def write_session(_req, sid, session, options)
|
83
83
|
return false unless sid
|
84
84
|
|
85
|
+
key = memcached_key_from_sid(sid)
|
86
|
+
return false unless key
|
87
|
+
|
85
88
|
with_dalli_client(false) do |dc|
|
86
89
|
dc.set(memcached_key_from_sid(sid), session, ttl(options[:expire_after]))
|
87
90
|
sid
|
@@ -90,7 +93,8 @@ module Rack
|
|
90
93
|
|
91
94
|
def delete_session(_req, sid, options)
|
92
95
|
with_dalli_client do |dc|
|
93
|
-
|
96
|
+
key = memcached_key_from_sid(sid)
|
97
|
+
dc.delete(key) if key
|
94
98
|
generate_sid_with(dc) unless options[:drop]
|
95
99
|
end
|
96
100
|
end
|
@@ -98,20 +102,24 @@ module Rack
|
|
98
102
|
private
|
99
103
|
|
100
104
|
def memcached_key_from_sid(sid)
|
101
|
-
sid.private_id
|
105
|
+
sid.private_id if sid.respond_to?(:private_id)
|
102
106
|
end
|
103
107
|
|
104
108
|
def existing_session_for_sid(client, sid)
|
105
109
|
return nil unless sid && !sid.empty?
|
106
110
|
|
107
|
-
|
111
|
+
key = memcached_key_from_sid(sid)
|
112
|
+
return nil if key.nil?
|
113
|
+
|
114
|
+
client.get(key)
|
108
115
|
end
|
109
116
|
|
110
117
|
def create_sid_with_empty_session(client)
|
111
118
|
loop do
|
112
119
|
sid = generate_sid_with(client)
|
120
|
+
key = memcached_key_from_sid(sid)
|
113
121
|
|
114
|
-
break sid if client.add(
|
122
|
+
break sid if key && client.add(key, {}, @default_ttl)
|
115
123
|
end
|
116
124
|
end
|
117
125
|
|
@@ -119,7 +127,8 @@ module Rack
|
|
119
127
|
loop do
|
120
128
|
raw_sid = generate_sid
|
121
129
|
sid = raw_sid.is_a?(String) ? Rack::Session::SessionId.new(raw_sid) : raw_sid
|
122
|
-
|
130
|
+
key = memcached_key_from_sid(sid)
|
131
|
+
break sid unless key && client.get(key)
|
123
132
|
end
|
124
133
|
end
|
125
134
|
|
@@ -161,7 +170,7 @@ module Rack
|
|
161
170
|
def ensure_connection_pool_added!
|
162
171
|
require 'connection_pool'
|
163
172
|
rescue LoadError => e
|
164
|
-
warn "You don't have connection_pool installed in your application. "\
|
173
|
+
warn "You don't have connection_pool installed in your application. " \
|
165
174
|
'Please add it to your Gemfile and run bundle install'
|
166
175
|
raise e
|
167
176
|
end
|
@@ -169,7 +178,7 @@ module Rack
|
|
169
178
|
def with_dalli_client(result_on_error = nil, &block)
|
170
179
|
@data.with(&block)
|
171
180
|
rescue ::Dalli::DalliError, Errno::ECONNREFUSED
|
172
|
-
raise if
|
181
|
+
raise if $ERROR_INFO.message.include?('undefined class')
|
173
182
|
|
174
183
|
if $VERBOSE
|
175
184
|
warn "#{self} is unable to find memcached server."
|