dalli 4.2.0 → 4.3.0
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/CHANGELOG.md +30 -0
- data/README.md +25 -0
- data/lib/dalli/key_manager.rb +22 -7
- data/lib/dalli/pipelined_getter.rb +30 -1
- data/lib/dalli/protocol/base.rb +55 -3
- data/lib/dalli/protocol/response_buffer.rb +9 -0
- data/lib/dalli/protocol/value_serializer.rb +26 -16
- data/lib/dalli/socket.rb +26 -2
- data/lib/dalli/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e97e2a407956737b411c33627d1f771be758017b881c68fab66edee95dc3249e
|
|
4
|
+
data.tar.gz: b57638f133a592e9d57b71530bc925ff6589b27549e8785bc6c7f334906636ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dd43e9e5b09b65174f2e46b84de40e4e5bcfae9645c6bb67017ca81a64d344e7345cc4bf685f11d4779aeaafd7c7163df5a67bf8410153034f4adf523385b95
|
|
7
|
+
data.tar.gz: b57506702dbdcb387490b3c0c56d2407aff4f0f789aec01e2b1a92c9f8d925a0225b8563e3dbc1ccf3c3ca72a25ca58f4199f1738d93bdf65a07893ed2930b6d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
Dalli Changelog
|
|
2
2
|
=====================
|
|
3
3
|
|
|
4
|
+
4.3.0
|
|
5
|
+
==========
|
|
6
|
+
|
|
7
|
+
New Features:
|
|
8
|
+
|
|
9
|
+
- Add `namespace_separator` option to customize the separator between namespace and key (#1019)
|
|
10
|
+
- Default is `:` for backward compatibility
|
|
11
|
+
- Must be a single non-alphanumeric character (e.g., `:`, `/`, `|`, `.`)
|
|
12
|
+
- Example: `Dalli::Client.new(servers, namespace: 'myapp', namespace_separator: '/')`
|
|
13
|
+
|
|
14
|
+
Bug Fixes:
|
|
15
|
+
|
|
16
|
+
- Fix architecture-dependent struct timeval packing for socket timeouts (#1034)
|
|
17
|
+
- Detects correct pack format for time_t and suseconds_t on each platform
|
|
18
|
+
- Fixes timeout issues on architectures with 64-bit time_t
|
|
19
|
+
|
|
20
|
+
- Fix get_multi hanging with large key counts (#776, #941)
|
|
21
|
+
- Add interleaved read/write for pipelined gets to prevent socket buffer deadlock
|
|
22
|
+
- For batches over 10,000 keys per server, requests are now sent in chunks
|
|
23
|
+
|
|
24
|
+
- **Breaking:** Enforce string-only values in raw mode (#1022)
|
|
25
|
+
- `set(key, nil, raw: true)` now raises `MarshalError` instead of storing `""`
|
|
26
|
+
- `set(key, 123, raw: true)` now raises `MarshalError` instead of storing `"123"`
|
|
27
|
+
- This matches the behavior of client-level `raw: true` mode
|
|
28
|
+
- To store counters, use string values: `set('counter', '0', raw: true)`
|
|
29
|
+
|
|
30
|
+
CI:
|
|
31
|
+
|
|
32
|
+
- Add TruffleRuby to CI test matrix (#988)
|
|
33
|
+
|
|
4
34
|
4.2.0
|
|
5
35
|
==========
|
|
6
36
|
|
data/README.md
CHANGED
|
@@ -31,6 +31,31 @@ Dalli supports two protocols for communicating with memcached:
|
|
|
31
31
|
Dalli::Client.new('localhost:11211', protocol: :meta)
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## Configuration Options
|
|
35
|
+
|
|
36
|
+
### Namespace
|
|
37
|
+
|
|
38
|
+
Use namespaces to partition your cache and avoid key collisions between different applications or environments:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# All keys will be prefixed with "myapp:"
|
|
42
|
+
Dalli::Client.new('localhost:11211', namespace: 'myapp')
|
|
43
|
+
|
|
44
|
+
# Dynamic namespace using a Proc (evaluated on each operation)
|
|
45
|
+
Dalli::Client.new('localhost:11211', namespace: -> { "tenant:#{Thread.current[:tenant_id]}" })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Namespace Separator
|
|
49
|
+
|
|
50
|
+
By default, the namespace and key are joined with a colon (`:`). You can customize this with the `namespace_separator` option:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# Keys will be prefixed with "myapp/" instead of "myapp:"
|
|
54
|
+
Dalli::Client.new('localhost:11211', namespace: 'myapp', namespace_separator: '/')
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The separator must be a single non-alphanumeric character. Valid examples: `:`, `/`, `|`, `.`, `-`, `_`, `#`
|
|
58
|
+
|
|
34
59
|
## Security Note
|
|
35
60
|
|
|
36
61
|
By default, Dalli uses Ruby's Marshal for serialization. Deserializing untrusted data with Marshal can lead to remote code execution. If you cache user-controlled data, consider using a safer serializer:
|
data/lib/dalli/key_manager.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Dalli
|
|
|
12
12
|
class KeyManager
|
|
13
13
|
MAX_KEY_LENGTH = 250
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
DEFAULT_NAMESPACE_SEPARATOR = ':'
|
|
16
16
|
|
|
17
17
|
# This is a hard coded md5 for historical reasons
|
|
18
18
|
TRUNCATED_KEY_SEPARATOR = ':md5:'
|
|
@@ -21,19 +21,26 @@ module Dalli
|
|
|
21
21
|
TRUNCATED_KEY_TARGET_SIZE = 249
|
|
22
22
|
|
|
23
23
|
DEFAULTS = {
|
|
24
|
-
digest_class: ::Digest::MD5
|
|
24
|
+
digest_class: ::Digest::MD5,
|
|
25
|
+
namespace_separator: DEFAULT_NAMESPACE_SEPARATOR
|
|
25
26
|
}.freeze
|
|
26
27
|
|
|
27
|
-
OPTIONS = %i[digest_class namespace].freeze
|
|
28
|
+
OPTIONS = %i[digest_class namespace namespace_separator].freeze
|
|
28
29
|
|
|
29
|
-
attr_reader :namespace
|
|
30
|
+
attr_reader :namespace, :namespace_separator
|
|
31
|
+
|
|
32
|
+
# Valid separators: non-alphanumeric, single printable ASCII characters
|
|
33
|
+
# Excludes: alphanumerics, whitespace, control characters
|
|
34
|
+
VALID_NAMESPACE_SEPARATORS = /\A[^a-zA-Z0-9\s\x00-\x1F\x7F]\z/
|
|
30
35
|
|
|
31
36
|
def initialize(client_options)
|
|
32
37
|
@key_options =
|
|
33
38
|
DEFAULTS.merge(client_options.slice(*OPTIONS))
|
|
34
39
|
validate_digest_class_option(@key_options)
|
|
40
|
+
validate_namespace_separator_option(@key_options)
|
|
35
41
|
|
|
36
42
|
@namespace = namespace_from_options
|
|
43
|
+
@namespace_separator = @key_options[:namespace_separator]
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
##
|
|
@@ -61,7 +68,7 @@ module Dalli
|
|
|
61
68
|
def key_with_namespace(key)
|
|
62
69
|
return key if namespace.nil?
|
|
63
70
|
|
|
64
|
-
"#{evaluate_namespace}#{
|
|
71
|
+
"#{evaluate_namespace}#{namespace_separator}#{key}"
|
|
65
72
|
end
|
|
66
73
|
|
|
67
74
|
def key_without_namespace(key)
|
|
@@ -75,9 +82,9 @@ module Dalli
|
|
|
75
82
|
end
|
|
76
83
|
|
|
77
84
|
def namespace_regexp
|
|
78
|
-
return /\A#{Regexp.escape(evaluate_namespace)}
|
|
85
|
+
return /\A#{Regexp.escape(evaluate_namespace)}#{Regexp.escape(namespace_separator)}/ if namespace.is_a?(Proc)
|
|
79
86
|
|
|
80
|
-
@namespace_regexp ||= /\A#{Regexp.escape(namespace)}
|
|
87
|
+
@namespace_regexp ||= /\A#{Regexp.escape(namespace)}#{Regexp.escape(namespace_separator)}/ unless namespace.nil?
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
def validate_digest_class_option(opts)
|
|
@@ -86,6 +93,14 @@ module Dalli
|
|
|
86
93
|
raise ArgumentError, 'The digest_class object must respond to the hexdigest method'
|
|
87
94
|
end
|
|
88
95
|
|
|
96
|
+
def validate_namespace_separator_option(opts)
|
|
97
|
+
sep = opts[:namespace_separator]
|
|
98
|
+
return if VALID_NAMESPACE_SEPARATORS.match?(sep)
|
|
99
|
+
|
|
100
|
+
raise ArgumentError,
|
|
101
|
+
'namespace_separator must be a single non-alphanumeric character (e.g., ":", "/", "|")'
|
|
102
|
+
end
|
|
103
|
+
|
|
89
104
|
def namespace_from_options
|
|
90
105
|
raw_namespace = @key_options[:namespace]
|
|
91
106
|
return nil unless raw_namespace
|
|
@@ -7,6 +7,13 @@ module Dalli
|
|
|
7
7
|
# Contains logic for the pipelined gets implemented by the client.
|
|
8
8
|
##
|
|
9
9
|
class PipelinedGetter
|
|
10
|
+
# For large batches, interleave sends with response draining to prevent
|
|
11
|
+
# socket buffer deadlock. Only kicks in above this threshold.
|
|
12
|
+
INTERLEAVE_THRESHOLD = 10_000
|
|
13
|
+
|
|
14
|
+
# Number of keys to send before draining responses during interleaved mode
|
|
15
|
+
CHUNK_SIZE = 10_000
|
|
16
|
+
|
|
10
17
|
def initialize(ring, key_manager)
|
|
11
18
|
@ring = ring
|
|
12
19
|
@key_manager = key_manager
|
|
@@ -19,8 +26,14 @@ module Dalli
|
|
|
19
26
|
return {} if keys.empty?
|
|
20
27
|
|
|
21
28
|
@ring.lock do
|
|
29
|
+
# Stores partial results collected during interleaved send phase
|
|
30
|
+
@partial_results = {}
|
|
22
31
|
servers = setup_requests(keys)
|
|
23
32
|
start_time = Time.now
|
|
33
|
+
|
|
34
|
+
# First yield any partial results collected during interleaved send
|
|
35
|
+
yield_partial_results(&block)
|
|
36
|
+
|
|
24
37
|
servers = fetch_responses(servers, start_time, @ring.socket_timeout, &block) until servers.empty?
|
|
25
38
|
end
|
|
26
39
|
rescue NetworkError => e
|
|
@@ -29,6 +42,15 @@ module Dalli
|
|
|
29
42
|
retry
|
|
30
43
|
end
|
|
31
44
|
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def yield_partial_results
|
|
48
|
+
@partial_results.each_pair do |key, value_list|
|
|
49
|
+
yield @key_manager.key_without_namespace(key), value_list
|
|
50
|
+
end
|
|
51
|
+
@partial_results.clear
|
|
52
|
+
end
|
|
53
|
+
|
|
32
54
|
def setup_requests(keys)
|
|
33
55
|
groups = groups_for_keys(keys)
|
|
34
56
|
make_getkq_requests(groups)
|
|
@@ -47,7 +69,14 @@ module Dalli
|
|
|
47
69
|
##
|
|
48
70
|
def make_getkq_requests(groups)
|
|
49
71
|
groups.each do |server, keys_for_server|
|
|
50
|
-
|
|
72
|
+
if keys_for_server.size <= INTERLEAVE_THRESHOLD
|
|
73
|
+
# Small batch - send all at once (existing behavior)
|
|
74
|
+
server.request(:pipelined_get, keys_for_server)
|
|
75
|
+
else
|
|
76
|
+
# Large batch - interleave sends with response draining
|
|
77
|
+
# Pass @partial_results directly to avoid hash allocation/merge overhead
|
|
78
|
+
server.request(:pipelined_get_interleaved, keys_for_server, CHUNK_SIZE, @partial_results)
|
|
79
|
+
end
|
|
51
80
|
rescue DalliError, NetworkError => e
|
|
52
81
|
Dalli.logger.debug { e.inspect }
|
|
53
82
|
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
data/lib/dalli/protocol/base.rb
CHANGED
|
@@ -42,8 +42,8 @@ module Dalli
|
|
|
42
42
|
@connection_manager.start_request!
|
|
43
43
|
response = send(opkey, *args)
|
|
44
44
|
|
|
45
|
-
# pipelined_get emit query but
|
|
46
|
-
@connection_manager.finish_request! unless opkey
|
|
45
|
+
# pipelined_get/pipelined_get_interleaved emit query but don't read the response(s)
|
|
46
|
+
@connection_manager.finish_request! unless %i[pipelined_get pipelined_get_interleaved].include?(opkey)
|
|
47
47
|
|
|
48
48
|
response
|
|
49
49
|
rescue Dalli::MarshalError => e
|
|
@@ -81,7 +81,9 @@ module Dalli
|
|
|
81
81
|
def pipeline_response_setup
|
|
82
82
|
verify_pipelined_state(:getkq)
|
|
83
83
|
write_noop
|
|
84
|
-
|
|
84
|
+
# Use ensure_ready instead of reset to preserve any data already buffered
|
|
85
|
+
# during interleaved pipelined get draining
|
|
86
|
+
response_buffer.ensure_ready
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
# Attempt to receive and parse as many key/value pairs as possible
|
|
@@ -220,6 +222,11 @@ module Dalli
|
|
|
220
222
|
end
|
|
221
223
|
|
|
222
224
|
def pipelined_get(keys)
|
|
225
|
+
# Clear buffer to remove any stale data from interrupted operations.
|
|
226
|
+
# Use clear (not reset) to keep pipeline_complete? = true, which is
|
|
227
|
+
# the expected state before pipeline_response_setup is called.
|
|
228
|
+
response_buffer.clear
|
|
229
|
+
|
|
223
230
|
req = +''
|
|
224
231
|
keys.each do |key|
|
|
225
232
|
req << quiet_get_request(key)
|
|
@@ -228,6 +235,51 @@ module Dalli
|
|
|
228
235
|
write(req)
|
|
229
236
|
end
|
|
230
237
|
|
|
238
|
+
# For large batches, interleave writing requests with draining responses.
|
|
239
|
+
# This prevents socket buffer deadlock when sending many keys.
|
|
240
|
+
# Populates the provided results hash with any responses drained during send.
|
|
241
|
+
def pipelined_get_interleaved(keys, chunk_size, results)
|
|
242
|
+
# Initialize the response buffer for draining during send phase
|
|
243
|
+
response_buffer.ensure_ready
|
|
244
|
+
|
|
245
|
+
keys.each_slice(chunk_size) do |chunk|
|
|
246
|
+
# Build and write this chunk of requests
|
|
247
|
+
req = +''
|
|
248
|
+
chunk.each do |key|
|
|
249
|
+
req << quiet_get_request(key)
|
|
250
|
+
end
|
|
251
|
+
write(req)
|
|
252
|
+
@connection_manager.flush
|
|
253
|
+
|
|
254
|
+
# Drain any available responses directly into results hash
|
|
255
|
+
drain_pipeline_responses(results)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Non-blocking read and processing of any available pipeline responses.
|
|
260
|
+
# Used during interleaved pipelined gets to prevent buffer deadlock.
|
|
261
|
+
# Populates the provided results hash directly to avoid allocation overhead.
|
|
262
|
+
def drain_pipeline_responses(results)
|
|
263
|
+
return unless connected?
|
|
264
|
+
|
|
265
|
+
# Non-blocking check if socket has data available
|
|
266
|
+
return unless sock.wait_readable(0)
|
|
267
|
+
|
|
268
|
+
# Read available data without blocking
|
|
269
|
+
response_buffer.read
|
|
270
|
+
|
|
271
|
+
# Process any complete responses in the buffer
|
|
272
|
+
loop do
|
|
273
|
+
status, cas, key, value = response_buffer.process_single_getk_response
|
|
274
|
+
break if status.nil? # No complete response available
|
|
275
|
+
|
|
276
|
+
results[key] = [value, cas] unless key.nil?
|
|
277
|
+
end
|
|
278
|
+
rescue SystemCallError, Dalli::NetworkError
|
|
279
|
+
# Ignore errors during drain - they'll be handled in fetch_responses
|
|
280
|
+
nil
|
|
281
|
+
end
|
|
282
|
+
|
|
231
283
|
def response_buffer
|
|
232
284
|
@response_buffer ||= ResponseBuffer.new(@connection_manager, response_processor)
|
|
233
285
|
end
|
|
@@ -41,6 +41,15 @@ module Dalli
|
|
|
41
41
|
@buffer = ''.b
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# Ensures the buffer is initialized for reading without discarding
|
|
45
|
+
# existing data. Used by interleaved pipelined get which may have
|
|
46
|
+
# already buffered partial responses during the send phase.
|
|
47
|
+
def ensure_ready
|
|
48
|
+
return if in_progress?
|
|
49
|
+
|
|
50
|
+
@buffer = ''.b
|
|
51
|
+
end
|
|
52
|
+
|
|
44
53
|
# Clear the internal response buffer
|
|
45
54
|
def clear
|
|
46
55
|
@buffer = nil
|
|
@@ -32,22 +32,8 @@ module Dalli
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def store(value, req_options, bitflags)
|
|
35
|
-
if req_options
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# If the value is a simple string, going through serialization is costly
|
|
39
|
-
# for no benefit other than preserving encoding.
|
|
40
|
-
# Assuming most strings are either UTF-8 or BINARY we can just store
|
|
41
|
-
# that information in the bitflags.
|
|
42
|
-
if req_options[:string_fastpath] && value.instance_of?(String)
|
|
43
|
-
case value.encoding
|
|
44
|
-
when Encoding::BINARY
|
|
45
|
-
return [value, bitflags]
|
|
46
|
-
when Encoding::UTF_8
|
|
47
|
-
return [value, bitflags | FLAG_UTF8]
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
35
|
+
return store_raw(value, bitflags) if req_options&.dig(:raw)
|
|
36
|
+
return store_string_fastpath(value, bitflags) if use_string_fastpath?(value, req_options)
|
|
51
37
|
|
|
52
38
|
[serialize_value(value), bitflags | FLAG_SERIALIZED]
|
|
53
39
|
end
|
|
@@ -85,6 +71,30 @@ module Dalli
|
|
|
85
71
|
|
|
86
72
|
private
|
|
87
73
|
|
|
74
|
+
def store_raw(value, bitflags)
|
|
75
|
+
unless value.is_a?(String)
|
|
76
|
+
raise Dalli::MarshalError, "Dalli raw mode requires string values, got: #{value.class}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
[value, bitflags]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# If the value is a simple string, going through serialization is costly
|
|
83
|
+
# for no benefit other than preserving encoding.
|
|
84
|
+
# Assuming most strings are either UTF-8 or BINARY we can just store
|
|
85
|
+
# that information in the bitflags.
|
|
86
|
+
def store_string_fastpath(value, bitflags)
|
|
87
|
+
case value.encoding
|
|
88
|
+
when Encoding::BINARY then [value, bitflags]
|
|
89
|
+
when Encoding::UTF_8 then [value, bitflags | FLAG_UTF8]
|
|
90
|
+
else [serialize_value(value), bitflags | FLAG_SERIALIZED]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def use_string_fastpath?(value, req_options)
|
|
95
|
+
req_options&.dig(:string_fastpath) && value.instance_of?(String)
|
|
96
|
+
end
|
|
97
|
+
|
|
88
98
|
def warn_if_marshal_default(protocol_options)
|
|
89
99
|
return if protocol_options.key?(:serializer)
|
|
90
100
|
return if @@marshal_warning_logged
|
data/lib/dalli/socket.rb
CHANGED
|
@@ -107,7 +107,7 @@ module Dalli
|
|
|
107
107
|
# aliases TCPSocket#initialize method to #original_resolv_initialize.
|
|
108
108
|
# https://github.com/ruby/resolv-replace/blob/v0.1.1/lib/resolv-replace.rb#L21
|
|
109
109
|
if RUBY_VERSION >= '3.0' &&
|
|
110
|
-
!::TCPSocket.
|
|
110
|
+
!::TCPSocket.private_method_defined?(:original_resolv_initialize)
|
|
111
111
|
sock = new(host, port, connect_timeout: options[:socket_timeout])
|
|
112
112
|
yield(sock)
|
|
113
113
|
else
|
|
@@ -138,16 +138,40 @@ module Dalli
|
|
|
138
138
|
return unless options[:socket_timeout]
|
|
139
139
|
|
|
140
140
|
if sock.respond_to?(:timeout=)
|
|
141
|
+
# Ruby 3.2+ has IO#timeout for reliable cross-platform timeout handling
|
|
141
142
|
sock.timeout = options[:socket_timeout]
|
|
142
143
|
else
|
|
144
|
+
# Ruby 3.1 fallback using socket options
|
|
145
|
+
# struct timeval has architecture-dependent sizes (time_t, suseconds_t)
|
|
143
146
|
seconds, fractional = options[:socket_timeout].divmod(1)
|
|
144
|
-
|
|
147
|
+
microseconds = (fractional * 1_000_000).to_i
|
|
148
|
+
timeval = pack_timeval(sock, seconds, microseconds)
|
|
145
149
|
|
|
146
150
|
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO, timeval)
|
|
147
151
|
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDTIMEO, timeval)
|
|
148
152
|
end
|
|
149
153
|
end
|
|
150
154
|
|
|
155
|
+
# Pack formats for struct timeval across architectures.
|
|
156
|
+
# Uses fixed-size formats for JRuby compatibility (JRuby doesn't support _ modifier on q).
|
|
157
|
+
# - ll: 8 bytes (32-bit time_t, 32-bit suseconds_t)
|
|
158
|
+
# - qq: 16 bytes (64-bit time_t, 64-bit suseconds_t or padded 32-bit)
|
|
159
|
+
TIMEVAL_PACK_FORMATS = %w[ll qq].freeze
|
|
160
|
+
TIMEVAL_TEST_VALUES = [0, 0].freeze
|
|
161
|
+
|
|
162
|
+
# Detect and cache the correct pack format for struct timeval on this platform.
|
|
163
|
+
# Different architectures have different sizes for time_t and suseconds_t.
|
|
164
|
+
def self.timeval_pack_format(sock)
|
|
165
|
+
@timeval_pack_format ||= begin
|
|
166
|
+
expected_size = sock.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO).data.bytesize
|
|
167
|
+
TIMEVAL_PACK_FORMATS.find { |fmt| TIMEVAL_TEST_VALUES.pack(fmt).bytesize == expected_size } || 'll'
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.pack_timeval(sock, seconds, microseconds)
|
|
172
|
+
[seconds, microseconds].pack(timeval_pack_format(sock))
|
|
173
|
+
end
|
|
174
|
+
|
|
151
175
|
def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
|
|
152
176
|
ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
|
|
153
177
|
ssl_socket.hostname = host
|
data/lib/dalli/version.rb
CHANGED