Dragonfly fixes blocked stream reads with replication
Dragonfly 1.37.2 fixes a stream wakeup crash, corrects negative JSON arithmetic and limits existing SETBIT updates to the affected byte.
Dragonfly 1.37.2 fixes a crash when a blocking XREAD wakes up while replication is active. The patch release also corrects negative integer results in JSON.NUMINCRBY and changes SETBIT to avoid retrieving a complete existing value for an in-range update. Each change concerns a small operation whose implementation reaches beyond the apparent size of the command.
I favour Dragonfly when one larger machine can keep a cache operationally simpler than a cluster. That preference does not reduce the importance of replication behaviour. A single-process scaling decision still needs confidence in the combinations of commands and server roles the application uses. The stream fix is a useful example because testing ordinary reads independently from replication would miss the failing combination.
A plain read entered consumer-group bookkeeping
The stream fix identifies JournalXReadGroupIfNeeded as the failing function. A plain XREAD has no consumer-group pointer. The relevant sitem.group field is null, while XREADGROUP supplies the group state that this bookkeeping expects. The blocking-read callback nevertheless called the helper without first establishing that the request was a group read.
With no replication journal, the existing helper returned before reaching the invalid access. With a replica connected, a journal existed and execution continued. The helper then dereferenced sitem.group to obtain entries_read. The patch description reports a segmentation fault when the blocked request resumed. Replication did not make the pointer valid, but the old guard effectively treated journal availability as sufficient permission to use it.
The new early-return condition checks for the absence of a journal, a request that is not a group read, or an empty result set. Any of those cases exits the helper. This makes the group's existence a property of the operation being processed, rather than an accidental assumption reached after a replication check.
The remaining code shows why the helper exists. For eligible group reads, it records claim-related operations when acknowledgement tracking requires them and records an XGROUP SETID operation with the final entry identifier and ENTRIESREAD value. Those are consumer-group effects. A plain blocked read does not acquire consumer-group state merely because it waited for a new stream entry.
The regression test includes the wakeup
The added integration test starts a primary and a replica, attaches the replica, and waits for replication readiness. It creates an initial stream entry before issuing the blocking read. The command waits for an entry after the stream's current end.
XREAD BLOCK 0 STREAMS mystream $
A second client operation appends another entry and releases the waiting read. The test then checks that the read returns a result and that replication finishes. The important execution path includes both waiting and resumption. Sending a read that can return immediately would not be an equivalent regression case for the reported crash.
I would use that sequence when checking an application's Streams integration against this patch. The test should preserve the distinction between XREAD and XREADGROUP, since conflating them caused the invalid assumption. I would also preserve the connected replica in the fixture. Removing replication would remove the condition that allowed the old helper to proceed.
This is a targeted reason to prioritise the patch for deployments using blocked stream reads and replication together. The source does not establish that every Streams command or every replication operation is affected. I would describe the impact through the documented trigger and use application tests to establish whether that trigger exists in a particular deployment.
JSON arithmetic needs signed and unsigned results
The JSON arithmetic change addresses conversion of an arithmetic result to uint64_t. The reported example begins with one and increments by negative two. It should produce negative one, but the old implementation returned 18446744073709551615. That is a correctness problem for an application expecting a decrement to cross zero.
JSON.SET json . {"a":1}
JSON.NUMINCRBY json $.a -2
The regression test expects [-1] from the second command. The final patch does more than unconditionally replace an unsigned cast with a signed cast. Floating-point operands retain the floating-point result path. For integer results, nonnegative values use unsigned storage. Negative values use signed storage when the result is at least the minimum signed 64-bit value. Results below that boundary set the overflow flag.
That distinction preserves positive values beyond the signed maximum. A separate test begins at 9223372036854775808 and increments by 2048. The test comment explains that double spacing at this magnitude is 2048, which makes the chosen increment exactly representable. Another test exercises a result below the signed minimum and expects an error. Both boundaries are relevant to judging the correction.
I would avoid treating this as a general promise of arbitrary-precision arithmetic. The code computes through a double result and then chooses a storage representation. The tests deliberately account for that representation. An application that relies on integer arithmetic near these boundaries needs its own explicit expectations, including how it handles errors and which numeric ranges it permits.
SETBIT changes the amount of data touched
The SETBIT patch replaces complete-value retrieval with access to the affected byte when the offset is already within an existing value. It reads that byte, determines the previous bit, and writes the byte only if the requested bit differs. The code still returns the previous bit value.
Creating a new key and extending an existing string remain separate paths. An out-of-range offset still requires obtaining and resizing the existing string, filling the extension with zero bytes and committing the longer value. The added regression test extends a three-byte string by setting bit offset 100, then checks the resulting length of thirteen bytes and the zeros in the intervening region.
The performance implication is specific to the work avoided for existing storage. I would compare in-range bitmap updates separately from growth, since the patch takes different paths for them. The release supplies no universal throughput number for this change. For an upgrade check, I would verify the old-bit return value and extension behaviour alongside any timing comparison, with blocked stream wakeups under replication covered by a separate regression fixture.