dalli 4.3.2 → 5.0.2
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 +96 -0
- data/Gemfile +1 -0
- data/README.md +4 -16
- data/lib/dalli/client.rb +72 -46
- data/lib/dalli/instrumentation.rb +3 -1
- data/lib/dalli/options.rb +1 -1
- data/lib/dalli/pid_cache.rb +1 -1
- data/lib/dalli/pipelined_getter.rb +9 -16
- data/lib/dalli/protocol/base.rb +28 -19
- data/lib/dalli/protocol/connection_manager.rb +9 -3
- data/lib/dalli/protocol/meta.rb +77 -5
- data/lib/dalli/protocol/response_buffer.rb +27 -12
- data/lib/dalli/protocol/{meta/response_processor.rb → response_processor.rb} +10 -22
- data/lib/dalli/protocol/server_config_parser.rb +1 -1
- data/lib/dalli/ring.rb +2 -2
- data/lib/dalli/servers_arg_normalizer.rb +1 -1
- data/lib/dalli/socket.rb +4 -0
- data/lib/dalli/version.rb +2 -2
- data/lib/dalli.rb +2 -4
- metadata +5 -11
- data/lib/dalli/protocol/binary/request_formatter.rb +0 -117
- data/lib/dalli/protocol/binary/response_header.rb +0 -36
- data/lib/dalli/protocol/binary/response_processor.rb +0 -239
- data/lib/dalli/protocol/binary/sasl_authentication.rb +0 -60
- data/lib/dalli/protocol/binary.rb +0 -200
- data/lib/dalli/protocol_deprecations.rb +0 -45
- /data/lib/dalli/protocol/{meta/key_regularizer.rb → key_regularizer.rb} +0 -0
- /data/lib/dalli/protocol/{meta/request_formatter.rb → request_formatter.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae3cbae2603955279bb78b75682ac1f56105d5ec2b2e0d464ab15255ed23369b
|
|
4
|
+
data.tar.gz: 2e5a3960e638293cfdf5663ef959d1ab9690620e09f0151a844cdeb9424cbf80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 243382482bc345bf982f2cb96fae28b974f37b2eee653a71c7f3a85518ef5acdbfda6f2fddacfa31da6252774000e11a1c96d3707d29808c636951d85ace17be
|
|
7
|
+
data.tar.gz: 5938608c26cd307e397cad4fdf6e2fda4519fdbd8df2b7d75352b691996fec4dca59632cc0053b8bf410612c8e6cada73ccad7baf5c000a183bd04bb196d056e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
Dalli Changelog
|
|
2
2
|
=====================
|
|
3
3
|
|
|
4
|
+
5.0.2
|
|
5
|
+
==========
|
|
6
|
+
|
|
7
|
+
Performance:
|
|
8
|
+
|
|
9
|
+
- Add single-server fast path for `get_multi`, `set_multi`, and `delete_multi` (#1077)
|
|
10
|
+
- When only one memcached server is configured, bypass the `Pipelined*` machinery (IO.select, response buffering, server grouping) and issue all quiet meta requests inline followed by a noop terminator
|
|
11
|
+
- `get_multi` shows ~1.5x improvement at 10 keys and ~1.75x at 100–500 keys compared to the `PipelinedGetter` path
|
|
12
|
+
- Thanks to Dan Mayer (Shopify) for this contribution
|
|
13
|
+
|
|
14
|
+
Development:
|
|
15
|
+
|
|
16
|
+
- Add `bin/benchmark_branch` script for benchmarking against the current branch
|
|
17
|
+
|
|
18
|
+
5.0.1
|
|
19
|
+
==========
|
|
20
|
+
|
|
21
|
+
Performance:
|
|
22
|
+
|
|
23
|
+
- Reduce object allocations in pipelined get response processing (#1072, #1078)
|
|
24
|
+
- Offset-based `ResponseBuffer`: track a read offset instead of slicing a new string after every parsed response; compact only when the consumed portion exceeds 4KB and more than half the buffer
|
|
25
|
+
- Inline response processor parsing: avoid intermediate array allocations from `split`-based header parsing
|
|
26
|
+
- Block-based `pipeline_next_responses`: yield `(key, value, cas)` directly when a block is given, avoiding per-call Hash allocation
|
|
27
|
+
- `PipelinedGetter`: replace Hash-based socket-to-server mapping with linear scan (faster for typical 1-5 server counts); use `Process.clock_gettime(CLOCK_MONOTONIC)` instead of `Time.now`
|
|
28
|
+
- Add cross-version benchmark script (`bin/compare_versions`) for reproducible performance comparisons across Dalli versions
|
|
29
|
+
|
|
30
|
+
Bug Fixes:
|
|
31
|
+
|
|
32
|
+
- Rescue `IOError` in connection manager `write`/`flush` methods (#1075)
|
|
33
|
+
- Prevents unhandled exceptions when a connection is closed mid-operation
|
|
34
|
+
- Thanks to Graham Cooper (Shopify) for this fix
|
|
35
|
+
|
|
36
|
+
Development:
|
|
37
|
+
|
|
38
|
+
- Add `rubocop-thread_safety` for detecting thread-safety issues (#1076)
|
|
39
|
+
- Add CONTRIBUTING.md with AI contribution policy (#1074)
|
|
40
|
+
|
|
41
|
+
5.0.0
|
|
42
|
+
==========
|
|
43
|
+
|
|
44
|
+
**Breaking Changes:**
|
|
45
|
+
|
|
46
|
+
- **Removed binary protocol** - The meta protocol is now the only supported protocol
|
|
47
|
+
- The `:protocol` option is no longer used
|
|
48
|
+
- Requires memcached 1.6+ (for meta protocol support)
|
|
49
|
+
- Users on older memcached versions must upgrade or stay on Dalli 4.x
|
|
50
|
+
|
|
51
|
+
- **Removed SASL authentication** - The meta protocol does not support authentication
|
|
52
|
+
- Use network-level security (firewall rules, VPN) or memcached's TLS support instead
|
|
53
|
+
- Users requiring SASL authentication must stay on Dalli 4.x with binary protocol
|
|
54
|
+
|
|
55
|
+
- **Ruby 3.3+ required** - Dropped support for Ruby 3.1 and 3.2
|
|
56
|
+
- Ruby 3.2 reached end-of-life in March 2026
|
|
57
|
+
- JRuby remains supported
|
|
58
|
+
|
|
59
|
+
Performance:
|
|
60
|
+
|
|
61
|
+
- **~7% read performance improvement** (CRuby only)
|
|
62
|
+
- Use native `IO#read` instead of custom `readfull` implementation
|
|
63
|
+
- Enabled by Ruby 3.3's `IO#timeout=` support
|
|
64
|
+
- JRuby continues to use `readfull` for compatibility
|
|
65
|
+
|
|
66
|
+
OpenTelemetry:
|
|
67
|
+
|
|
68
|
+
- Migrate to stable OTel semantic conventions (#1070)
|
|
69
|
+
- `db.system` renamed to `db.system.name`
|
|
70
|
+
- `db.operation` renamed to `db.operation.name`
|
|
71
|
+
- `server.address` now contains hostname only; `server.port` is a separate integer attribute
|
|
72
|
+
- `get_with_metadata` and `fetch_with_lock` now include `server.address`/`server.port`
|
|
73
|
+
- Add `db.query.text` span attribute with configurable modes
|
|
74
|
+
- `:otel_db_statement` option: `:include`, `:obfuscate`, or `nil` (default: omitted)
|
|
75
|
+
- Add `peer.service` span attribute
|
|
76
|
+
- `:otel_peer_service` option for logical service naming
|
|
77
|
+
|
|
78
|
+
Internal:
|
|
79
|
+
|
|
80
|
+
- Simplified protocol directory structure: moved `lib/dalli/protocol/meta/*` to `lib/dalli/protocol/`
|
|
81
|
+
- Removed deprecated binary protocol files and SASL authentication code
|
|
82
|
+
- Removed `require 'set'` (autoloaded in Ruby 3.3+)
|
|
83
|
+
|
|
84
|
+
4.3.3
|
|
85
|
+
==========
|
|
86
|
+
|
|
87
|
+
Performance:
|
|
88
|
+
|
|
89
|
+
- Reduce object allocations in pipelined get response processing (#1072)
|
|
90
|
+
- Offset-based `ResponseBuffer`: track a read offset instead of slicing a new string after every parsed response; compact only when the consumed portion exceeds 4KB and more than half the buffer
|
|
91
|
+
- Inline response processor parsing: avoid intermediate array allocations from `split`-based header parsing in both binary and meta protocols
|
|
92
|
+
- Block-based `pipeline_next_responses`: yield `(key, value, cas)` directly when a block is given, avoiding per-call Hash allocation
|
|
93
|
+
- `PipelinedGetter`: replace Hash-based socket-to-server mapping with linear scan (faster for typical 1-5 server counts); use `Process.clock_gettime(CLOCK_MONOTONIC)` instead of `Time.now`
|
|
94
|
+
- Add cross-version benchmark script (`bin/compare_versions`) for reproducible performance comparisons across Dalli versions
|
|
95
|
+
|
|
96
|
+
Bug Fixes:
|
|
97
|
+
|
|
98
|
+
- Skip OTel integration tests when meta protocol is unavailable (#1072)
|
|
99
|
+
|
|
4
100
|
4.3.2
|
|
5
101
|
==========
|
|
6
102
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -10,26 +10,14 @@ Dalli supports:
|
|
|
10
10
|
* Fine-grained control of data serialization and compression
|
|
11
11
|
* Thread-safe operation (either through use of a connection pool, or by using the Dalli client in threadsafe mode)
|
|
12
12
|
* SSL/TLS connections to memcached
|
|
13
|
-
* SASL authentication
|
|
14
13
|
* OpenTelemetry distributed tracing (automatic when SDK is present)
|
|
15
14
|
|
|
16
15
|
The name is a variant of Salvador Dali for his famous painting [The Persistence of Memory](http://en.wikipedia.org/wiki/The_Persistence_of_Memory).
|
|
17
16
|
|
|
18
17
|
## Requirements
|
|
19
18
|
|
|
20
|
-
* Ruby 3.
|
|
21
|
-
* memcached 1.
|
|
22
|
-
|
|
23
|
-
## Protocol Options
|
|
24
|
-
|
|
25
|
-
Dalli supports two protocols for communicating with memcached:
|
|
26
|
-
|
|
27
|
-
* `:binary` (default) - Works with all memcached versions, supports SASL authentication
|
|
28
|
-
* `:meta` - Requires memcached 1.6+, better performance for some operations, no authentication support
|
|
29
|
-
|
|
30
|
-
```ruby
|
|
31
|
-
Dalli::Client.new('localhost:11211', protocol: :meta)
|
|
32
|
-
```
|
|
19
|
+
* Ruby 3.3 or later (JRuby also supported)
|
|
20
|
+
* memcached 1.6 or later
|
|
33
21
|
|
|
34
22
|
## Configuration Options
|
|
35
23
|
|
|
@@ -64,7 +52,7 @@ By default, Dalli uses Ruby's Marshal for serialization. Deserializing untrusted
|
|
|
64
52
|
Dalli::Client.new('localhost:11211', serializer: JSON)
|
|
65
53
|
```
|
|
66
54
|
|
|
67
|
-
See the [
|
|
55
|
+
See the [5.0-Upgrade.md](5.0-Upgrade.md) guide for upgrade information.
|
|
68
56
|
|
|
69
57
|
## OpenTelemetry Tracing
|
|
70
58
|
|
|
@@ -125,7 +113,7 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
|
125
113
|
|
|
126
114
|
## Contributing
|
|
127
115
|
|
|
128
|
-
|
|
116
|
+
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute, including our policy on AI-authored contributions.
|
|
129
117
|
|
|
130
118
|
## Appreciation
|
|
131
119
|
|
data/lib/dalli/client.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'digest/md5'
|
|
4
|
-
require 'set'
|
|
5
4
|
|
|
6
5
|
# encoding: ascii
|
|
7
6
|
module Dalli
|
|
@@ -48,8 +47,6 @@ module Dalli
|
|
|
48
47
|
# serialization pipeline.
|
|
49
48
|
# - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
|
|
50
49
|
# useful for injecting a FIPS compliant hash object.
|
|
51
|
-
# - :protocol - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
|
|
52
|
-
# to communicate with memcached.
|
|
53
50
|
# - :otel_db_statement - controls the +db.query.text+ span attribute when OpenTelemetry is loaded.
|
|
54
51
|
# +:include+ logs the full operation and key(s), +:obfuscate+ replaces keys with "?",
|
|
55
52
|
# +nil+ (default) omits the attribute entirely.
|
|
@@ -58,9 +55,9 @@ module Dalli
|
|
|
58
55
|
def initialize(servers = nil, options = {})
|
|
59
56
|
@normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
|
60
57
|
@options = normalize_options(options)
|
|
58
|
+
warn_removed_options(@options)
|
|
61
59
|
@key_manager = ::Dalli::KeyManager.new(@options)
|
|
62
60
|
@ring = nil
|
|
63
|
-
emit_deprecation_warnings
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
#
|
|
@@ -102,10 +99,7 @@ module Dalli
|
|
|
102
99
|
end
|
|
103
100
|
|
|
104
101
|
##
|
|
105
|
-
# Get value with extended metadata
|
|
106
|
-
#
|
|
107
|
-
# IMPORTANT: This method requires memcached 1.6+ and the meta protocol (protocol: :meta).
|
|
108
|
-
# It will raise an error if used with the binary protocol.
|
|
102
|
+
# Get value with extended metadata.
|
|
109
103
|
#
|
|
110
104
|
# @param key [String] the cache key
|
|
111
105
|
# @param options [Hash] options controlling what metadata to return
|
|
@@ -133,8 +127,6 @@ module Dalli
|
|
|
133
127
|
# # => { value: "data", cas: 123, hit_before: true, last_access: 42 }
|
|
134
128
|
#
|
|
135
129
|
def get_with_metadata(key, options = {})
|
|
136
|
-
raise_unless_meta_protocol!
|
|
137
|
-
|
|
138
130
|
key = key.to_s
|
|
139
131
|
key = @key_manager.validate_key(key)
|
|
140
132
|
|
|
@@ -208,9 +200,6 @@ module Dalli
|
|
|
208
200
|
# cache entry (the "thundering herd" problem). Only one client wins the right to
|
|
209
201
|
# regenerate; other clients receive the stale value (if available) or wait.
|
|
210
202
|
#
|
|
211
|
-
# IMPORTANT: This method requires memcached 1.6+ and the meta protocol (protocol: :meta).
|
|
212
|
-
# It will raise an error if used with the binary protocol.
|
|
213
|
-
#
|
|
214
203
|
# @param key [String] the cache key
|
|
215
204
|
# @param ttl [Integer] time-to-live for the cached value in seconds
|
|
216
205
|
# @param lock_ttl [Integer] how long the lock/stub lives (default: 30 seconds)
|
|
@@ -236,8 +225,6 @@ module Dalli
|
|
|
236
225
|
def fetch_with_lock(key, ttl: nil, lock_ttl: 30, recache_threshold: nil, req_options: nil, &block)
|
|
237
226
|
raise ArgumentError, 'Block is required for fetch_with_lock' unless block_given?
|
|
238
227
|
|
|
239
|
-
raise_unless_meta_protocol!
|
|
240
|
-
|
|
241
228
|
key = key.to_s
|
|
242
229
|
key = @key_manager.validate_key(key)
|
|
243
230
|
|
|
@@ -324,7 +311,11 @@ module Dalli
|
|
|
324
311
|
return if hash.empty?
|
|
325
312
|
|
|
326
313
|
Instrumentation.trace('set_multi', multi_trace_attrs('set_multi', hash.size, hash.keys)) do
|
|
327
|
-
|
|
314
|
+
if ring.servers.size == 1
|
|
315
|
+
single_server_set_multi(hash, ttl_or_default(ttl), req_options)
|
|
316
|
+
else
|
|
317
|
+
pipelined_setter.process(hash, ttl_or_default(ttl), req_options)
|
|
318
|
+
end
|
|
328
319
|
end
|
|
329
320
|
end
|
|
330
321
|
|
|
@@ -381,7 +372,11 @@ module Dalli
|
|
|
381
372
|
return if keys.empty?
|
|
382
373
|
|
|
383
374
|
Instrumentation.trace('delete_multi', multi_trace_attrs('delete_multi', keys.size, keys)) do
|
|
384
|
-
|
|
375
|
+
if ring.servers.size == 1
|
|
376
|
+
single_server_delete_multi(keys)
|
|
377
|
+
else
|
|
378
|
+
pipelined_deleter.process(keys)
|
|
379
|
+
end
|
|
385
380
|
end
|
|
386
381
|
end
|
|
387
382
|
|
|
@@ -514,10 +509,6 @@ module Dalli
|
|
|
514
509
|
|
|
515
510
|
private
|
|
516
511
|
|
|
517
|
-
# Records hit/miss metrics on a span for cache observability.
|
|
518
|
-
# @param span [OpenTelemetry::Trace::Span, nil] the span to record on
|
|
519
|
-
# @param key_count [Integer] total keys requested
|
|
520
|
-
# @param hit_count [Integer] keys found in cache
|
|
521
512
|
def record_hit_miss_metrics(span, key_count, hit_count)
|
|
522
513
|
return unless span
|
|
523
514
|
|
|
@@ -539,13 +530,52 @@ module Dalli
|
|
|
539
530
|
|
|
540
531
|
def get_multi_hash(keys)
|
|
541
532
|
Instrumentation.trace_with_result('get_multi', get_multi_attributes(keys)) do |span|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
533
|
+
hash = if ring.servers.size == 1
|
|
534
|
+
single_server_get_multi(keys)
|
|
535
|
+
else
|
|
536
|
+
{}.tap do |h|
|
|
537
|
+
pipelined_getter.process(keys) { |k, data| h[k] = data.first }
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
record_hit_miss_metrics(span, keys.size, hash.size)
|
|
541
|
+
hash
|
|
546
542
|
end
|
|
547
543
|
end
|
|
548
544
|
|
|
545
|
+
def single_server
|
|
546
|
+
server = ring.servers.first
|
|
547
|
+
server if server&.alive?
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def single_server_get_multi(keys)
|
|
551
|
+
keys.map! { |k| @key_manager.validate_key(k.to_s) }
|
|
552
|
+
return {} unless (server = single_server)
|
|
553
|
+
|
|
554
|
+
result = server.request(:read_multi_req, keys)
|
|
555
|
+
result.transform_keys! { |k| @key_manager.key_without_namespace(k) }
|
|
556
|
+
result
|
|
557
|
+
rescue Dalli::NetworkError
|
|
558
|
+
{}
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def single_server_set_multi(hash, ttl, req_options)
|
|
562
|
+
pairs = hash.transform_keys { |k| @key_manager.validate_key(k.to_s) }
|
|
563
|
+
return unless (server = single_server)
|
|
564
|
+
|
|
565
|
+
server.request(:write_multi_req, pairs, ttl, req_options)
|
|
566
|
+
rescue Dalli::NetworkError
|
|
567
|
+
nil
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def single_server_delete_multi(keys)
|
|
571
|
+
validated_keys = keys.map { |k| @key_manager.validate_key(k.to_s) }
|
|
572
|
+
return unless (server = single_server)
|
|
573
|
+
|
|
574
|
+
server.request(:delete_multi_req, validated_keys)
|
|
575
|
+
rescue Dalli::NetworkError
|
|
576
|
+
nil
|
|
577
|
+
end
|
|
578
|
+
|
|
549
579
|
def get_multi_attributes(keys)
|
|
550
580
|
multi_trace_attrs('get_multi', keys.size, keys)
|
|
551
581
|
end
|
|
@@ -607,16 +637,7 @@ module Dalli
|
|
|
607
637
|
end
|
|
608
638
|
|
|
609
639
|
def ring
|
|
610
|
-
@ring ||= Dalli::Ring.new(@normalized_servers,
|
|
611
|
-
end
|
|
612
|
-
|
|
613
|
-
def protocol_implementation
|
|
614
|
-
@protocol_implementation ||= case @options[:protocol]&.to_s
|
|
615
|
-
when 'meta'
|
|
616
|
-
Dalli::Protocol::Meta
|
|
617
|
-
else
|
|
618
|
-
Dalli::Protocol::Binary
|
|
619
|
-
end
|
|
640
|
+
@ring ||= Dalli::Ring.new(@normalized_servers, @options)
|
|
620
641
|
end
|
|
621
642
|
|
|
622
643
|
##
|
|
@@ -627,7 +648,7 @@ module Dalli
|
|
|
627
648
|
#
|
|
628
649
|
# This method also forces retries on network errors - when
|
|
629
650
|
# a particular memcached instance becomes unreachable, or the
|
|
630
|
-
#
|
|
651
|
+
# operation times out.
|
|
631
652
|
##
|
|
632
653
|
def perform(*all_args)
|
|
633
654
|
return yield if block_given?
|
|
@@ -654,6 +675,21 @@ module Dalli
|
|
|
654
675
|
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
|
655
676
|
end
|
|
656
677
|
|
|
678
|
+
REMOVED_OPTIONS = {
|
|
679
|
+
protocol: 'Dalli 5.0 only supports the meta protocol. The :protocol option has been removed.',
|
|
680
|
+
username: 'Dalli 5.0 removed SASL authentication support. The :username option is ignored.',
|
|
681
|
+
password: 'Dalli 5.0 removed SASL authentication support. The :password option is ignored.'
|
|
682
|
+
}.freeze
|
|
683
|
+
private_constant :REMOVED_OPTIONS
|
|
684
|
+
|
|
685
|
+
def warn_removed_options(opts)
|
|
686
|
+
REMOVED_OPTIONS.each do |key, message|
|
|
687
|
+
next unless opts.key?(key)
|
|
688
|
+
|
|
689
|
+
Dalli.logger.warn(message)
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
657
693
|
def pipelined_getter
|
|
658
694
|
PipelinedGetter.new(ring, @key_manager)
|
|
659
695
|
end
|
|
@@ -665,15 +701,5 @@ module Dalli
|
|
|
665
701
|
def pipelined_deleter
|
|
666
702
|
PipelinedDeleter.new(ring, @key_manager)
|
|
667
703
|
end
|
|
668
|
-
|
|
669
|
-
def raise_unless_meta_protocol!
|
|
670
|
-
return if protocol_implementation == Dalli::Protocol::Meta
|
|
671
|
-
|
|
672
|
-
raise Dalli::DalliError,
|
|
673
|
-
'This operation requires the meta protocol (memcached 1.6+). ' \
|
|
674
|
-
'Use protocol: :meta when creating the client.'
|
|
675
|
-
end
|
|
676
|
-
|
|
677
|
-
include ProtocolDeprecations
|
|
678
704
|
end
|
|
679
705
|
end
|
|
@@ -8,7 +8,7 @@ module Dalli
|
|
|
8
8
|
# When OpenTelemetry is loaded, Dalli automatically creates spans for cache operations.
|
|
9
9
|
# When OpenTelemetry is not available, all tracing methods are no-ops with zero overhead.
|
|
10
10
|
#
|
|
11
|
-
# Dalli
|
|
11
|
+
# Dalli 5.0 uses the stable OTel semantic conventions for database spans.
|
|
12
12
|
#
|
|
13
13
|
# == Span Attributes
|
|
14
14
|
#
|
|
@@ -61,11 +61,13 @@ module Dalli
|
|
|
61
61
|
# Uses the library name 'dalli' and current Dalli::VERSION.
|
|
62
62
|
#
|
|
63
63
|
# @return [OpenTelemetry::Trace::Tracer, nil] the tracer or nil if OTel unavailable
|
|
64
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
64
65
|
def tracer
|
|
65
66
|
return @tracer if defined?(@tracer)
|
|
66
67
|
|
|
67
68
|
@tracer = (OpenTelemetry.tracer_provider.tracer('dalli', Dalli::VERSION) if defined?(OpenTelemetry))
|
|
68
69
|
end
|
|
70
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
69
71
|
|
|
70
72
|
# Returns true if instrumentation is enabled (OpenTelemetry SDK is available).
|
|
71
73
|
#
|
data/lib/dalli/options.rb
CHANGED
data/lib/dalli/pid_cache.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'set'
|
|
4
|
-
|
|
5
3
|
module Dalli
|
|
6
4
|
##
|
|
7
5
|
# Contains logic for the pipelined gets implemented by the client.
|
|
@@ -29,7 +27,7 @@ module Dalli
|
|
|
29
27
|
# Stores partial results collected during interleaved send phase
|
|
30
28
|
@partial_results = {}
|
|
31
29
|
servers = setup_requests(keys)
|
|
32
|
-
start_time =
|
|
30
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
33
31
|
|
|
34
32
|
# First yield any partial results collected during interleaved send
|
|
35
33
|
yield_partial_results(&block)
|
|
@@ -149,7 +147,7 @@ module Dalli
|
|
|
149
147
|
end
|
|
150
148
|
|
|
151
149
|
def remaining_time(start, timeout)
|
|
152
|
-
elapsed =
|
|
150
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
153
151
|
return 0 if elapsed > timeout
|
|
154
152
|
|
|
155
153
|
timeout - elapsed
|
|
@@ -168,8 +166,8 @@ module Dalli
|
|
|
168
166
|
# Processes responses from a server. Returns true if there are no
|
|
169
167
|
# additional responses from this server.
|
|
170
168
|
def process_server(server)
|
|
171
|
-
server.pipeline_next_responses
|
|
172
|
-
yield @key_manager.key_without_namespace(key),
|
|
169
|
+
server.pipeline_next_responses do |key, value, cas|
|
|
170
|
+
yield @key_manager.key_without_namespace(key), [value, cas]
|
|
173
171
|
end
|
|
174
172
|
|
|
175
173
|
server.pipeline_complete?
|
|
@@ -178,18 +176,13 @@ module Dalli
|
|
|
178
176
|
def servers_with_response(servers, timeout)
|
|
179
177
|
return [] if servers.empty?
|
|
180
178
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# My suspicion is that we may want to try and push this down into the
|
|
184
|
-
# individual servers, but I'm not sure. For now, we keep the
|
|
185
|
-
# mapping between the alerted object (the socket) and the
|
|
186
|
-
# corrresponding server here.
|
|
187
|
-
server_map = servers.each_with_object({}) { |s, h| h[s.sock] = s }
|
|
188
|
-
|
|
189
|
-
readable, = IO.select(server_map.keys, nil, nil, timeout)
|
|
179
|
+
sockets = servers.map(&:sock)
|
|
180
|
+
readable, = IO.select(sockets, nil, nil, timeout)
|
|
190
181
|
return [] if readable.nil?
|
|
191
182
|
|
|
192
|
-
|
|
183
|
+
# For typical server counts (1-5), linear scan is faster than
|
|
184
|
+
# building and looking up a hash map
|
|
185
|
+
readable.filter_map { |sock| servers.find { |s| s.sock == sock } }
|
|
193
186
|
end
|
|
194
187
|
|
|
195
188
|
def groups_for_keys(*keys)
|
data/lib/dalli/protocol/base.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Dalli
|
|
|
22
22
|
|
|
23
23
|
def initialize(attribs, client_options = {})
|
|
24
24
|
hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
|
|
25
|
+
warn_uri_credentials(user_creds)
|
|
25
26
|
@options = client_options.merge(user_creds)
|
|
26
27
|
@raw_mode = client_options[:raw]
|
|
27
28
|
@value_marshaller = @raw_mode ? StringMarshaller.new(@options) : ValueMarshaller.new(@options)
|
|
@@ -91,10 +92,13 @@ module Dalli
|
|
|
91
92
|
# repeatedly whenever this server's socket is readable until
|
|
92
93
|
# #pipeline_complete?.
|
|
93
94
|
#
|
|
94
|
-
#
|
|
95
|
-
|
|
95
|
+
# When a block is given, yields (key, value, cas) for each response,
|
|
96
|
+
# avoiding intermediate Hash allocation. Returns nil.
|
|
97
|
+
# Without a block, returns a Hash of { key => [value, cas] }.
|
|
98
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
99
|
+
def pipeline_next_responses(&block)
|
|
96
100
|
reconnect_on_pipeline_complete!
|
|
97
|
-
values =
|
|
101
|
+
values = nil
|
|
98
102
|
|
|
99
103
|
response_buffer.read
|
|
100
104
|
|
|
@@ -108,16 +112,24 @@ module Dalli
|
|
|
108
112
|
|
|
109
113
|
# If the status is ok and the key is not nil, then this is a
|
|
110
114
|
# getkq response with a value that we want to set in the response hash
|
|
111
|
-
|
|
115
|
+
unless key.nil?
|
|
116
|
+
if block
|
|
117
|
+
yield key, value, cas
|
|
118
|
+
else
|
|
119
|
+
values ||= {}
|
|
120
|
+
values[key] = [value, cas]
|
|
121
|
+
end
|
|
122
|
+
end
|
|
112
123
|
|
|
113
124
|
# Get the next response from the buffer
|
|
114
125
|
status, cas, key, value = response_buffer.process_single_getk_response
|
|
115
126
|
end
|
|
116
127
|
|
|
117
|
-
values
|
|
128
|
+
values || {}
|
|
118
129
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
|
|
119
130
|
@connection_manager.error_on_request!(e)
|
|
120
131
|
end
|
|
132
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
121
133
|
|
|
122
134
|
# Abort current pipelined get. Generally used to signal an external
|
|
123
135
|
# timeout during pipelined get. The underlying socket is
|
|
@@ -141,18 +153,6 @@ module Dalli
|
|
|
141
153
|
!response_buffer.in_progress?
|
|
142
154
|
end
|
|
143
155
|
|
|
144
|
-
def username
|
|
145
|
-
@options[:username] || ENV.fetch('MEMCACHE_USERNAME', nil)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def password
|
|
149
|
-
@options[:password] || ENV.fetch('MEMCACHE_PASSWORD', nil)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def require_auth?
|
|
153
|
-
!username.nil?
|
|
154
|
-
end
|
|
155
|
-
|
|
156
156
|
def quiet?
|
|
157
157
|
Thread.current[::Dalli::QUIET]
|
|
158
158
|
end
|
|
@@ -162,6 +162,16 @@ module Dalli
|
|
|
162
162
|
|
|
163
163
|
private
|
|
164
164
|
|
|
165
|
+
URI_CREDENTIAL_WARNING = 'Dalli 5.0 removed SASL authentication. ' \
|
|
166
|
+
'Credentials in memcached:// URIs are ignored.'
|
|
167
|
+
private_constant :URI_CREDENTIAL_WARNING
|
|
168
|
+
|
|
169
|
+
def warn_uri_credentials(user_creds)
|
|
170
|
+
return if user_creds[:username].nil? && user_creds[:password].nil?
|
|
171
|
+
|
|
172
|
+
Dalli.logger.warn(URI_CREDENTIAL_WARNING)
|
|
173
|
+
end
|
|
174
|
+
|
|
165
175
|
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
|
166
176
|
private_constant :ALLOWED_QUIET_OPS
|
|
167
177
|
|
|
@@ -216,8 +226,7 @@ module Dalli
|
|
|
216
226
|
|
|
217
227
|
def connect
|
|
218
228
|
@connection_manager.establish_connection
|
|
219
|
-
|
|
220
|
-
@version = version # Connect socket if not authed
|
|
229
|
+
@version = version
|
|
221
230
|
up!
|
|
222
231
|
end
|
|
223
232
|
|
|
@@ -156,20 +156,26 @@ module Dalli
|
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
def read(count)
|
|
159
|
-
|
|
159
|
+
# JRuby doesn't support IO#timeout=, so use custom readfull implementation
|
|
160
|
+
# CRuby 3.3+ has IO#timeout= which makes IO#read work with timeouts
|
|
161
|
+
if RUBY_ENGINE == 'jruby'
|
|
162
|
+
@sock.readfull(count)
|
|
163
|
+
else
|
|
164
|
+
@sock.read(count)
|
|
165
|
+
end
|
|
160
166
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
|
|
161
167
|
error_on_request!(e)
|
|
162
168
|
end
|
|
163
169
|
|
|
164
170
|
def write(bytes)
|
|
165
171
|
@sock.write(bytes)
|
|
166
|
-
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS => e
|
|
172
|
+
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, IOError => e
|
|
167
173
|
error_on_request!(e)
|
|
168
174
|
end
|
|
169
175
|
|
|
170
176
|
def flush
|
|
171
177
|
@sock.flush
|
|
172
|
-
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS => e
|
|
178
|
+
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, IOError => e
|
|
173
179
|
error_on_request!(e)
|
|
174
180
|
end
|
|
175
181
|
|