dalli 3.2.3 → 3.2.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0ef39f4ff4e9e465b2522707f3c4eea85a94373ba0d3de4b9d940830001b118
4
- data.tar.gz: 35cb362ec6075818f062106769481e92c90752936ecaf9a00fae25725ee4ea94
3
+ metadata.gz: a98f25c5f6f750d72b83fdd0b6e45a9d693520a91b9b7d985bd26fd0abfac41e
4
+ data.tar.gz: 9e95098e69ece841757034d69cc2091e0b450feed17290bf5fffd527e6d45ac7
5
5
  SHA512:
6
- metadata.gz: 0c6b2b205551fb10fb2e49ab7c3c3eba3c552c0bc4b8132783b51f6cdc7a9ddd4c272bd24ed13f9d78e29353e3b84daec001b2691ad630bdfd517bb7af86fd54
7
- data.tar.gz: 6ef9f6733d8e6d5f8a6bf8fb7d782e99c450494e03d4e5fe46037b77665682df63eaa9a3eb05007d1720b175564c8e91524c0df92d0856dc1f68066501732a06
6
+ metadata.gz: 25368bee67a5fd00aa7795cc06839c366fb7c1c33386ca0304c1cdfee3a4c82729716128d8f452cb7e0c0e2c24e2c3aec384eb7e3e0ee68c3915d93e17917456
7
+ data.tar.gz: f0094059b3e7430b3b03a0a6d8795ba0e76bdf1622ba2531b3ceffa11ebb3fb2442a4512a5c98e27e00fba253a7241fb58f47e3f93c58944676c8cd5d0dfe123
data/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ Dalli Changelog
4
4
  Unreleased
5
5
  ==========
6
6
 
7
+ 3.2.7
8
+ ==========
9
+
10
+ - Fix cascading error when there's an underlying network error in a pipelined get (eugeneius)
11
+ - Ruby 3.4/head compatibility by adding base64 to gemspec (tagliala)
12
+ - Add Ruby 3.3 to CI (m-nakamura145)
13
+ - Use Socket's connect_timeout when available, and pass timeout to the socket's send and receive timeouts (mlarraz)
14
+
15
+ 3.2.6
16
+ ==========
17
+
18
+ - Rescue IO::TimeoutError raised by Ruby since 3.2.0 on blocking reads/writes (skaes)
19
+ - Fix rubydoc link (JuanitoFatas)
20
+
21
+ 3.2.5
22
+ ==========
23
+
24
+ - Better handle memcached requests being interrupted by Thread#raise or Thread#kill (byroot)
25
+ - Unexpected errors are no longer treated as `Dalli::NetworkError`, including errors raised by `Timeout.timeout` (byroot)
26
+
27
+ 3.2.4
28
+ ==========
29
+
30
+ - Cache PID calls for performance since glibc no longer caches in recent versions (byroot)
31
+ - Preallocate the read buffer in Socket#readfull (byroot)
32
+
7
33
  3.2.3
8
34
  ==========
9
35
 
@@ -45,7 +71,7 @@ Unreleased
45
71
  3.1.4
46
72
  ==========
47
73
 
48
- - Improve response parsing performance (casperisfine)
74
+ - Improve response parsing performance (byroot)
49
75
  - Reorganize binary protocol parsing a bit (petergoldstein)
50
76
  - Fix handling of non-ASCII keys in get_multi (petergoldstein)
51
77
 
data/Gemfile CHANGED
@@ -4,6 +4,18 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
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'
12
+ gem 'rubocop'
13
+ gem 'rubocop-minitest'
14
+ gem 'rubocop-performance'
15
+ gem 'rubocop-rake'
16
+ gem 'simplecov'
17
+ end
18
+
7
19
  group :test do
8
20
  gem 'ruby-prof', platform: :mri
9
21
  end
data/README.md CHANGED
@@ -23,7 +23,13 @@ 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://rubydoc.info/github/petergoldstein/dalli/Dalli/Client) - Ruby documentation for the `Dalli::Client` API
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
 
@@ -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
@@ -60,7 +60,7 @@ module Dalli
60
60
  deleted = []
61
61
 
62
62
  servers.each do |server|
63
- next unless server.alive?
63
+ next unless server.connected?
64
64
 
65
65
  begin
66
66
  finish_query_for_server(server)
@@ -32,7 +32,13 @@ module Dalli
32
32
  verify_state(opkey)
33
33
 
34
34
  begin
35
- send(opkey, *args)
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
- down!
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
- verify_state(:getkq)
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, Timeout::Error, EOFError => e
109
+ rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
104
110
  @connection_manager.error_on_request!(e)
105
111
  end
106
112
 
@@ -169,6 +175,11 @@ module Dalli
169
175
  raise_down_error unless ensure_connected!
170
176
  end
171
177
 
178
+ def verify_pipelined_state(_opkey)
179
+ @connection_manager.confirm_in_progress!
180
+ raise_down_error unless connected?
181
+ end
182
+
172
183
  # The socket connection to the underlying server is initialized as a side
173
184
  # effect of this call. In fact, this is the ONLY place where that
174
185
  # socket connection is initialized.
@@ -197,8 +208,6 @@ module Dalli
197
208
  authenticate_connection if require_auth?
198
209
  @version = version # Connect socket if not authed
199
210
  up!
200
- rescue Dalli::DalliError
201
- raise
202
211
  end
203
212
 
204
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)
@@ -109,7 +109,7 @@ module Dalli
109
109
  end
110
110
 
111
111
  def self.as_8byte_uint(val)
112
- [val >> 32, 0xFFFFFFFF & val]
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('UTF-8') if key_len.positive?
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 = parse_as_stored_value ? @value_marshaller.retrieve(value, bitflags) : value
55
+ value = @value_marshaller.retrieve(value, bitflags) if parse_as_stored_value
56
56
  [key, value]
57
57
  end
58
58
 
@@ -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 = Process.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
- error_on_request!(RuntimeError.new('Already writing to socket')) if request_in_progress?
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
 
@@ -134,36 +147,26 @@ module Dalli
134
147
  end
135
148
 
136
149
  def read_line
137
- start_request!
138
150
  data = @sock.gets("\r\n")
139
151
  error_on_request!('EOF in read_line') if data.nil?
140
- finish_request!
141
152
  data
142
- rescue SystemCallError, Timeout::Error, EOFError => e
153
+ rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
143
154
  error_on_request!(e)
144
155
  end
145
156
 
146
157
  def read(count)
147
- start_request!
148
- data = @sock.readfull(count)
149
- finish_request!
150
- data
151
- rescue SystemCallError, Timeout::Error, EOFError => e
158
+ @sock.readfull(count)
159
+ rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
152
160
  error_on_request!(e)
153
161
  end
154
162
 
155
163
  def write(bytes)
156
- start_request!
157
- result = @sock.write(bytes)
158
- finish_request!
159
- result
160
- rescue SystemCallError, Timeout::Error => e
164
+ @sock.write(bytes)
165
+ rescue SystemCallError, *TIMEOUT_ERRORS => e
161
166
  error_on_request!(e)
162
167
  end
163
168
 
164
- # Non-blocking read. Should only be used in the context
165
- # of a caller who has called start_request!, but not yet
166
- # called finish_request!. Here to support the operation
169
+ # Non-blocking read. Here to support the operation
167
170
  # of the get_multi operation
168
171
  def read_nonblock
169
172
  @sock.read_available
@@ -226,7 +229,7 @@ module Dalli
226
229
  end
227
230
 
228
231
  def fork_detected?
229
- @pid && @pid != Process.pid
232
+ @pid && @pid != PIDCache.pid
230
233
  end
231
234
 
232
235
  def log_down_detected
@@ -23,7 +23,7 @@ module Dalli
23
23
  def self.decode(encoded_key, base64_encoded)
24
24
  return encoded_key unless base64_encoded
25
25
 
26
- Base64.strict_decode64(encoded_key).force_encoding('UTF-8')
26
+ Base64.strict_decode64(encoded_key).force_encoding(Encoding::UTF_8)
27
27
  end
28
28
  end
29
29
  end
@@ -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/socket.rb CHANGED
@@ -13,7 +13,7 @@ module Dalli
13
13
  ##
14
14
  module InstanceMethods
15
15
  def readfull(count)
16
- value = +''
16
+ value = String.new(capacity: count + 1)
17
17
  loop do
18
18
  result = read_nonblock(count - value.bytesize, exception: false)
19
19
  value << result if append_to_buffer?(result)
@@ -88,14 +88,24 @@ module Dalli
88
88
  # options - supports enhanced logging in the case of a timeout
89
89
  attr_accessor :options
90
90
 
91
- def self.open(host, port, options = {})
92
- Timeout.timeout(options[:socket_timeout]) do
93
- sock = new(host, port)
91
+ if RUBY_VERSION >= '3.0'
92
+ def self.open(host, port, options = {})
93
+ sock = new(host, port, connect_timeout: options[:socket_timeout])
94
94
  sock.options = { host: host, port: port }.merge(options)
95
95
  init_socket_options(sock, options)
96
96
 
97
97
  options[:ssl_context] ? wrapping_ssl_socket(sock, host, options[:ssl_context]) : sock
98
98
  end
99
+ else
100
+ def self.open(host, port, options = {})
101
+ Timeout.timeout(options[:socket_timeout]) do
102
+ sock = new(host, port)
103
+ sock.options = { host: host, port: port }.merge(options)
104
+ init_socket_options(sock, options)
105
+
106
+ options[:ssl_context] ? wrapping_ssl_socket(sock, host, options[:ssl_context]) : sock
107
+ end
108
+ end
99
109
  end
100
110
 
101
111
  def self.init_socket_options(sock, options)
@@ -103,6 +113,15 @@ module Dalli
103
113
  sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive]
104
114
  sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
105
115
  sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
116
+
117
+ return unless options[:socket_timeout]
118
+
119
+ seconds, fractional = options[:socket_timeout].divmod(1)
120
+ microseconds = fractional * 1_000_000
121
+ timeval = [seconds, microseconds].pack('l_2')
122
+
123
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO, timeval)
124
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDTIMEO, timeval)
106
125
  end
107
126
 
108
127
  def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
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.2.3'
4
+ VERSION = '3.2.7'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
7
7
  end
data/lib/dalli.rb CHANGED
@@ -34,7 +34,7 @@ module Dalli
34
34
  QUIET = :dalli_multi
35
35
 
36
36
  def self.logger
37
- @logger ||= (rails_logger || default_logger)
37
+ @logger ||= rails_logger || default_logger
38
38
  end
39
39
 
40
40
  def self.rails_logger
@@ -178,7 +178,7 @@ module Rack
178
178
  def with_dalli_client(result_on_error = nil, &block)
179
179
  @data.with(&block)
180
180
  rescue ::Dalli::DalliError, Errno::ECONNREFUSED
181
- raise if /undefined class/.match?($ERROR_INFO.message)
181
+ raise if $ERROR_INFO.message.include?('undefined class')
182
182
 
183
183
  if $VERBOSE
184
184
  warn "#{self} is unable to find memcached server."
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.2.3
4
+ version: 3.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
@@ -9,134 +9,16 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-10-28 00:00:00.000000000 Z
12
+ date: 2024-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: connection_pool
15
+ name: base64
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: '0'
21
- type: :development
22
- prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: minitest
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '5'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '5'
42
- - !ruby/object:Gem::Dependency
43
- name: rack
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '2.0'
49
- - - ">="
50
- - !ruby/object:Gem::Version
51
- version: 2.2.0
52
- type: :development
53
- prerelease: false
54
- version_requirements: !ruby/object:Gem::Requirement
55
- requirements:
56
- - - "~>"
57
- - !ruby/object:Gem::Version
58
- version: '2.0'
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 2.2.0
62
- - !ruby/object:Gem::Dependency
63
- name: rake
64
- requirement: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '13.0'
69
- type: :development
70
- prerelease: false
71
- version_requirements: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '13.0'
76
- - !ruby/object:Gem::Dependency
77
- name: rubocop
78
- requirement: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- type: :development
84
- prerelease: false
85
- version_requirements: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- - !ruby/object:Gem::Dependency
91
- name: rubocop-minitest
92
- requirement: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- type: :development
98
- prerelease: false
99
- version_requirements: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- - !ruby/object:Gem::Dependency
105
- name: rubocop-performance
106
- requirement: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- type: :development
112
- prerelease: false
113
- version_requirements: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- - !ruby/object:Gem::Dependency
119
- name: rubocop-rake
120
- requirement: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- type: :development
126
- prerelease: false
127
- version_requirements: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- - !ruby/object:Gem::Dependency
133
- name: simplecov
134
- requirement: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
- type: :development
21
+ type: :runtime
140
22
  prerelease: false
141
23
  version_requirements: !ruby/object:Gem::Requirement
142
24
  requirements:
@@ -161,6 +43,7 @@ files:
161
43
  - lib/dalli/compressor.rb
162
44
  - lib/dalli/key_manager.rb
163
45
  - lib/dalli/options.rb
46
+ - lib/dalli/pid_cache.rb
164
47
  - lib/dalli/pipelined_getter.rb
165
48
  - lib/dalli/protocol.rb
166
49
  - lib/dalli/protocol/base.rb
@@ -206,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
89
  - !ruby/object:Gem::Version
207
90
  version: '0'
208
91
  requirements: []
209
- rubygems_version: 3.3.24
92
+ rubygems_version: 3.5.5
210
93
  signing_key:
211
94
  specification_version: 4
212
95
  summary: High performance memcached client for Ruby