Skip to content

Conversation

@mgravell
Copy link
Collaborator

@mgravell mgravell commented Nov 3, 2025

8.4 adds

  • SET key value ... [IFEQ|IFNE|IFDEQ|IFDNE hash-or-value] modifiers for check-and-set (CAS) semantics
  • DEL key [IFEQ|IFNE|IFDEQ|IFDNE hash-or-value] modifiers for check-and-delete (CAD) semantics
  • DIGEST key for fetching the server-computed hash of a given string value

IFEQ / IFNE are value equality / non-equality checks; the key is interpreted as a string and checked for full equality against the supplied value. IFDEQ/IFDNE are digest (hash) equality / non-equality checks; the key is interpreted as a string and the digest computed, and the digest is checked for equality against the supplied value. In the case of a test failure, the operation is a no-op and a nil reply is provided, the same as for a XX / NX failure.

The key can also be computed local via xxhash3 (64-bit mode).

This PR:

  • adds relevant set/delete overloads that apply this rule, via the new ValueCondition abstraction, which supersedes When (and supports all legacy When use-cases)
  • adds an API to fetch the digest from the server, as an "IFDEQ" ValueCondition (this can also be negated, to create an "IFDNE" ValueCondition)
  • changes LockExtend[Async] and LockRelease[Async] to prefer CAS/CAD over MULTI/EXEC when available
    • note that there is not currently [P]EXPIRE ... IFEQ, so we use SET key value EX ttl IFEQ value - mildly suboptimal, but not enough to be a problem
  • includes unit tests for local hashing and the ValueCondition API surface
  • includes integration tests for the new APIs

Note that System.IO.Hashing is consumed to supply XxHash3 - this is a new package dependency, with ns20, net462, net8 and net9 TFMs.

Note: is is planned to combine this work with MSETEX (also 8.4), and use the new expiration abstraction in the new APIs. This means that the current API with TimeSpan? expiry is not "final" - a combined API will allow all of (nothing), EX, PX, EXAT, PXAT, KEEPTTL and PERSIST to be overlapped on a single API, similar to how ValueCondition works here.

Note: CI may fail horribly right now; the 8.4 RC1 ref'd by CI update has glitches that are already fixed. This will be updated (8.4 RC1 is likely to be re-released; it was effectively an internal preview)

Note that all 8.4 features are going to be marked [Experimental] for now, since 8.4 is an RC. This is [SER002] in the API defs below.

The ValueCondition API is worth explicit mention; summary:

[SER002]StackExchange.Redis.ValueCondition
(type definition; `readonly struct`)  

[SER002]StackExchange.Redis.ValueCondition.AsDigest() -> StackExchange.Redis.ValueCondition
(converts an existing equality condition to a digest condition, by computing the digest; existing digest conditions are unchanged; the equality/non-equality aspect is preserved; "always" etc will throw)

[SER002]StackExchange.Redis.ValueCondition.Value.get -> StackExchange.Redis.RedisValue
(gets the underlying value associated with a condition; in the case of a digest, this is based on a `long`)

[SER002]StackExchange.Redis.ValueCondition.ValueCondition() -> void
(implicit constructor, because struct)

[SER002]static StackExchange.Redis.ValueCondition.Always.get -> StackExchange.Redis.ValueCondition
(static property, semantically identical to `When.Always`)

[SER002]static StackExchange.Redis.ValueCondition.CalculateDigest(System.ReadOnlySpan<byte> value) -> StackExchange.Redis.ValueCondition
(calculate the `IFDEQ` hash for a given payload; utility method)

[SER002]static StackExchange.Redis.ValueCondition.DigestEqual(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
(create an IFDEQ condition from a value, by computing the hash)

[SER002]static StackExchange.Redis.ValueCondition.DigestNotEqual(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
(create an IFDNE condition from a value, by computing the hash)

[SER002]static StackExchange.Redis.ValueCondition.Equal(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
(create an IFEQ condition from a value, retaining the provided value)

[SER002]static StackExchange.Redis.ValueCondition.Exists.get -> StackExchange.Redis.ValueCondition
(static property for an XX condition, semantically identical to `When.Exists`)

[SER002]static StackExchange.Redis.ValueCondition.implicit operator StackExchange.Redis.ValueCondition(StackExchange.Redis.When when) -> StackExchange.Redis.ValueCondition
(allows a `When` value to be used to specify a `ValueCondition`)

[SER002]static StackExchange.Redis.ValueCondition.NotEqual(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
(create an IFNE condition from a value, retaining the provided value)

[SER002]static StackExchange.Redis.ValueCondition.NotExists.get -> StackExchange.Redis.ValueCondition
(static property for an NX condition, semantically identical to `When.NotExists`)

[SER002]static StackExchange.Redis.ValueCondition.operator !(in StackExchange.Redis.ValueCondition value) -> StackExchange.Redis.ValueCondition
(negates a condition; NX<-->XX, IFNE<-->IFEQ, IFDNE<-->IFDEQ; others throw)

[SER002]static StackExchange.Redis.ValueCondition.ParseDigest(System.ReadOnlySpan<byte> digest) -> StackExchange.Redis.ValueCondition
(parses a digest specified as hex bytes into an IFDEQ condition)

[SER002]static StackExchange.Redis.ValueCondition.ParseDigest(System.ReadOnlySpan<char> digest) -> StackExchange.Redis.ValueCondition
(parses a digest specified as hex chars into an IFDEQ condition)

@mgravell mgravell added this to the server 8.4 milestone Nov 3, 2025
@mgravell mgravell self-assigned this Nov 3, 2025
@mgravell mgravell merged commit 94e0090 into main Nov 5, 2025
4 checks passed
@mgravell mgravell deleted the marc/cas-cad branch November 5, 2025 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants