dalli 3.0.1 → 3.0.5
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 +4 -4
- data/Gemfile +11 -5
- data/History.md +42 -12
- data/README.md +25 -139
- data/lib/dalli/cas/client.rb +2 -0
- data/lib/dalli/client.rb +188 -188
- data/lib/dalli/compressor.rb +13 -4
- data/lib/dalli/key_manager.rb +113 -0
- data/lib/dalli/options.rb +2 -2
- data/lib/dalli/protocol/binary/request_formatter.rb +109 -0
- data/lib/dalli/protocol/binary/response_processor.rb +149 -0
- data/lib/dalli/protocol/binary/sasl_authentication.rb +57 -0
- data/lib/dalli/protocol/binary.rb +274 -483
- data/lib/dalli/protocol/server_config_parser.rb +84 -0
- data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
- data/lib/dalli/protocol/value_compressor.rb +85 -0
- data/lib/dalli/protocol/value_marshaller.rb +59 -0
- data/lib/dalli/protocol/value_serializer.rb +91 -0
- data/lib/dalli/protocol.rb +2 -3
- data/lib/dalli/ring.rb +91 -35
- data/lib/dalli/server.rb +2 -2
- data/lib/dalli/servers_arg_normalizer.rb +54 -0
- data/lib/dalli/socket.rb +98 -45
- data/lib/dalli/version.rb +3 -1
- data/lib/dalli.rb +31 -11
- data/lib/rack/session/dalli.rb +28 -18
- metadata +60 -7
data/lib/dalli/socket.rb
CHANGED
@@ -1,58 +1,88 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'openssl'
|
4
|
+
require 'rbconfig'
|
4
5
|
|
5
6
|
module Dalli
|
7
|
+
##
|
8
|
+
# Various socket implementations used by Dalli.
|
9
|
+
##
|
6
10
|
module Socket
|
11
|
+
##
|
12
|
+
# Common methods for all socket implementations.
|
13
|
+
##
|
7
14
|
module InstanceMethods
|
8
|
-
|
9
15
|
def readfull(count)
|
10
|
-
value = +
|
16
|
+
value = +''
|
11
17
|
loop do
|
12
18
|
result = read_nonblock(count - value.bytesize, exception: false)
|
13
|
-
|
14
|
-
raise Timeout::Error, "IO timeout: #{safe_options.inspect}" unless IO.select([self], nil, nil, options[:socket_timeout])
|
15
|
-
elsif result == :wait_writable
|
16
|
-
raise Timeout::Error, "IO timeout: #{safe_options.inspect}" unless IO.select(nil, [self], nil, options[:socket_timeout])
|
17
|
-
elsif result
|
18
|
-
value << result
|
19
|
-
else
|
20
|
-
raise Errno::ECONNRESET, "Connection reset: #{safe_options.inspect}"
|
21
|
-
end
|
19
|
+
value << result if append_to_buffer?(result)
|
22
20
|
break if value.bytesize == count
|
23
21
|
end
|
24
22
|
value
|
25
23
|
end
|
26
24
|
|
27
25
|
def read_available
|
28
|
-
value = +
|
26
|
+
value = +''
|
29
27
|
loop do
|
30
28
|
result = read_nonblock(8196, exception: false)
|
31
|
-
if result
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
elsif result
|
36
|
-
value << result
|
37
|
-
else
|
38
|
-
raise Errno::ECONNRESET, "Connection reset: #{safe_options.inspect}"
|
39
|
-
end
|
29
|
+
break if WAIT_RCS.include?(result)
|
30
|
+
raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result
|
31
|
+
|
32
|
+
value << result
|
40
33
|
end
|
41
34
|
value
|
42
35
|
end
|
43
36
|
|
44
|
-
|
45
|
-
|
37
|
+
WAIT_RCS = %i[wait_writable wait_readable].freeze
|
38
|
+
|
39
|
+
def append_to_buffer?(result)
|
40
|
+
raise Timeout::Error, "IO timeout: #{logged_options.inspect}" if nonblock_timed_out?(result)
|
41
|
+
raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result
|
42
|
+
|
43
|
+
!WAIT_RCS.include?(result)
|
44
|
+
end
|
45
|
+
|
46
|
+
def nonblock_timed_out?(result)
|
47
|
+
return true if result == :wait_readable && !wait_readable(options[:socket_timeout])
|
48
|
+
|
49
|
+
# TODO: Do we actually need this? Looks to be only used in read_nonblock
|
50
|
+
result == :wait_writable && !wait_writable(options[:socket_timeout])
|
51
|
+
end
|
52
|
+
|
53
|
+
FILTERED_OUT_OPTIONS = %i[username password].freeze
|
54
|
+
def logged_options
|
55
|
+
options.reject { |k, _| FILTERED_OUT_OPTIONS.include? k }
|
46
56
|
end
|
47
57
|
end
|
48
58
|
|
59
|
+
##
|
60
|
+
# Wraps the below TCP socket class in the case where the client
|
61
|
+
# has configured a TLS/SSL connection between Dalli and the
|
62
|
+
# Memcached server.
|
63
|
+
##
|
49
64
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
50
65
|
include Dalli::Socket::InstanceMethods
|
51
66
|
def options
|
52
67
|
io.options
|
53
68
|
end
|
69
|
+
|
70
|
+
unless method_defined?(:wait_readable)
|
71
|
+
def wait_readable(timeout = nil)
|
72
|
+
to_io.wait_readable(timeout)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
unless method_defined?(:wait_writable)
|
77
|
+
def wait_writable(timeout = nil)
|
78
|
+
to_io.wait_writable(timeout)
|
79
|
+
end
|
80
|
+
end
|
54
81
|
end
|
55
82
|
|
83
|
+
##
|
84
|
+
# A standard TCP socket between the Dalli client and the Memcached server.
|
85
|
+
##
|
56
86
|
class TCP < TCPSocket
|
57
87
|
include Dalli::Socket::InstanceMethods
|
58
88
|
attr_accessor :options, :server
|
@@ -60,34 +90,57 @@ module Dalli
|
|
60
90
|
def self.open(host, port, server, options = {})
|
61
91
|
Timeout.timeout(options[:socket_timeout]) do
|
62
92
|
sock = new(host, port)
|
63
|
-
sock.options = {host: host, port: port}.merge(options)
|
93
|
+
sock.options = { host: host, port: port }.merge(options)
|
64
94
|
sock.server = server
|
65
|
-
sock
|
66
|
-
|
67
|
-
sock
|
68
|
-
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
|
69
|
-
|
70
|
-
return sock unless options[:ssl_context]
|
71
|
-
|
72
|
-
ssl_socket = Dalli::Socket::SSLSocket.new(sock, options[:ssl_context])
|
73
|
-
ssl_socket.hostname = host
|
74
|
-
ssl_socket.sync_close = true
|
75
|
-
ssl_socket.connect
|
76
|
-
ssl_socket
|
95
|
+
init_socket_options(sock, options)
|
96
|
+
|
97
|
+
options[:ssl_context] ? wrapping_ssl_socket(sock, host, options[:ssl_context]) : sock
|
77
98
|
end
|
78
99
|
end
|
100
|
+
|
101
|
+
def self.init_socket_options(sock, options)
|
102
|
+
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
|
103
|
+
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive]
|
104
|
+
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
|
105
|
+
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
|
109
|
+
ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
|
110
|
+
ssl_socket.hostname = host
|
111
|
+
ssl_socket.sync_close = true
|
112
|
+
ssl_socket.connect
|
113
|
+
ssl_socket
|
114
|
+
end
|
79
115
|
end
|
80
116
|
|
81
|
-
|
82
|
-
|
83
|
-
|
117
|
+
if /mingw|mswin/.match?(RbConfig::CONFIG['host_os'])
|
118
|
+
##
|
119
|
+
# UNIX domain sockets are not supported on Windows platforms.
|
120
|
+
##
|
121
|
+
class UNIX
|
122
|
+
def initialize(*_args)
|
123
|
+
raise Dalli::DalliError, 'Unix sockets are not supported on Windows platform.'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
else
|
84
127
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
128
|
+
##
|
129
|
+
# UNIX represents a UNIX domain socket, which is an interprocess communication
|
130
|
+
# mechanism between processes on the same host. Used when the Memcached server
|
131
|
+
# is running on the same machine as the Dalli client.
|
132
|
+
##
|
133
|
+
class UNIX < UNIXSocket
|
134
|
+
include Dalli::Socket::InstanceMethods
|
135
|
+
attr_accessor :options, :server
|
136
|
+
|
137
|
+
def self.open(path, server, options = {})
|
138
|
+
Timeout.timeout(options[:socket_timeout]) do
|
139
|
+
sock = new(path)
|
140
|
+
sock.options = { path: path }.merge(options)
|
141
|
+
sock.server = server
|
142
|
+
sock
|
143
|
+
end
|
91
144
|
end
|
92
145
|
end
|
93
146
|
end
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
@@ -1,30 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require "dalli/protocol"
|
7
|
-
require "dalli/protocol/binary"
|
8
|
-
require "dalli/socket"
|
9
|
-
require "dalli/version"
|
10
|
-
require "dalli/options"
|
11
|
-
|
3
|
+
##
|
4
|
+
# Namespace for all Dalli code.
|
5
|
+
##
|
12
6
|
module Dalli
|
13
|
-
autoload :Server,
|
7
|
+
autoload :Server, 'dalli/server'
|
14
8
|
|
15
9
|
# generic error
|
16
10
|
class DalliError < RuntimeError; end
|
11
|
+
|
17
12
|
# socket/server communication error
|
18
13
|
class NetworkError < DalliError; end
|
14
|
+
|
19
15
|
# no server available/alive error
|
20
16
|
class RingError < DalliError; end
|
17
|
+
|
21
18
|
# application error in marshalling serialization
|
22
19
|
class MarshalError < DalliError; end
|
20
|
+
|
23
21
|
# application error in marshalling deserialization or decompression
|
24
22
|
class UnmarshalError < DalliError; end
|
23
|
+
|
25
24
|
# payload too big for memcached
|
26
25
|
class ValueOverMaxSize < DalliError; end
|
27
26
|
|
27
|
+
# Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
|
28
|
+
class NilObject; end # rubocop:disable Lint/EmptyClass
|
29
|
+
NOT_FOUND = NilObject.new
|
30
|
+
|
28
31
|
def self.logger
|
29
32
|
@logger ||= (rails_logger || default_logger)
|
30
33
|
end
|
@@ -35,7 +38,7 @@ module Dalli
|
|
35
38
|
end
|
36
39
|
|
37
40
|
def self.default_logger
|
38
|
-
require
|
41
|
+
require 'logger'
|
39
42
|
l = Logger.new($stdout)
|
40
43
|
l.level = Logger::INFO
|
41
44
|
l
|
@@ -45,3 +48,20 @@ module Dalli
|
|
45
48
|
@logger = logger
|
46
49
|
end
|
47
50
|
end
|
51
|
+
|
52
|
+
require 'dalli/version'
|
53
|
+
|
54
|
+
require 'dalli/compressor'
|
55
|
+
require 'dalli/client'
|
56
|
+
require 'dalli/key_manager'
|
57
|
+
require 'dalli/ring'
|
58
|
+
require 'dalli/protocol'
|
59
|
+
require 'dalli/protocol/binary'
|
60
|
+
require 'dalli/protocol/server_config_parser'
|
61
|
+
require 'dalli/protocol/ttl_sanitizer'
|
62
|
+
require 'dalli/protocol/value_compressor'
|
63
|
+
require 'dalli/protocol/value_marshaller'
|
64
|
+
require 'dalli/protocol/value_serializer'
|
65
|
+
require 'dalli/servers_arg_normalizer'
|
66
|
+
require 'dalli/socket'
|
67
|
+
require 'dalli/options'
|
data/lib/rack/session/dalli.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'rack/session/abstract/id'
|
3
4
|
require 'dalli'
|
4
5
|
require 'connection_pool'
|
6
|
+
require 'English'
|
5
7
|
|
6
8
|
module Rack
|
7
9
|
module Session
|
10
|
+
# Rack::Session::Dalli provides memcached based session management.
|
8
11
|
class Dalli < Abstract::Persisted
|
9
12
|
attr_reader :pool
|
10
13
|
|
14
|
+
# Don't freeze this until we fix the specs/implementation
|
15
|
+
# rubocop:disable Style/MutableConstant
|
11
16
|
DEFAULT_DALLI_OPTIONS = {
|
12
|
-
:
|
13
|
-
:
|
17
|
+
namespace: 'rack:session',
|
18
|
+
memcache_server: 'localhost:11211'
|
14
19
|
}
|
20
|
+
# rubocop:enable Style/MutableConstant
|
15
21
|
|
16
22
|
# Brings in a new Rack::Session::Dalli middleware with the given
|
17
23
|
# `:memcache_server`. The server is either a hostname, or a
|
@@ -67,7 +73,7 @@ module Rack
|
|
67
73
|
# for more information about it and its default options (which would only
|
68
74
|
# be applicable if you supplied one of the two options, but not both).
|
69
75
|
#
|
70
|
-
def initialize(app, options={})
|
76
|
+
def initialize(app, options = {})
|
71
77
|
# Parent uses DEFAULT_OPTIONS to build @default_options for Rack::Session
|
72
78
|
super
|
73
79
|
|
@@ -80,10 +86,12 @@ module Rack
|
|
80
86
|
@pool = ConnectionPool.new(popts || {}) { ::Dalli::Client.new(mserv, mopts) }
|
81
87
|
end
|
82
88
|
|
83
|
-
def get_session(
|
89
|
+
def get_session(_env, sid)
|
84
90
|
with_block([nil, {}]) do |dc|
|
85
|
-
unless sid
|
86
|
-
old_sid
|
91
|
+
unless sid && !sid.empty? && (session = dc.get(sid))
|
92
|
+
old_sid = sid
|
93
|
+
sid = generate_sid_with(dc)
|
94
|
+
session = {}
|
87
95
|
unless dc.add(sid, session, @default_ttl)
|
88
96
|
sid = old_sid
|
89
97
|
redo # generate a new sid and try again
|
@@ -93,7 +101,7 @@ module Rack
|
|
93
101
|
end
|
94
102
|
end
|
95
103
|
|
96
|
-
def set_session(
|
104
|
+
def set_session(_env, session_id, new_session, options)
|
97
105
|
return false unless session_id
|
98
106
|
|
99
107
|
with_block(false) do |dc|
|
@@ -102,7 +110,7 @@ module Rack
|
|
102
110
|
end
|
103
111
|
end
|
104
112
|
|
105
|
-
def destroy_session(
|
113
|
+
def destroy_session(_env, session_id, options)
|
106
114
|
with_block do |dc|
|
107
115
|
dc.delete(session_id)
|
108
116
|
generate_sid_with(dc) unless options[:drop]
|
@@ -124,14 +132,15 @@ module Rack
|
|
124
132
|
private
|
125
133
|
|
126
134
|
def extract_dalli_options(options)
|
127
|
-
raise
|
135
|
+
raise 'Rack::Session::Dalli no longer supports the :cache option.' if options[:cache]
|
128
136
|
|
129
|
-
popts = {}
|
130
137
|
# Filter out Rack::Session-specific options and apply our defaults
|
131
|
-
|
132
|
-
|
138
|
+
# Filter out Rack::Session-specific options and apply our defaults
|
139
|
+
filtered_opts = options.reject { |k, _| DEFAULT_OPTIONS.key? k }
|
140
|
+
mopts = DEFAULT_DALLI_OPTIONS.merge(filtered_opts)
|
133
141
|
mserv = mopts.delete :memcache_server
|
134
142
|
|
143
|
+
popts = {}
|
135
144
|
if mopts[:pool_size] || mopts[:pool_timeout]
|
136
145
|
popts[:size] = mopts.delete :pool_size if mopts[:pool_size]
|
137
146
|
popts[:timeout] = mopts.delete :pool_timeout if mopts[:pool_timeout]
|
@@ -141,20 +150,21 @@ module Rack
|
|
141
150
|
[mserv, mopts, popts]
|
142
151
|
end
|
143
152
|
|
144
|
-
def generate_sid_with(
|
145
|
-
|
153
|
+
def generate_sid_with(client)
|
154
|
+
loop do
|
146
155
|
sid = generate_sid
|
147
|
-
break sid unless
|
156
|
+
break sid unless client.get(sid)
|
148
157
|
end
|
149
158
|
end
|
150
159
|
|
151
|
-
def with_block(default=nil, &block)
|
160
|
+
def with_block(default = nil, &block)
|
152
161
|
@pool.with(&block)
|
153
162
|
rescue ::Dalli::DalliError, Errno::ECONNREFUSED
|
154
|
-
raise if
|
163
|
+
raise if /undefined class/.match?($ERROR_INFO.message)
|
164
|
+
|
155
165
|
if $VERBOSE
|
156
166
|
warn "#{self} is unable to find memcached server."
|
157
|
-
warn
|
167
|
+
warn $ERROR_INFO.inspect
|
158
168
|
end
|
159
169
|
default
|
160
170
|
end
|
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.0.
|
4
|
+
version: 3.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter M. Goldstein
|
@@ -9,8 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-11-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: connection_pool
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
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'
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: rack
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -26,7 +40,21 @@ dependencies:
|
|
26
40
|
- !ruby/object:Gem::Version
|
27
41
|
version: '0'
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
43
|
+
name: rubocop
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rubocop-minitest
|
30
58
|
requirement: !ruby/object:Gem::Requirement
|
31
59
|
requirements:
|
32
60
|
- - ">="
|
@@ -40,7 +68,21 @@ dependencies:
|
|
40
68
|
- !ruby/object:Gem::Version
|
41
69
|
version: '0'
|
42
70
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
71
|
+
name: rubocop-performance
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rubocop-rake
|
44
86
|
requirement: !ruby/object:Gem::Requirement
|
45
87
|
requirements:
|
46
88
|
- - ">="
|
@@ -69,18 +111,29 @@ files:
|
|
69
111
|
- lib/dalli/cas/client.rb
|
70
112
|
- lib/dalli/client.rb
|
71
113
|
- lib/dalli/compressor.rb
|
114
|
+
- lib/dalli/key_manager.rb
|
72
115
|
- lib/dalli/options.rb
|
73
116
|
- lib/dalli/protocol.rb
|
74
117
|
- lib/dalli/protocol/binary.rb
|
118
|
+
- lib/dalli/protocol/binary/request_formatter.rb
|
119
|
+
- lib/dalli/protocol/binary/response_processor.rb
|
120
|
+
- lib/dalli/protocol/binary/sasl_authentication.rb
|
121
|
+
- lib/dalli/protocol/server_config_parser.rb
|
122
|
+
- lib/dalli/protocol/ttl_sanitizer.rb
|
123
|
+
- lib/dalli/protocol/value_compressor.rb
|
124
|
+
- lib/dalli/protocol/value_marshaller.rb
|
125
|
+
- lib/dalli/protocol/value_serializer.rb
|
75
126
|
- lib/dalli/ring.rb
|
76
127
|
- lib/dalli/server.rb
|
128
|
+
- lib/dalli/servers_arg_normalizer.rb
|
77
129
|
- lib/dalli/socket.rb
|
78
130
|
- lib/dalli/version.rb
|
79
131
|
- lib/rack/session/dalli.rb
|
80
132
|
homepage: https://github.com/petergoldstein/dalli
|
81
133
|
licenses:
|
82
134
|
- MIT
|
83
|
-
metadata:
|
135
|
+
metadata:
|
136
|
+
rubygems_mfa_required: 'true'
|
84
137
|
post_install_message:
|
85
138
|
rdoc_options: []
|
86
139
|
require_paths:
|
@@ -89,14 +142,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
142
|
requirements:
|
90
143
|
- - ">="
|
91
144
|
- !ruby/object:Gem::Version
|
92
|
-
version: '
|
145
|
+
version: '2.5'
|
93
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
147
|
requirements:
|
95
148
|
- - ">="
|
96
149
|
- !ruby/object:Gem::Version
|
97
150
|
version: '0'
|
98
151
|
requirements: []
|
99
|
-
rubygems_version: 3.2.
|
152
|
+
rubygems_version: 3.2.31
|
100
153
|
signing_key:
|
101
154
|
specification_version: 4
|
102
155
|
summary: High performance memcached client for Ruby
|