dalli 3.1.5 → 3.2.6
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.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9a374b5939ef874ce8067059c88b088c9129e7ab09ff891b1ad1ca3cf13c3ce
|
4
|
+
data.tar.gz: d5937c697bcd86a6d3d4207d328ce7e73c854b93a1d19e402630c3f80caec2ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36010355440b325c1f8d5b922e882a8a8e199b8bd48dda92b1dcc4866f29e81b536ce41ff4896441c6c2c4f4e8e8faaf891ca6355e47de91733baf10bdfa9eee
|
7
|
+
data.tar.gz: 0c2b42e88960838ceef44c6c0f696b938925ade65da7c8791a07539ca36e18d6e874693dfde840a08e78ac9076f6810735a05d137c98d8c64387414f89d6f016
|
data/{History.md → CHANGELOG.md}
RENAMED
@@ -4,16 +4,66 @@ Dalli Changelog
|
|
4
4
|
Unreleased
|
5
5
|
==========
|
6
6
|
|
7
|
+
3.2.6
|
8
|
+
==========
|
9
|
+
|
10
|
+
- Rescue IO::TimeoutError raised by Ruby since 3.2.0 on blocking reads/writes (skaes)
|
11
|
+
- Fix rubydoc link (JuanitoFatas)
|
12
|
+
|
13
|
+
3.2.5
|
14
|
+
==========
|
15
|
+
|
16
|
+
- Better handle memcached requests being interrupted by Thread#raise or Thread#kill (byroot)
|
17
|
+
- Unexpected errors are no longer treated as `Dalli::NetworkError`, including errors raised by `Timeout.timeout` (byroot)
|
18
|
+
|
19
|
+
3.2.4
|
20
|
+
==========
|
21
|
+
|
22
|
+
- Cache PID calls for performance since glibc no longer caches in recent versions (byroot)
|
23
|
+
- Preallocate the read buffer in Socket#readfull (byroot)
|
24
|
+
|
25
|
+
3.2.3
|
26
|
+
==========
|
27
|
+
|
28
|
+
- Sanitize CAS inputs to ensure additional commands are not passed to memcached (xhzeem / petergoldstein)
|
29
|
+
- Sanitize input to flush command to ensure additional commands are not passed to memcached (xhzeem / petergoldstein)
|
30
|
+
- Namespaces passed as procs are now evaluated every time, as opposed to just on initialization (nrw505)
|
31
|
+
- Fix missing require of uri in ServerConfigParser (adam12)
|
32
|
+
- Fix link to the CHANGELOG.md file in README.md (rud)
|
33
|
+
|
34
|
+
3.2.2
|
35
|
+
==========
|
36
|
+
|
37
|
+
- Ensure apps are resilient against old session ids (kbrock)
|
38
|
+
|
39
|
+
3.2.1
|
40
|
+
==========
|
41
|
+
|
42
|
+
- Fix null replacement bug on some SASL-authenticated services (veritas1)
|
43
|
+
|
44
|
+
3.2.0
|
45
|
+
==========
|
46
|
+
|
47
|
+
- BREAKING CHANGE: Remove protocol_implementation client option (petergoldstein)
|
48
|
+
- Add protocol option with meta implementation (petergoldstein)
|
49
|
+
|
50
|
+
3.1.6
|
51
|
+
==========
|
52
|
+
|
53
|
+
- Fix bug with cas/cas! with "Not found" value (petergoldstein)
|
54
|
+
- Add Ruby 3.1 to CI (petergoldstein)
|
55
|
+
- Replace reject(&:nil?) with compact (petergoldstein)
|
56
|
+
|
7
57
|
3.1.5
|
8
58
|
==========
|
9
59
|
|
10
|
-
- Fix bug with get_cas key with "Not found" value
|
60
|
+
- Fix bug with get_cas key with "Not found" value (petergoldstein)
|
11
61
|
- Replace should return nil, not raise error, on miss (petergoldstein)
|
12
62
|
|
13
63
|
3.1.4
|
14
64
|
==========
|
15
65
|
|
16
|
-
- Improve response parsing performance (
|
66
|
+
- Improve response parsing performance (byroot)
|
17
67
|
- Reorganize binary protocol parsing a bit (petergoldstein)
|
18
68
|
- Fix handling of non-ASCII keys in get_multi (petergoldstein)
|
19
69
|
|
@@ -100,6 +150,9 @@ Unreleased
|
|
100
150
|
* The Rack session adapter has been refactored to remove support for thread-unsafe
|
101
151
|
configurations. You will need to include the `connection_pool` gem in
|
102
152
|
your Gemfile to ensure session operations are thread-safe.
|
153
|
+
* When using namespaces, the algorithm for calculating truncated keys was
|
154
|
+
changed. Non-truncated keys and truncated keys for the non-namespace
|
155
|
+
case were left unchanged.
|
103
156
|
|
104
157
|
- Raise NetworkError when multi response gets into corrupt state (mervync, #783)
|
105
158
|
- Validate servers argument (semaperepelitsa, petergoldstein, #776)
|
data/Gemfile
CHANGED
@@ -4,13 +4,18 @@ source 'https://rubygems.org'
|
|
4
4
|
|
5
5
|
gemspec
|
6
6
|
|
7
|
-
group :test do
|
8
|
-
gem '
|
9
|
-
gem '
|
7
|
+
group :development, :test do
|
8
|
+
gem 'connection_pool'
|
9
|
+
gem 'minitest', '~> 5'
|
10
|
+
gem 'rack', '~> 2.0', '>= 2.2.0'
|
11
|
+
gem 'rake', '~> 13.0'
|
10
12
|
gem 'rubocop'
|
11
13
|
gem 'rubocop-minitest'
|
12
14
|
gem 'rubocop-performance'
|
13
15
|
gem 'rubocop-rake'
|
14
|
-
gem 'ruby-prof', platform: :mri
|
15
16
|
gem 'simplecov'
|
16
17
|
end
|
18
|
+
|
19
|
+
group :test do
|
20
|
+
gem 'ruby-prof', platform: :mri
|
21
|
+
end
|
data/README.md
CHANGED
@@ -23,11 +23,17 @@ The name is a variant of Salvador Dali for his famous painting [The Persistence
|
|
23
23
|
* [Announcements](https://github.com/petergoldstein/dalli/discussions/categories/announcements) - Announcements of interest to the Dalli community will be posted here.
|
24
24
|
* [Bug Reports](https://github.com/petergoldstein/dalli/issues) - If you discover a problem with Dalli, please submit a bug report in the tracker.
|
25
25
|
* [Forum](https://github.com/petergoldstein/dalli/discussions/categories/q-a) - If you have questions about Dalli, please post them here.
|
26
|
-
* [Client API](https://www.rubydoc.info/
|
26
|
+
* [Client API](https://www.rubydoc.info/gems/dalli) - Ruby documentation for the `Dalli::Client` API
|
27
|
+
|
28
|
+
## Development
|
29
|
+
|
30
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can run `bin/console` for an interactive prompt that will allow you to experiment.
|
31
|
+
|
32
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
27
33
|
|
28
34
|
## Contributing
|
29
35
|
|
30
|
-
If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update
|
36
|
+
If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update the [changelog](CHANGELOG.md) with a one sentence description of your fix so you get credit as a contributor.
|
31
37
|
|
32
38
|
## Appreciation
|
33
39
|
|
data/lib/dalli/client.rb
CHANGED
@@ -43,11 +43,11 @@ module Dalli
|
|
43
43
|
# #fetch operations.
|
44
44
|
# - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
|
45
45
|
# useful for injecting a FIPS compliant hash object.
|
46
|
-
# - :
|
47
|
-
#
|
46
|
+
# - :protocol - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
|
47
|
+
# to communicate with memcached.
|
48
48
|
#
|
49
49
|
def initialize(servers = nil, options = {})
|
50
|
-
@
|
50
|
+
@normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
51
51
|
@options = normalize_options(options)
|
52
52
|
@key_manager = ::Dalli::KeyManager.new(@options)
|
53
53
|
@ring = nil
|
@@ -375,7 +375,6 @@ module Dalli
|
|
375
375
|
|
376
376
|
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
377
377
|
(value, cas) = perform(:cas, key)
|
378
|
-
value = nil if !value || value == 'Not found'
|
379
378
|
return if value.nil? && !always_set
|
380
379
|
|
381
380
|
newvalue = yield(value)
|
@@ -393,17 +392,16 @@ module Dalli
|
|
393
392
|
end
|
394
393
|
|
395
394
|
def ring
|
396
|
-
|
397
|
-
# to the Ring
|
398
|
-
@ring ||= Dalli::Ring.new(
|
399
|
-
@servers.map do |s|
|
400
|
-
protocol_implementation.new(s, @options)
|
401
|
-
end, @options
|
402
|
-
)
|
395
|
+
@ring ||= Dalli::Ring.new(@normalized_servers, protocol_implementation, @options)
|
403
396
|
end
|
404
397
|
|
405
398
|
def protocol_implementation
|
406
|
-
@protocol_implementation ||= @options
|
399
|
+
@protocol_implementation ||= case @options[:protocol]&.to_s
|
400
|
+
when 'meta'
|
401
|
+
Dalli::Protocol::Meta
|
402
|
+
else
|
403
|
+
Dalli::Protocol::Binary
|
404
|
+
end
|
407
405
|
end
|
408
406
|
|
409
407
|
##
|
data/lib/dalli/key_manager.rb
CHANGED
@@ -61,7 +61,7 @@ module Dalli
|
|
61
61
|
def key_with_namespace(key)
|
62
62
|
return key if namespace.nil?
|
63
63
|
|
64
|
-
"#{
|
64
|
+
"#{evaluate_namespace}#{NAMESPACE_SEPARATOR}#{key}"
|
65
65
|
end
|
66
66
|
|
67
67
|
def key_without_namespace(key)
|
@@ -75,6 +75,8 @@ module Dalli
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def namespace_regexp
|
78
|
+
return /\A#{Regexp.escape(evaluate_namespace)}:/ if namespace.is_a?(Proc)
|
79
|
+
|
78
80
|
@namespace_regexp ||= /\A#{Regexp.escape(namespace)}:/.freeze unless namespace.nil?
|
79
81
|
end
|
80
82
|
|
@@ -87,9 +89,15 @@ module Dalli
|
|
87
89
|
def namespace_from_options
|
88
90
|
raw_namespace = @key_options[:namespace]
|
89
91
|
return nil unless raw_namespace
|
90
|
-
return raw_namespace.
|
92
|
+
return raw_namespace.to_s unless raw_namespace.is_a?(Proc)
|
93
|
+
|
94
|
+
raw_namespace
|
95
|
+
end
|
96
|
+
|
97
|
+
def evaluate_namespace
|
98
|
+
return namespace.call.to_s if namespace.is_a?(Proc)
|
91
99
|
|
92
|
-
|
100
|
+
namespace
|
93
101
|
end
|
94
102
|
|
95
103
|
##
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
##
|
5
|
+
# Dalli::PIDCache is a wrapper class for PID checking to avoid system calls when checking the PID.
|
6
|
+
##
|
7
|
+
module PIDCache
|
8
|
+
if !Process.respond_to?(:fork) # JRuby or TruffleRuby
|
9
|
+
@pid = Process.pid
|
10
|
+
singleton_class.attr_reader(:pid)
|
11
|
+
elsif Process.respond_to?(:_fork) # Ruby 3.1+
|
12
|
+
class << self
|
13
|
+
attr_reader :pid
|
14
|
+
|
15
|
+
def update!
|
16
|
+
@pid = Process.pid
|
17
|
+
end
|
18
|
+
end
|
19
|
+
update!
|
20
|
+
|
21
|
+
##
|
22
|
+
# Dalli::PIDCache::CoreExt hooks into Process to be able to reset the PID cache after fork
|
23
|
+
##
|
24
|
+
module CoreExt
|
25
|
+
def _fork
|
26
|
+
child_pid = super
|
27
|
+
PIDCache.update! if child_pid.zero?
|
28
|
+
child_pid
|
29
|
+
end
|
30
|
+
end
|
31
|
+
Process.singleton_class.prepend(CoreExt)
|
32
|
+
else # Ruby 3.0 or older
|
33
|
+
class << self
|
34
|
+
def pid
|
35
|
+
Process.pid
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -167,7 +167,7 @@ module Dalli
|
|
167
167
|
groups = @ring.keys_grouped_by_server(keys)
|
168
168
|
if (unfound_keys = groups.delete(nil))
|
169
169
|
Dalli.logger.debug do
|
170
|
-
"unable to get keys for #{unfound_keys.length} keys "\
|
170
|
+
"unable to get keys for #{unfound_keys.length} keys " \
|
171
171
|
'because no matching server was found'
|
172
172
|
end
|
173
173
|
end
|
data/lib/dalli/protocol/base.rb
CHANGED
@@ -32,7 +32,13 @@ module Dalli
|
|
32
32
|
verify_state(opkey)
|
33
33
|
|
34
34
|
begin
|
35
|
-
|
35
|
+
@connection_manager.start_request!
|
36
|
+
response = send(opkey, *args)
|
37
|
+
|
38
|
+
# pipelined_get emit query but doesn't read the response(s)
|
39
|
+
@connection_manager.finish_request! unless opkey == :pipelined_get
|
40
|
+
|
41
|
+
response
|
36
42
|
rescue Dalli::MarshalError => e
|
37
43
|
log_marshal_err(args.first, e)
|
38
44
|
raise
|
@@ -40,7 +46,8 @@ module Dalli
|
|
40
46
|
raise
|
41
47
|
rescue StandardError => e
|
42
48
|
log_unexpected_err(e)
|
43
|
-
|
49
|
+
close
|
50
|
+
raise
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
@@ -65,10 +72,9 @@ module Dalli
|
|
65
72
|
#
|
66
73
|
# Returns nothing.
|
67
74
|
def pipeline_response_setup
|
68
|
-
|
75
|
+
verify_pipelined_state(:getkq)
|
69
76
|
write_noop
|
70
77
|
response_buffer.reset
|
71
|
-
@connection_manager.start_request!
|
72
78
|
end
|
73
79
|
|
74
80
|
# Attempt to receive and parse as many key/value pairs as possible
|
@@ -100,7 +106,7 @@ module Dalli
|
|
100
106
|
end
|
101
107
|
|
102
108
|
values
|
103
|
-
rescue SystemCallError,
|
109
|
+
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
|
104
110
|
@connection_manager.error_on_request!(e)
|
105
111
|
end
|
106
112
|
|
@@ -127,11 +133,11 @@ module Dalli
|
|
127
133
|
end
|
128
134
|
|
129
135
|
def username
|
130
|
-
@options[:username] || ENV
|
136
|
+
@options[:username] || ENV.fetch('MEMCACHE_USERNAME', nil)
|
131
137
|
end
|
132
138
|
|
133
139
|
def password
|
134
|
-
@options[:password] || ENV
|
140
|
+
@options[:password] || ENV.fetch('MEMCACHE_PASSWORD', nil)
|
135
141
|
end
|
136
142
|
|
137
143
|
def require_auth?
|
@@ -147,6 +153,13 @@ module Dalli
|
|
147
153
|
|
148
154
|
private
|
149
155
|
|
156
|
+
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
157
|
+
def verify_allowed_quiet!(opkey)
|
158
|
+
return if ALLOWED_QUIET_OPS.include?(opkey)
|
159
|
+
|
160
|
+
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
161
|
+
end
|
162
|
+
|
150
163
|
##
|
151
164
|
# Checks to see if we can execute the specified operation. Checks
|
152
165
|
# whether the connection is in use, and whether the command is allowed
|
@@ -162,6 +175,11 @@ module Dalli
|
|
162
175
|
raise_down_error unless ensure_connected!
|
163
176
|
end
|
164
177
|
|
178
|
+
def verify_pipelined_state(_opkey)
|
179
|
+
@connection_manager.confirm_in_progress!
|
180
|
+
raise_down_error unless connected?
|
181
|
+
end
|
182
|
+
|
165
183
|
# The socket connection to the underlying server is initialized as a side
|
166
184
|
# effect of this call. In fact, this is the ONLY place where that
|
167
185
|
# socket connection is initialized.
|
@@ -190,8 +208,6 @@ module Dalli
|
|
190
208
|
authenticate_connection if require_auth?
|
191
209
|
@version = version # Connect socket if not authed
|
192
210
|
up!
|
193
|
-
rescue Dalli::DalliError
|
194
|
-
raise
|
195
211
|
end
|
196
212
|
|
197
213
|
def pipelined_get(keys)
|
@@ -86,7 +86,7 @@ module Dalli
|
|
86
86
|
touch: TTL_AND_KEY,
|
87
87
|
gat: TTL_AND_KEY
|
88
88
|
}.freeze
|
89
|
-
FORMAT = BODY_FORMATS.transform_values { |v| REQ_HEADER_FORMAT + v
|
89
|
+
FORMAT = BODY_FORMATS.transform_values { |v| REQ_HEADER_FORMAT + v }
|
90
90
|
|
91
91
|
# rubocop:disable Metrics/ParameterLists
|
92
92
|
def self.standard_request(opkey:, key: nil, value: nil, opaque: 0, cas: 0, bitflags: nil, ttl: nil)
|
@@ -94,7 +94,7 @@ module Dalli
|
|
94
94
|
key_len = key.nil? ? 0 : key.bytesize
|
95
95
|
value_len = value.nil? ? 0 : value.bytesize
|
96
96
|
header = [REQUEST, OPCODES[opkey], key_len, extra_len, 0, 0, extra_len + key_len + value_len, opaque, cas]
|
97
|
-
body = [bitflags, ttl, key, value].
|
97
|
+
body = [bitflags, ttl, key, value].compact
|
98
98
|
(header + body).pack(FORMAT[opkey])
|
99
99
|
end
|
100
100
|
# rubocop:enable Metrics/ParameterLists
|
@@ -109,7 +109,7 @@ module Dalli
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def self.as_8byte_uint(val)
|
112
|
-
[val >> 32,
|
112
|
+
[val >> 32, val & 0xFFFFFFFF]
|
113
113
|
end
|
114
114
|
end
|
115
115
|
end
|
@@ -50,9 +50,9 @@ module Dalli
|
|
50
50
|
extra_len = resp_header.extra_len
|
51
51
|
key_len = resp_header.key_len
|
52
52
|
bitflags = extra_len.positive? ? body.unpack1('N') : 0x0
|
53
|
-
key = body.byteslice(extra_len, key_len).force_encoding(
|
53
|
+
key = body.byteslice(extra_len, key_len).force_encoding(Encoding::UTF_8) if key_len.positive?
|
54
54
|
value = body.byteslice((extra_len + key_len)..-1)
|
55
|
-
value =
|
55
|
+
value = @value_marshaller.retrieve(value, bitflags) if parse_as_stored_value
|
56
56
|
[key, value]
|
57
57
|
end
|
58
58
|
|
@@ -18,13 +18,6 @@ module Dalli
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
22
|
-
def verify_allowed_quiet!(opkey)
|
23
|
-
return if ALLOWED_QUIET_OPS.include?(opkey)
|
24
|
-
|
25
|
-
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
26
|
-
end
|
27
|
-
|
28
21
|
# Retrieval Commands
|
29
22
|
def get(key, options = nil)
|
30
23
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
@@ -4,6 +4,8 @@ require 'English'
|
|
4
4
|
require 'socket'
|
5
5
|
require 'timeout'
|
6
6
|
|
7
|
+
require 'dalli/pid_cache'
|
8
|
+
|
7
9
|
module Dalli
|
8
10
|
module Protocol
|
9
11
|
##
|
@@ -51,7 +53,8 @@ module Dalli
|
|
51
53
|
Dalli.logger.debug { "Dalli::Server#connect #{name}" }
|
52
54
|
|
53
55
|
@sock = memcached_socket
|
54
|
-
@pid =
|
56
|
+
@pid = PIDCache.pid
|
57
|
+
@request_in_progress = false
|
55
58
|
rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
|
56
59
|
# SocketError = DNS resolution failure
|
57
60
|
error_on_request!(e)
|
@@ -96,7 +99,13 @@ module Dalli
|
|
96
99
|
end
|
97
100
|
|
98
101
|
def confirm_ready!
|
99
|
-
|
102
|
+
close if request_in_progress?
|
103
|
+
close_on_fork if fork_detected?
|
104
|
+
end
|
105
|
+
|
106
|
+
def confirm_in_progress!
|
107
|
+
raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless request_in_progress?
|
108
|
+
|
100
109
|
close_on_fork if fork_detected?
|
101
110
|
end
|
102
111
|
|
@@ -122,10 +131,14 @@ module Dalli
|
|
122
131
|
end
|
123
132
|
|
124
133
|
def start_request!
|
134
|
+
raise '[Dalli] Request already in progress. This may be a bug in Dalli.' if @request_in_progress
|
135
|
+
|
125
136
|
@request_in_progress = true
|
126
137
|
end
|
127
138
|
|
128
139
|
def finish_request!
|
140
|
+
raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless @request_in_progress
|
141
|
+
|
129
142
|
@request_in_progress = false
|
130
143
|
end
|
131
144
|
|
@@ -133,27 +146,27 @@ module Dalli
|
|
133
146
|
@request_in_progress = false
|
134
147
|
end
|
135
148
|
|
136
|
-
def
|
137
|
-
|
138
|
-
|
139
|
-
finish_request!
|
149
|
+
def read_line
|
150
|
+
data = @sock.gets("\r\n")
|
151
|
+
error_on_request!('EOF in read_line') if data.nil?
|
140
152
|
data
|
141
|
-
rescue SystemCallError,
|
153
|
+
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
|
154
|
+
error_on_request!(e)
|
155
|
+
end
|
156
|
+
|
157
|
+
def read(count)
|
158
|
+
@sock.readfull(count)
|
159
|
+
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
|
142
160
|
error_on_request!(e)
|
143
161
|
end
|
144
162
|
|
145
163
|
def write(bytes)
|
146
|
-
|
147
|
-
|
148
|
-
finish_request!
|
149
|
-
result
|
150
|
-
rescue SystemCallError, Timeout::Error => e
|
164
|
+
@sock.write(bytes)
|
165
|
+
rescue SystemCallError, *TIMEOUT_ERRORS => e
|
151
166
|
error_on_request!(e)
|
152
167
|
end
|
153
168
|
|
154
|
-
# Non-blocking read.
|
155
|
-
# of a caller who has called start_request!, but not yet
|
156
|
-
# called finish_request!. Here to support the operation
|
169
|
+
# Non-blocking read. Here to support the operation
|
157
170
|
# of the get_multi operation
|
158
171
|
def read_nonblock
|
159
172
|
@sock.read_available
|
@@ -216,7 +229,7 @@ module Dalli
|
|
216
229
|
end
|
217
230
|
|
218
231
|
def fork_detected?
|
219
|
-
@pid && @pid !=
|
232
|
+
@pid && @pid != PIDCache.pid
|
220
233
|
end
|
221
234
|
|
222
235
|
def log_down_detected
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Dalli
|
6
|
+
module Protocol
|
7
|
+
class Meta
|
8
|
+
##
|
9
|
+
# The meta protocol requires that keys be ASCII only, so Unicode keys are
|
10
|
+
# not supported. In addition, the use of whitespace in the key is not
|
11
|
+
# allowed.
|
12
|
+
# memcached supports the use of base64 hashes for keys containing
|
13
|
+
# whitespace or non-ASCII characters, provided the 'b' flag is included in the request.
|
14
|
+
class KeyRegularizer
|
15
|
+
WHITESPACE = /\s/.freeze
|
16
|
+
|
17
|
+
def self.encode(key)
|
18
|
+
return [key, false] if key.ascii_only? && !WHITESPACE.match(key)
|
19
|
+
|
20
|
+
[Base64.strict_encode64(key), true]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.decode(encoded_key, base64_encoded)
|
24
|
+
return encoded_key unless base64_encoded
|
25
|
+
|
26
|
+
Base64.strict_decode64(encoded_key).force_encoding(Encoding::UTF_8)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
class Meta
|
6
|
+
##
|
7
|
+
# Class that encapsulates logic for formatting meta protocol requests
|
8
|
+
# to memcached.
|
9
|
+
##
|
10
|
+
class RequestFormatter
|
11
|
+
# Since these are string construction methods, we're going to disable these
|
12
|
+
# Rubocop directives. We really can't make this construction much simpler,
|
13
|
+
# and introducing an intermediate object seems like overkill.
|
14
|
+
#
|
15
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
17
|
+
# rubocop:disable Metrics/ParameterLists
|
18
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
19
|
+
def self.meta_get(key:, value: true, return_cas: false, ttl: nil, base64: false, quiet: false)
|
20
|
+
cmd = "mg #{key}"
|
21
|
+
cmd << ' v f' if value
|
22
|
+
cmd << ' c' if return_cas
|
23
|
+
cmd << ' b' if base64
|
24
|
+
cmd << " T#{ttl}" if ttl
|
25
|
+
cmd << ' k q s' if quiet # Return the key in the response if quiet
|
26
|
+
cmd + TERMINATOR
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, base64: false, quiet: false)
|
30
|
+
cmd = "ms #{key} #{value.bytesize}"
|
31
|
+
cmd << ' c' unless %i[append prepend].include?(mode)
|
32
|
+
cmd << ' b' if base64
|
33
|
+
cmd << " F#{bitflags}" if bitflags
|
34
|
+
cmd << cas_string(cas)
|
35
|
+
cmd << " T#{ttl}" if ttl
|
36
|
+
cmd << " M#{mode_to_token(mode)}"
|
37
|
+
cmd << ' q' if quiet
|
38
|
+
cmd << TERMINATOR
|
39
|
+
cmd << value
|
40
|
+
cmd + TERMINATOR
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.meta_delete(key:, cas: nil, ttl: nil, base64: false, quiet: false)
|
44
|
+
cmd = "md #{key}"
|
45
|
+
cmd << ' b' if base64
|
46
|
+
cmd << cas_string(cas)
|
47
|
+
cmd << " T#{ttl}" if ttl
|
48
|
+
cmd << ' q' if quiet
|
49
|
+
cmd + TERMINATOR
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, base64: false, quiet: false)
|
53
|
+
cmd = "ma #{key} v"
|
54
|
+
cmd << ' b' if base64
|
55
|
+
cmd << " D#{delta}" if delta
|
56
|
+
cmd << " J#{initial}" if initial
|
57
|
+
# Always set a TTL if an initial value is specified
|
58
|
+
cmd << " N#{ttl || 0}" if ttl || initial
|
59
|
+
cmd << cas_string(cas)
|
60
|
+
cmd << ' q' if quiet
|
61
|
+
cmd << " M#{incr ? 'I' : 'D'}"
|
62
|
+
cmd + TERMINATOR
|
63
|
+
end
|
64
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
65
|
+
# rubocop:enable Metrics/MethodLength
|
66
|
+
# rubocop:enable Metrics/ParameterLists
|
67
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
68
|
+
|
69
|
+
def self.meta_noop
|
70
|
+
"mn#{TERMINATOR}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.version
|
74
|
+
"version#{TERMINATOR}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.flush(delay: nil, quiet: false)
|
78
|
+
cmd = +'flush_all'
|
79
|
+
cmd << " #{parse_to_64_bit_int(delay, 0)}" if delay
|
80
|
+
cmd << ' noreply' if quiet
|
81
|
+
cmd + TERMINATOR
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.stats(arg = nil)
|
85
|
+
cmd = +'stats'
|
86
|
+
cmd << " #{arg}" if arg
|
87
|
+
cmd + TERMINATOR
|
88
|
+
end
|
89
|
+
|
90
|
+
# rubocop:disable Metrics/MethodLength
|
91
|
+
def self.mode_to_token(mode)
|
92
|
+
case mode
|
93
|
+
when :add
|
94
|
+
'E'
|
95
|
+
when :replace
|
96
|
+
'R'
|
97
|
+
when :append
|
98
|
+
'A'
|
99
|
+
when :prepend
|
100
|
+
'P'
|
101
|
+
else
|
102
|
+
'S'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
# rubocop:enable Metrics/MethodLength
|
106
|
+
|
107
|
+
def self.cas_string(cas)
|
108
|
+
cas = parse_to_64_bit_int(cas, nil)
|
109
|
+
cas.nil? || cas.zero? ? '' : " C#{cas}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.parse_to_64_bit_int(val, default)
|
113
|
+
val.nil? ? nil : Integer(val)
|
114
|
+
rescue ArgumentError
|
115
|
+
# Sanitize to default if it isn't parsable as an integer
|
116
|
+
default
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|