Redis 5.0.7 fixes expiration during repeated key lookups
Redis 5.0.7 uses a fixed expiration reference during command execution. The release also corrects propagation from module callbacks and thread-safe contexts.
Time can invalidate an object even when no other client changes it. A Redis command may look up the same expiring key more than once. If each lookup uses a fresh wall-clock reading, the first can return an object and the second can expire and free it. Code retaining the first reference then holds something that no longer exists.
Redis 5.0.7 addresses that class of failure by using a fixed expiration reference during command execution. The 19 November release notes describe a rare potential crash in commands processing multiple keys. They rate the update high urgency while explicitly saying none of the included bugs is very critical. The reason to examine the change is the lifetime rule it establishes around repeated lookups.
I have used Redis since its release and value the predictability of its command model. This fix shows that predictability needs an implementation rule for time as well as for client interleaving. A command can be isolated from another client's mutation and still encounter its own expiration decision twice.
A command needs one expiration reference
The 5.0.7 expiration implementation distinguishes several time contexts in keyIsExpired. Lua execution uses the script's start time. A command executing inside the fixed-time region uses the cached server time. Outside those contexts, expiration can use a fresh time reading.
The source comment names RPOPLPUSH as an example of an operation that can reopen a key. The important property is the repeated access, not an assumption that every multi-key command follows the same internal path. A command that already holds an object reference must not have a subsequent lookup free that object solely because a deadline passed between the two accesses.
The server command path updates cached time before invoking the command procedure. It tracks entry into and exit from the region where expiration uses that reference. That establishes a consistent basis for expiration decisions during the command's execution.
The change does not remove expiration deadlines. A later command receives a later reference time and can find the key expired. The fixed reference defines how one execution sees the passage of time. It prevents a deadline crossing inside that execution from invalidating an object already opened there.
This is a lifetime correction with a temporal cause. No second client needs to remove the key. A source review focused only on concurrent mutation could therefore miss the problem. Expiration is itself an action capable of freeing data, and the command's own later lookup can trigger it.
Unblocking has the same lifetime requirement
The blocked-client handling code establishes a fixed-time expiration region while serving clients waiting on ready keys. Its comment identifies BRPOPLPUSH as a case with repeated lookups. That extends the same rule beyond a command entering through the ordinary direct-dispatch path.
This detail matters because blocking changes when the operation completes. The work that serves an unblocked client runs after the original command has waited. Reusing only the ordinary command wrapper's protection would not necessarily cover that later work. The ready-key path needs its own bounded execution region.
The implementation updates cached time for that region as well. It therefore uses a current reference for the work being performed, while keeping that reference stable during the sequence of relevant accesses. A fixed reference need not be stale indefinitely to satisfy the lifetime requirement.
I find this a useful way to evaluate consistency claims in event-driven code. The relevant unit is the operation currently holding references. Its entry and exit points may differ between direct commands and deferred completion, so the source has to establish the rule in both places.
Module propagation depends on context lifetime too
The release also corrects replication from module commands and callbacks. The module implementation at the release tag distinguishes an ordinary module command context from callback contexts. Cleanup now invokes propagation handling when freeing a context.
An ordinary command has the core command machinery available to finish queued propagation. A callback does not necessarily run inside that same wrapper. The updated code can emit its accumulated operations directly for a callback context and restore the previously saved operation array afterward. That prevents propagation handling from depending on an outer command path that may not exist.
Thread-safe contexts receive different treatment in RM_Replicate. The source explains that they may live for an unspecified duration and can be locked and unlocked repeatedly. Accumulating a transaction until context destruction would give that lifetime inappropriate control over when replication occurs.
For those contexts, the updated implementation propagates the specified command immediately instead of collecting it into the ordinary callback transaction wrapper. This concerns emission into the AOF and replication stream. It does not imply a new acknowledgement guarantee from replicas or a synchronous storage guarantee for every module call.
The patch has several distinct scopes
The notes also list stream behavior fixes and a noncritical AOF fsync bookkeeping issue. These entries should remain separate from the expiration crash and module propagation changes. The release's high urgency applies to the maintenance update as assessed by its authors, not a claim that every listed correction causes the same severity of failure.
The expiration correction has a specific general lesson for interpreting this version's behavior. During a protected execution region, repeated key lookups compare against one reference time. After that region ends, later work can use a new time. This keeps the key's deadline meaningful while protecting references already obtained during an operation.
For modules, the corresponding boundary is the execution context that owns propagation work. A callback needs to finish its queued operations without assuming an ordinary command wrapper, while a potentially long-lived thread-safe context must emit commands without waiting for eventual destruction. Redis 5.0.7 records those distinct lifetimes in the implementation rather than treating all module entry points as interchangeable.