Skip to content

Bump dalli from 5.0.6 to 5.1.0 - #4750

Open
dependabot[bot] wants to merge 1 commit into
devfrom
dependabot/bundler/dalli-5.1.0
Open

Bump dalli from 5.0.6 to 5.1.0#4750
dependabot[bot] wants to merge 1 commit into
devfrom
dependabot/bundler/dalli-5.1.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 20, 2026

Copy link
Copy Markdown
Contributor

Bumps dalli from 5.0.6 to 5.1.0.

Release notes

Sourced from dalli's releases.

v5.1.0

Features:

  • Add opaque routing tokens: :p_token and :l_token request options (#1147, #1154)

    • get, gat, get_cas, get_with_metadata, fetch_with_lock, set/add/replace/set_cas/replace_cas, append/prepend, incr/decr, cas/cas!, delete/delete_cas, and the bulk operations (get_multi, get_multi_cas, get_multi_with_metadata, set_multi, delete_multi) all accept per-request :p_token/:l_token options, appended to the wire protocol as P<token>/L<token> -- applied to every key on the bulk methods
    • memcached itself ignores these tokens; per the meta protocol spec they exist as hints for a proxy or router sitting between the client and memcached
    • CRLF and NUL bytes raise ArgumentError before the request reaches the socket, both in Dalli::Client and in RequestFormatter, so a bad token can't be used for wire-protocol injection and can't close the connection out from under the caller the way a formatter-only check would
    • The bulk methods and delete/delete_cas were deferred out of #1147 to avoid racing other in-flight PRs touching the same method signatures; #1154 completes them, including both the single-server fast path and the multi-server pipelined path for each
    • Extracted from #1130; thanks to Nick Herson for the original idea and Jianbin Chen for porting it forward
  • Support tombstone (mark-stale) deletes on delete, delete_cas, and delete_multi (#1145, #1153)

    • :invalidate marks the item stale instead of removing it, so #get_with_metadata / #get_multi_with_metadata report stale: true and a reader can tell "another process is repopulating this" apart from "this was never here" -- a tombstoned key is not a miss
    • :tombstone_ttl controls how long the stale marker lives; requires :invalidate, since memcached only honors the TTL on a delete when it accompanies the invalidate flag
    • :drop_value removes the item's value but leaves the item; on its own it is not a tombstone -- reads are an ordinary hit with an empty value
    • delete_multi applies the same options to every key in the batch, on both the single-server and pipelined paths; its return value keeps counting keys the server found and acted on, so under :invalidate it reports how many keys were tombstoned rather than removed
    • Extracted from #1130; thanks to Jianbin Chen for this contribution
  • Add :miss and :return_ttl_remaining to get_with_metadata (#1143)

    • :miss is now always present in the returned Hash, distinguishing a true miss from a stored nil under cache_nils or a tombstoned, stale hit -- neither of which a nil :value alone can tell apart
    • :return_ttl_remaining exposes the meta protocol's t flag as :ttl_remaining (seconds remaining, or -1 for an item with no expiry), following the same opt-in shape as :return_hit_status / :return_last_access
    • Extracted from #1130; thanks to Jianbin Chen for this contribution
  • Add get_multi_with_metadata for stale-aware bulk reads (#1144)

    • Returns { key => { value:, cas:, stale:, miss: } } for the keys that were found; genuine misses are omitted, matching get_multi / get_multi_cas -- a tombstoned item is a hit at the protocol level, so it is still returned, with stale: true
    • Routes to the same single-server fast path / pipelined-getter split as get_multi
    • Extracted from #1130; thanks to Jianbin Chen for this contribution

Other changes:

  • Raise the documented minimum supported memcached version to 1.6.27 (#1140)
    • Groundwork for the features above: drop_value tombstone deletes require 1.6.27, the highest floor of anything landing from #1130
    • Not enforced at runtime -- MIN_SUPPORTED_MEMCACHED_VERSION only gates the test harness and the README's support statement, so this changes no running client's behavior

Bug Fixes:

  • Retry a transient network error during a liveness check instead of treating it as terminal (#1150)

    • Dalli::Protocol::Base#alive? caught any NetworkError -- including RetryableNetworkError, a subclass -- and unconditionally converted it to false, with no retry. This didn't match the retry-then-raise contract every other network-facing path in Dalli follows: a single transient connection hiccup during the liveness check itself (as opposed to an actual request) permanently reported a healthy server as down for that check
    • Now retries once on RetryableNetworkError, letting error_on_request!'s own fail-count threshold decide when to actually give up (the same mechanism the rest of the codebase relies on): it keeps retrying until socket_max_failures is reached, then raises a terminal NetworkError, which is still converted to false as before
    • Behavior change: a server that was previously marked "down" (engaging the down_retry_delay cooldown) only via an actual request could now also reach that state via a liveness check (alive?, and anything that calls it -- Dalli::Client#stats, #reset_stats, and Ring's own server selection) exhausting its retries. Previously, a solitary transient failure during a liveness check was silently forgotten rather than tracked, so the cooldown was inconsistently applied depending on which code path first observed the failure
    • Likely a contributing cause of the same intermittently failing get_multi failover integration test noted in #1149: that fix addressed the send/receive phase, but the liveness-check retry it introduced can itself force a fresh connect() mid-retry, giving this separate, pre-existing gap in alive? more chances to fire
    • Also fixed a test (test_ring.rb, "detect when a dead server is up again") that had been unknowingly relying on the old behavior: it never engaged the down_retry_delay cooldown from a single transient failure, so its 0.5s delay never actually gated anything. Updated to use a 0s delay, since the test's intent is to verify reconnection is detected, not to test cooldown timing
  • Base64-encode keys containing control characters, not just NUL (#1148)

    • KeyRegularizer.required? decided whether a key needed base64 encoding using /\s/, which matches most whitespace but none of the C0 control range (0x00-0x1F) or DEL (0x7F) -- a key that was otherwise ASCII-only and contained no whitespace (e.g. "foo\x00bar" or a key with an embedded ESC byte) went out on the wire unencoded
    • Not a command-injection risk: the text protocol splits commands on CRLF, not other control bytes. The risk is key confusion -- anything downstream that treats one of these bytes specially (a C string terminating at NUL, a terminal or log line interpreting an escape byte) could silently act on a different key than Dalli believes it sent
    • A raw control byte in a key was never protocol-compliant in the first place: memcached's own spec (protocol.txt) states a key "must not include control characters or whitespace." The only sanctioned way to carry such content in a key is the meta protocol's base64 (b flag) path -- the one whitespace and non-ASCII keys already used, and the one these keys now use too. The check is now /[\p{Cntrl}\s]/, matching that rule directly rather than special-casing NUL
    • Behavior change: a key containing a control character now produces different bytes on the wire (base64-encoded, per the meta protocol's b flag) than before. Existing cache entries stored under the old, unencoded form of such a key will read as a miss once every reader has upgraded. During a rolling deploy, old and new Dalli versions disagree about which physical key such a logical key maps to -- not just a one-time cutover, but ongoing inconsistency between the old-version and new-version server pools for the duration of the rollout. Harmless for an ordinary cached value (worst case, extra cache misses); worth accounting for if such a key ever backs something stateful, like a lock or counter. Expected to be rare in practice: embedding a raw control byte in a cache key is unusual, and doing so was already outside what the protocol permits
    • Found while auditing request_formatter.rb during the routing-token work in #1130 / #1147; unrelated to that change and predates it
  • Retry transient network errors in get_multi, set_multi and delete_multi instead of silently swallowing them (#1149)

... (truncated)

Changelog

Sourced from dalli's changelog.

5.1.0

Features:

  • Add opaque routing tokens: :p_token and :l_token request options (#1147, #1154)

    • get, gat, get_cas, get_with_metadata, fetch_with_lock, set/add/replace/set_cas/replace_cas, append/prepend, incr/decr, cas/cas!, delete/delete_cas, and the bulk operations (get_multi, get_multi_cas, get_multi_with_metadata, set_multi, delete_multi) all accept per-request :p_token/:l_token options, appended to the wire protocol as P<token>/L<token> -- applied to every key on the bulk methods
    • memcached itself ignores these tokens; per the meta protocol spec they exist as hints for a proxy or router sitting between the client and memcached
    • CRLF and NUL bytes raise ArgumentError before the request reaches the socket, both in Dalli::Client and in RequestFormatter, so a bad token can't be used for wire-protocol injection and can't close the connection out from under the caller the way a formatter-only check would
    • The bulk methods and delete/delete_cas were deferred out of #1147 to avoid racing other in-flight PRs touching the same method signatures; #1154 completes them, including both the single-server fast path and the multi-server pipelined path for each
    • Extracted from #1130; thanks to Nick Herson for the original idea and Jianbin Chen for porting it forward
  • Support tombstone (mark-stale) deletes on delete, delete_cas, and delete_multi (#1145, #1153)

    • :invalidate marks the item stale instead of removing it, so #get_with_metadata / #get_multi_with_metadata report stale: true and a reader can tell "another process is repopulating this" apart from "this was never here" -- a tombstoned key is not a miss
    • :tombstone_ttl controls how long the stale marker lives; requires :invalidate, since memcached only honors the TTL on a delete when it accompanies the invalidate flag
    • :drop_value removes the item's value but leaves the item; on its own it is not a tombstone -- reads are an ordinary hit with an empty value
    • delete_multi applies the same options to every key in the batch, on both the single-server and pipelined paths; its return value keeps counting keys the server found and acted on, so under :invalidate it reports how many keys were tombstoned rather than removed
    • Extracted from #1130; thanks to Jianbin Chen for this contribution
  • Add :miss and :return_ttl_remaining to get_with_metadata (#1143)

    • :miss is now always present in the returned Hash, distinguishing a true miss from a stored nil under cache_nils or a tombstoned, stale hit -- neither of which a nil :value alone can tell apart
    • :return_ttl_remaining exposes the meta protocol's t flag as :ttl_remaining (seconds remaining, or -1 for an item with no expiry), following the same opt-in shape as :return_hit_status / :return_last_access
    • Extracted from #1130; thanks to Jianbin Chen for this contribution
  • Add get_multi_with_metadata for stale-aware bulk reads (#1144)

    • Returns { key => { value:, cas:, stale:, miss: } } for the keys that were found; genuine misses are omitted, matching get_multi / get_multi_cas -- a tombstoned item is a hit at the protocol level, so it is still returned, with stale: true
    • Routes to the same single-server fast path / pipelined-getter split as get_multi
    • Extracted from #1130; thanks to Jianbin Chen for this contribution

Other changes:

  • Raise the documented minimum supported memcached version to 1.6.27 (#1140)
    • Groundwork for the features above: drop_value tombstone deletes require 1.6.27, the highest floor of anything landing from #1130
    • Not enforced at runtime -- MIN_SUPPORTED_MEMCACHED_VERSION only gates the test harness and the README's support statement, so this changes no running client's behavior

Bug Fixes:

  • Retry a transient network error during a liveness check instead of treating it as terminal (#1150)

    • Dalli::Protocol::Base#alive? caught any NetworkError -- including RetryableNetworkError, a subclass -- and unconditionally converted it to false, with no retry. This didn't match the retry-then-raise contract every other network-facing path in Dalli follows: a single transient connection hiccup during the liveness check itself (as opposed to an actual request) permanently reported a healthy server as down for that check
    • Now retries once on RetryableNetworkError, letting error_on_request!'s own fail-count threshold decide when to actually give up (the same mechanism the rest of the codebase relies on): it keeps retrying until socket_max_failures is reached, then raises a terminal NetworkError, which is still converted to false as before
    • Behavior change: a server that was previously marked "down" (engaging the down_retry_delay cooldown) only via an actual request could now also reach that state via a liveness check (alive?, and anything that calls it -- Dalli::Client#stats, #reset_stats, and Ring's own server selection) exhausting its retries. Previously, a solitary transient failure during a liveness check was silently forgotten rather than tracked, so the cooldown was inconsistently applied depending on which code path first observed the failure
    • Likely a contributing cause of the same intermittently failing get_multi failover integration test noted in #1149: that fix addressed the send/receive phase, but the liveness-check retry it introduced can itself force a fresh connect() mid-retry, giving this separate, pre-existing gap in alive? more chances to fire
    • Also fixed a test (test_ring.rb, "detect when a dead server is up again") that had been unknowingly relying on the old behavior: it never engaged the down_retry_delay cooldown from a single transient failure, so its 0.5s delay never actually gated anything. Updated to use a 0s delay, since the test's intent is to verify reconnection is detected, not to test cooldown timing
  • Base64-encode keys containing control characters, not just NUL (#1148)

    • KeyRegularizer.required? decided whether a key needed base64 encoding using /\s/, which matches most whitespace but none of the C0 control range (0x00-0x1F) or DEL (0x7F) -- a key that was otherwise ASCII-only and contained no whitespace (e.g. "foo\x00bar" or a key with an embedded ESC byte) went out on the wire unencoded
    • Not a command-injection risk: the text protocol splits commands on CRLF, not other control bytes. The risk is key confusion -- anything downstream that treats one of these bytes specially (a C string terminating at NUL, a terminal or log line interpreting an escape byte) could silently act on a different key than Dalli believes it sent
    • A raw control byte in a key was never protocol-compliant in the first place: memcached's own spec (protocol.txt) states a key "must not include control characters or whitespace." The only sanctioned way to carry such content in a key is the meta protocol's base64 (b flag) path -- the one whitespace and non-ASCII keys already used, and the one these keys now use too. The check is now /[\p{Cntrl}\s]/, matching that rule directly rather than special-casing NUL
    • Behavior change: a key containing a control character now produces different bytes on the wire (base64-encoded, per the meta protocol's b flag) than before. Existing cache entries stored under the old, unencoded form of such a key will read as a miss once every reader has upgraded. During a rolling deploy, old and new Dalli versions disagree about which physical key such a logical key maps to -- not just a one-time cutover, but ongoing inconsistency between the old-version and new-version server pools for the duration of the rollout. Harmless for an ordinary cached value (worst case, extra cache misses); worth accounting for if such a key ever backs something stateful, like a lock or counter. Expected to be rare in practice: embedding a raw control byte in a cache key is unusual, and doing so was already outside what the protocol permits
    • Found while auditing request_formatter.rb during the routing-token work in #1130 / #1147; unrelated to that change and predates it

... (truncated)

Commits
  • fc1b590 Merge pull request #1157 from petergoldstein/release/5.1.0
  • c79b4fb Prepare 5.1.0 release
  • aa545f8 Merge pull request #1154 from petergoldstein/feat/routing-tokens-bulk
  • fb25aa3 Merge remote-tracking branch 'origin/main' into feat/routing-tokens-bulk
  • 10b3561 Merge pull request #1150 from petergoldstein/fix/alive-retryable-network-error
  • ec71a9b Merge remote-tracking branch 'origin/main' into fix/alive-retryable-network-e...
  • 49d560b Merge remote-tracking branch 'origin/main' into feat/routing-tokens-bulk
  • 94613b9 Merge pull request #1155 from petergoldstein/fix/key-regularizer-nul-bytes
  • 10aa02e Fix comment accuracy per Copilot review
  • 917a9ff Broaden the fix to all control characters, not just NUL
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps [dalli](https://github.com/petergoldstein/dalli) from 5.0.6 to 5.1.0.
- [Release notes](https://github.com/petergoldstein/dalli/releases)
- [Changelog](https://github.com/petergoldstein/dalli/blob/main/CHANGELOG.md)
- [Commits](petergoldstein/dalli@v5.0.6...v5.1.0)

---
updated-dependencies:
- dependency-name: dalli
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies ruby Pull requests that update Ruby code labels Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies ruby Pull requests that update Ruby code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants