github dahomey-technologies/rustis 0.22.0

4 hours ago

Added

  • ClusterConfig::read_preference routes read-only commands to the replicas.
    A cluster client sent every keyed command to the shard's master, replicas being
    connected only to serve the broadcast policies, so a read-heavy deployment could
    not spread its reads at all. ReadPreference::PreferReplica — spelled
    ?read_preference=prefer_replica in a URI — sends the commands the server reports
    as readonly to the replicas of their shard, in round-robin, and puts those
    connections in READONLY mode. Writes, blocking commands, transactions and
    redirections stay on the master, as does everything when a shard has no reachable
    replica. The default, ReadPreference::Master, keeps the previous behaviour: a
    replica lags behind its master, so reading from one trades consistency for
    throughput and has to be asked for.

  • Config::credentials_provider authenticates with credentials resolved at every
    handshake.
    Config::password is fixed once and for all, so a client of a managed
    Redis whose password is a short-lived token (ElastiCache IAM, Memorystore IAM, Entra
    ID, Vault) reconnects for one token lifetime and then fails authentication for good.
    A CredentialsProvider — implemented for any closure returning
    Result<Credentials> — is asked again on each reconnection. It takes precedence over
    username/password and has no URI representation. SentinelConfig::credentials_provider
    does the same for the Sentinel instances themselves; the two are independent, a
    Sentinel being a different server with its own ACLs.

Changed

  • The manifest declares license = "MIT" instead of license-file. With
    license-file, cargo metadata reports license: null, so every tool that
    keys on the SPDX identifier — license scanners, dependency audits, policy
    checks — sees the crate as carrying no license. LICENSE is the verbatim MIT
    text and is still packaged, so the terms are unchanged.

  • A double no longer decodes as an integer unless the conversion is exact.
    Every integer width converted a Double reply with a saturating cast, so a
    ZSCORE of 3.9 read as i64 gave 3, 1e300 gave i64::MAX and NaN
    gave 0 — a plausible number for a value the server never sent. A double now
    deserializes to an integer only when it is finite, has no fractional part and
    fits the target; anything else fails with ClientError::CannotParseInteger,
    as an out-of-range integer reply already did. Read such a reply as f64 to
    keep the fractional value. A negative reply read as u128 no longer wraps
    either. Both deserializers — RESP and resp::Value — are covered.

  • An array of more than one element no longer decodes as a single integer.
    Every integer width (i8i128, u8u128) unwrapped an array reply to its
    first element and discarded the rest, so a mistyped response shape produced a
    plausible number instead of an error. Only a one-element array still unwraps;
    a longer one fails with ClientError::CannotParseInteger.

  • A connection URI now rejects a query parameter it does not understand. An
    unknown key (?commandtimeout=5000, ?reconnection=constant) and a value that
    does not parse (?command_timeout=5s) were both dropped silently, leaving the
    default in place — a mistyped command_timeout meant no timeout at all. Both
    now fail with ClientError::InvalidUri, whose message names the parameter.
    Code relying on a URI with an unknown parameter being accepted must drop it.
    The documented reconnection parameter never existed and has been removed from
    the list; max_command_attempts, which is parsed, has been added to it.

  • keep_alive now defaults to 30 seconds instead of None. Paired with the
    default command_timeout of 0 (no timeout), the previous default left a
    half-open connection — one silently dropped by a NAT, a firewall or a load
    balancer — detected by nothing: no timeout, no keepalive, no socket error, so
    every awaiting caller parked forever and on_reconnect never fired. Set
    keep_alive to None, or keep_alive=0 in a URL, to restore the old
    behaviour.

  • Dependencies updated to their latest releases, including two major bumps:
    rand 0.9 → 0.10 and atoi 2.0 → 3.1. Both are internal; the public API is
    unchanged. serial_test stays on 3.x — its 4.0 requires Rust 1.93, above the
    crate's 1.88 MSRV, which is itself unchanged.

Fixed

  • A Sentinel client rediscovers its master when the one it holds is demoted.
    A failover turns the master into a replica without closing the connections it
    already serves, so nothing in the transport says the topology moved: the client
    kept writing to a replica, reading stale data and collecting READONLY on every
    write, for as long as the socket held — which is forever with the default absence
    of command_timeout and TCP keepalive. A READONLY received on a Sentinel
    connection now triggers a reconnection, and a Sentinel reconnection polls the
    sentinels for the master again and accepts a node only once ROLE confirms it.
    The caller who issued the refused write still receives the READONLY itself; the
    commands in flight follow the usual reconnection rules. Standalone and cluster
    connections are unchanged — neither has a master to look up.

  • TRYAGAIN and CLUSTERDOWN are now retried instead of reaching the
    caller.
    Both were parsed and never consulted, so a routine resharding — a
    multi-key command whose keys straddle a slot in migration — or a failover
    produced an application-visible error the driver is meant to absorb. A cluster
    command answered TRYAGAIN is now replayed after 25 ms, and one answered
    CLUSTERDOWN after 250 ms and a topology reload, through the same retry path
    as ASK/MOVED and under the same max_command_attempts cap.

  • Dropping a command future does not cancel the command, and this is now
    documented.
    The message is already queued when the future is awaited, so it
    is sent and executed by the server; only the reply is discarded. Every
    tokio::time::timeout, select!, aborted task and command_timeout therefore
    leaves a non-idempotent command applied, which nothing said. A new
    Cancellation and timeouts section in the client module states the contract
    and what to do about it; Client::send and Config::command_timeout point at
    it.

  • The silent nil coercion is now documented as a trap instead of a convenience.
    A nil reply decodes as the neutral value of the response type — 0 for every
    integer width, 0.0 for floats, false for bool, "" for String — so a
    missing counter reads as zero with no error. The behaviour is unchanged, but the
    resp module, StringCommands::get and Client::send now state it and point at
    Option<R>, which is honoured before any conversion and yields None on nil.
    The get example presented the String case as a convenience.

  • The generic-command API documentation no longer teaches a Cluster misroute.
    The MSET/MGET examples in the crate documentation and in Client::send added
    their keys with arg, which does not mark an argument as a key: the command
    carried no slot and was sent to a random node. They now use key and same-slot
    hash tags, and the rule is stated in the cmd, arg and key documentation.

  • keep_alive and no_delay are now applied to TLS connections. Both were set
    only on the plain TCP path: a TLS connection ran with Nagle's algorithm enabled
    (up to 40 ms added to a small command) and without TCP keepalive, so a half-open
    socket was detected by nothing. Both paths now share a single socket-setup step
    applied to the TcpStream before the TLS handshake.

  • Vector-set commands are usable in pipelines and transactions again.
    VectorSetCommands was implemented for &Pipeline and &Transaction instead of
    &mut, so .queue() and .forget() did not resolve on any of them and the whole
    family was unreachable in batch mode.

  • Building without any runtime feature now fails with a message that says so.
    default-features = false without tokio-runtime left every function of the
    runtime layer without a body, and the user got nine cannot find … in this scope
    errors pointing at internal items. A compile_error! now names the missing
    feature and how to enable it.

  • Enabling rustls or native-tls on its own is now a clear compile error. The
    backend-only features gate the TLS configuration types, while the connection code
    reading them lives behind tokio-rustls / tokio-native-tls. Enabled alone they
    produced six cannot find … in this scope errors on internal stream types. A
    compile_error! now names the runtime feature to enable instead.

  • CI builds the feature combinations that were unbuildable. No job compiled the
    crate without a runtime feature, which is why that break went unnoticed. The
    feature matrix gains pool, json, client-cache and the two TLS runtimes each
    on their own, and a new job asserts that the four rejected configurations — no
    runtime, both TLS runtimes, and each backend-only feature alone — fail with their
    own compile_error! rather than with a cascade of internal errors.

  • The actix_long_polling_pubsub and axum_long_polling_pubsub examples compile
    again.
    They passed lpop's count as a usize where the signature takes a u32.

Don't miss a new rustis release

NewReleases is sending notifications on new releases.