dalli 3.1.5 → 3.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a9612ab509bc2a6b7bd2560a7a10564183b3b9fbb2aa0655f339b30067407fa
4
- data.tar.gz: 510c3ed03a4e2b4ae4e7f8540eac19e7d28508341b768edc938f9886d5504fbe
3
+ metadata.gz: 56babb2362639cca3fd24225eea2a76a0d9a075a3b113b7f8ea09814a79bf59a
4
+ data.tar.gz: 309f6be3fce52b38608c4b22e5dce439e68944796fbe1f2240b2ed40760e8700
5
5
  SHA512:
6
- metadata.gz: 81e31363e781e948f66c3a143cd506162e4b51b4c67bb76a8cbdbfb2efdcdbd279216b24cdc077c3c2e4ad88b356ffec1a74f4428c556b05d17591b171b874d2
7
- data.tar.gz: ef45c25f8552e203751a51cb32172b5670b6b509ac45cbcc41b19a4f70688f0f50eb01b09af32c3faa05d643dd93acce2e748d8cedbba5dded585070b6ed7ec6
6
+ metadata.gz: 16847c7b39ff624deeb5fecd324d3f0bddafdcd6096c5f65cfa7a79e8b2369930b7078e8fc019bd49015c3415b43f33ea07cf85380b28d3c46199339bc9aeb2a
7
+ data.tar.gz: 890f87a779947ec269fb9889334eaa7b4424a654995b9b12b723dee7452f82de066b538465800c20bf9ed9bb2c5a81fbca3ae5ff382340f241f0525ad158df0c
data/History.md CHANGED
@@ -4,10 +4,23 @@ Dalli Changelog
4
4
  Unreleased
5
5
  ==========
6
6
 
7
+ 3.2.0
8
+ ==========
9
+
10
+ - BREAKING CHANGE: Remove protocol_implementation client option (petergoldstein)
11
+ - Add protocol option with meta implementation (petergoldstein)
12
+
13
+ 3.1.6
14
+ ==========
15
+
16
+ - Fix bug with cas/cas! with "Not found" value (petergoldstein)
17
+ - Add Ruby 3.1 to CI (petergoldstein)
18
+ - Replace reject(&:nil?) with compact (petergoldstein)
19
+
7
20
  3.1.5
8
21
  ==========
9
22
 
10
- - Fix bug with get_cas key with "Not found" value [petergoldstein]
23
+ - Fix bug with get_cas key with "Not found" value (petergoldstein)
11
24
  - Replace should return nil, not raise error, on miss (petergoldstein)
12
25
 
13
26
  3.1.4
data/lib/dalli/client.rb CHANGED
@@ -43,8 +43,8 @@ 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
- # - :protocol_implementation - defaults to Dalli::Protocol::Binary which uses the binary protocol. Allows you to
47
- # pass an alternative implementation using another protocol.
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
  @servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
@@ -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)
@@ -403,7 +402,12 @@ module Dalli
403
402
  end
404
403
 
405
404
  def protocol_implementation
406
- @protocol_implementation ||= @options.fetch(:protocol_implementation, Dalli::Protocol::Binary)
405
+ @protocol_implementation ||= case @options[:protocol]&.to_s
406
+ when 'meta'
407
+ Dalli::Protocol::Meta
408
+ else
409
+ Dalli::Protocol::Binary
410
+ end
407
411
  end
408
412
 
409
413
  ##
@@ -147,6 +147,13 @@ module Dalli
147
147
 
148
148
  private
149
149
 
150
+ ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
151
+ def verify_allowed_quiet!(opkey)
152
+ return if ALLOWED_QUIET_OPS.include?(opkey)
153
+
154
+ raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
155
+ end
156
+
150
157
  ##
151
158
  # Checks to see if we can execute the specified operation. Checks
152
159
  # whether the connection is in use, and whether the command is allowed
@@ -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].reject(&:nil?)
97
+ body = [bitflags, ttl, key, value].compact
98
98
  (header + body).pack(FORMAT[opkey])
99
99
  end
100
100
  # rubocop:enable Metrics/ParameterLists
@@ -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)
@@ -133,6 +133,16 @@ module Dalli
133
133
  @request_in_progress = false
134
134
  end
135
135
 
136
+ def read_line
137
+ start_request!
138
+ data = @sock.gets("\r\n")
139
+ error_on_request!('EOF in read_line') if data.nil?
140
+ finish_request!
141
+ data
142
+ rescue SystemCallError, Timeout::Error, EOFError => e
143
+ error_on_request!(e)
144
+ end
145
+
136
146
  def read(count)
137
147
  start_request!
138
148
  data = @sock.readfull(count)
@@ -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('UTF-8')
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,108 @@
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 << " C#{cas}" if cas && !cas.zero?
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 << " C#{cas}" if cas && !cas.zero?
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
+ cmd << " C#{cas}" if cas && !cas.zero?
58
+ cmd << " N#{ttl}" if ttl
59
+ cmd << ' q' if quiet
60
+ cmd << " M#{incr ? 'I' : 'D'}"
61
+ cmd + TERMINATOR
62
+ end
63
+ # rubocop:enable Metrics/CyclomaticComplexity
64
+ # rubocop:enable Metrics/MethodLength
65
+ # rubocop:enable Metrics/ParameterLists
66
+ # rubocop:enable Metrics/PerceivedComplexity
67
+
68
+ def self.meta_noop
69
+ "mn#{TERMINATOR}"
70
+ end
71
+
72
+ def self.version
73
+ "version#{TERMINATOR}"
74
+ end
75
+
76
+ def self.flush(delay: nil, quiet: false)
77
+ cmd = +'flush_all'
78
+ cmd << " #{delay}" if delay
79
+ cmd << ' noreply' if quiet
80
+ cmd + TERMINATOR
81
+ end
82
+
83
+ def self.stats(arg = nil)
84
+ cmd = +'stats'
85
+ cmd << " #{arg}" if arg
86
+ cmd + TERMINATOR
87
+ end
88
+
89
+ # rubocop:disable Metrics/MethodLength
90
+ def self.mode_to_token(mode)
91
+ case mode
92
+ when :add
93
+ 'E'
94
+ when :replace
95
+ 'R'
96
+ when :append
97
+ 'A'
98
+ when :prepend
99
+ 'P'
100
+ else
101
+ 'S'
102
+ end
103
+ end
104
+ # rubocop:enable Metrics/MethodLength
105
+ end
106
+ end
107
+ end
108
+ end
@@ -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..-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,177 @@
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
+ encoded_key, base64 = KeyRegularizer.encode(key)
48
+ req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, value: false, base64: base64)
49
+ write(req)
50
+ response_processor.meta_get_without_value
51
+ end
52
+
53
+ # TODO: This is confusing, as there's a cas command in memcached
54
+ # and this isn't it. Maybe rename? Maybe eliminate?
55
+ def cas(key)
56
+ encoded_key, base64 = KeyRegularizer.encode(key)
57
+ req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64)
58
+ write(req)
59
+ response_processor.meta_get_with_value_and_cas
60
+ end
61
+
62
+ # Storage Commands
63
+ def set(key, value, ttl, cas, options)
64
+ write_storage_req(:set, key, value, ttl, cas, options)
65
+ response_processor.meta_set_with_cas unless quiet?
66
+ end
67
+
68
+ def add(key, value, ttl, options)
69
+ write_storage_req(:add, key, value, ttl, nil, options)
70
+ response_processor.meta_set_with_cas unless quiet?
71
+ end
72
+
73
+ def replace(key, value, ttl, cas, options)
74
+ write_storage_req(:replace, key, value, ttl, cas, options)
75
+ response_processor.meta_set_with_cas unless quiet?
76
+ end
77
+
78
+ # rubocop:disable Metrics/ParameterLists
79
+ def write_storage_req(mode, key, raw_value, ttl = nil, cas = nil, options = {})
80
+ (value, bitflags) = @value_marshaller.store(key, raw_value, options)
81
+ ttl = TtlSanitizer.sanitize(ttl) if ttl
82
+ encoded_key, base64 = KeyRegularizer.encode(key)
83
+ req = RequestFormatter.meta_set(key: encoded_key, value: value,
84
+ bitflags: bitflags, cas: cas,
85
+ ttl: ttl, mode: mode, quiet: quiet?, base64: base64)
86
+ write(req)
87
+ end
88
+ # rubocop:enable Metrics/ParameterLists
89
+
90
+ def append(key, value)
91
+ write_append_prepend_req(:append, key, value)
92
+ response_processor.meta_set_append_prepend unless quiet?
93
+ end
94
+
95
+ def prepend(key, value)
96
+ write_append_prepend_req(:prepend, key, value)
97
+ response_processor.meta_set_append_prepend unless quiet?
98
+ end
99
+
100
+ # rubocop:disable Metrics/ParameterLists
101
+ def write_append_prepend_req(mode, key, value, ttl = nil, cas = nil, _options = {})
102
+ ttl = TtlSanitizer.sanitize(ttl) if ttl
103
+ encoded_key, base64 = KeyRegularizer.encode(key)
104
+ req = RequestFormatter.meta_set(key: encoded_key, value: value, base64: base64,
105
+ cas: cas, ttl: ttl, mode: mode, quiet: quiet?)
106
+ write(req)
107
+ end
108
+ # rubocop:enable Metrics/ParameterLists
109
+
110
+ # Delete Commands
111
+ def delete(key, cas)
112
+ encoded_key, base64 = KeyRegularizer.encode(key)
113
+ req = RequestFormatter.meta_delete(key: encoded_key, cas: cas,
114
+ base64: base64, quiet: quiet?)
115
+ write(req)
116
+ response_processor.meta_delete unless quiet?
117
+ end
118
+
119
+ # Arithmetic Commands
120
+ def decr(key, count, ttl, initial)
121
+ decr_incr false, key, count, ttl, initial
122
+ end
123
+
124
+ def incr(key, count, ttl, initial)
125
+ decr_incr true, key, count, ttl, initial
126
+ end
127
+
128
+ def decr_incr(incr, key, delta, ttl, initial)
129
+ ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
130
+ encoded_key, base64 = KeyRegularizer.encode(key)
131
+ write(RequestFormatter.meta_arithmetic(key: encoded_key, delta: delta, initial: initial, incr: incr, ttl: ttl,
132
+ quiet: quiet?, base64: base64))
133
+ response_processor.decr_incr unless quiet?
134
+ end
135
+
136
+ # Other Commands
137
+ def flush(delay = 0)
138
+ write(RequestFormatter.flush(delay: delay))
139
+ response_processor.flush unless quiet?
140
+ end
141
+
142
+ # Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
143
+ # We need to read all the responses at once.
144
+ def noop
145
+ write_noop
146
+ response_processor.consume_all_responses_until_mn
147
+ end
148
+
149
+ def stats(info = nil)
150
+ write(RequestFormatter.stats(info))
151
+ response_processor.stats
152
+ end
153
+
154
+ def reset_stats
155
+ write(RequestFormatter.stats('reset'))
156
+ response_processor.reset
157
+ end
158
+
159
+ def version
160
+ write(RequestFormatter.version)
161
+ response_processor.version
162
+ end
163
+
164
+ def write_noop
165
+ write(RequestFormatter.meta_noop)
166
+ end
167
+
168
+ def authenticate_connection
169
+ raise Dalli::DalliError, 'Authentication not supported for the meta protocol.'
170
+ end
171
+
172
+ require_relative 'meta/key_regularizer'
173
+ require_relative 'meta/request_formatter'
174
+ require_relative 'meta/response_processor'
175
+ end
176
+ end
177
+ end
@@ -12,6 +12,7 @@ module Dalli
12
12
  def initialize(io_source, response_processor)
13
13
  @io_source = io_source
14
14
  @response_processor = response_processor
15
+ @buffer = nil
15
16
  end
16
17
 
17
18
  def read
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '3.1.5'
4
+ VERSION = '3.2.0'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
7
7
  end
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'
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: 3.1.5
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-12-18 00:00:00.000000000 Z
12
+ date: 2022-01-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: connection_pool
@@ -128,6 +128,10 @@ files:
128
128
  - lib/dalli/protocol/binary/response_processor.rb
129
129
  - lib/dalli/protocol/binary/sasl_authentication.rb
130
130
  - lib/dalli/protocol/connection_manager.rb
131
+ - lib/dalli/protocol/meta.rb
132
+ - lib/dalli/protocol/meta/key_regularizer.rb
133
+ - lib/dalli/protocol/meta/request_formatter.rb
134
+ - lib/dalli/protocol/meta/response_processor.rb
131
135
  - lib/dalli/protocol/response_buffer.rb
132
136
  - lib/dalli/protocol/server_config_parser.rb
133
137
  - lib/dalli/protocol/ttl_sanitizer.rb
@@ -160,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
164
  - !ruby/object:Gem::Version
161
165
  version: '0'
162
166
  requirements: []
163
- rubygems_version: 3.2.33
167
+ rubygems_version: 3.3.4
164
168
  signing_key:
165
169
  specification_version: 4
166
170
  summary: High performance memcached client for Ruby