Dragonfly 1.26.1 repairs reply framing and list iterators
Dragonfly 1.26.1 corrects bulk-string framing, RESP3 handling and reverse-list iteration. Tiering fixes address pending writes and free-space bookkeeping.
A client parser can hide a server serialization bug by accepting an invalid reply. Dragonfly's bulk-string correction describes a corner case where two zero bytes appeared where the protocol required carriage return and newline. The patch author notes that the Redis Python parser apparently did not validate that ending when the rest of the response was consistent. Dragonfly 1.26.1, published on January 13, includes the repair.
The release also fixes RESP3 handling in combined command execution and an invalid reverse iterator during list removal. Its tiering changes repair expiration cleanup and external-allocation bookkeeping. These are corrections to existing behavior, with no requirement for applications to adopt a new command family.
I find Dragonfly useful where a cache has outgrown one Redis process and using a larger machine remains operationally attractive. That preference depends on familiar wire behavior remaining correct across the engine's internal execution paths. This patch is relevant to that requirement at both the protocol and storage layers.
Bulk-string endings are part of the response
The reply builder can assemble output from copied pieces and references to external strings. Its patch changes how it prepares an I/O vector when a flush changes the available buffer. The destination pointer must agree with the vector describing where the new bytes belong. The repair recalculates that relationship and adds a debug assertion to check it.
This explains why a correct payload length alone did not establish a correct reply. The prefix and contents could remain consistent while the final delimiter bytes were wrong. A client tolerant of those bytes can make an integration appear healthy, even though another conforming parser might reject the response.
The regression test sends arrays containing many strings and parses the complete output. It verifies the resulting response and expected element count across repeated sends. That test targets the batched reply-builder behavior which produced the invalid ending, instead of merely checking a single short string in isolation.
Protocol selection must reach captured replies
The RESP3 repair passes protocol-version information into the capturing reply builder used during command squashing. The change replaces an implicit boolean representation with an explicit response-version enum and supplies that choice where replies are constructed. Nulls, doubles and collection representations depend on that context.
The release notes specifically mention inconsistent ZRANGE behavior with MULTI. The underlying issue is that an internal execution path can capture and later emit a reply without retaining the protocol state used by the ordinary path. Supporting a command and supporting its correct response representation across batching are separate requirements.
A performance optimization which combines work should preserve the connection's negotiated protocol. Otherwise the same logical request can produce a different representation depending on how it reaches execution. Propagating the response version makes that dependency explicit at the reply-construction boundary.
Removing the last list node ends iteration
The release describes its LREM correction as a data-race fix, but the linked patch gives a more specific mechanism. A reverse iterator was not set correctly when deletion removed the final node. The repair sets its current pointer to null when the list has no remaining nodes.
The added test removes entries through a tail iterator and checks that advancing after the last removal returns false. That documents an iterator-lifetime postcondition. The last deleted node's previous-link value cannot describe a valid continuation once the container is empty.
Calling this merely a concurrency improvement would lose that detail. The public correction establishes invalid iterator handling, without requiring an invented account of two threads racing over a particular production workload. The practical result is that list removal can end traversal correctly at the empty-list boundary.
Tiering must cancel work for expired values
Pending-stash cleanup addresses deletion while a value still has a tiering write pending. The deletion path now checks that pending state and cancels the stash. Already external values follow their deletion path instead. A value's expiration must invalidate work which could otherwise complete after its logical deletion.
The external-allocation correction addresses a different mismatch. Large allocations bypass segment bookkeeping and come directly from an extent tree, so freeing them must return the aligned range through the corresponding mechanism. The patch brings allocation and freeing into agreement for that size class.
A related extent-tree repair replaces a retained iterator with a retained key when later tree modifications can invalidate the iterator. Together these changes make tiering's asynchronous work and free-space records follow the lifetime of the underlying values and allocations.
I regard these as substantive compatibility fixes. An application can see ordinary list and string commands while the engine captures replies, moves values or coalesces external free space. Dragonfly 1.26.1 repairs specific invariants in those paths, including the exact bytes emitted to clients and the state retained after deleting the final object.