October 7, 2026·4 min read·engineeringdragonflycorrectnesspatternsreplication

Dragonfly removes recursion from glob matching

Dragonfly 2.0.2 replaces recursive glob matching to prevent fiber-stack crashes. The patch retains matching semantics and distinguishes disabled cluster operations from pending migration configuration.

Dragonfly's old glob matcher rejected recursive nesting beyond one thousand calls, but that limit did not protect its smaller fiber stacks from overflowing first. Version 2.0.2 replaces recursion with an iterative matcher. The October 7 patch release lists affected uses including key scans, ACL key patterns, pattern subscriptions and configuration lookup. A utility that looks like a string-processing detail therefore participates in several server behaviours.

I favour Dragonfly's ability to scale one process across a larger machine. Shared utility code deserves particular attention in that arrangement because many commands depend on it. The interesting part of this fix is the replacement of a nesting limit with an algorithm that does not use recursive calls. Deep patterns can retain their intended meaning without consuming one stack frame per recursive search.

A star previously created another call

The matcher patch removes stringmatchlen_impl, adapted from Valkey. Its star-handling path tried to match the remaining pattern against the remaining input by calling itself. When that attempt failed, it advanced through the input and tried again. Successive wildcard structure could therefore consume stack through nested calls before the matcher completed its search.

The new MatchGlobImpl keeps the active pattern and input as string views. It also remembers the pattern immediately after the most recent star and the input position where that star currently stops absorbing bytes. On mismatch, the algorithm advances that saved input position and retries the saved pattern. The retry state lives in local variables without a recursive call chain.

Consecutive stars collapse through SkipStars. If skipping them reaches the end of a pattern while input remains, the match succeeds. When a following token fails, only the most recent star is retried. The implementation can search directly for a following literal byte in the case-sensitive path, skipping input positions that cannot start the required continuation.

There is also a fast path for leading literal bytes. Those bytes have to match at their current positions before any wildcard logic applies. A pattern beginning with an incompatible literal can fail immediately. The patch exposes the stateless MatchGlob entry point for tests and benchmarks, with a header comment describing iterative matching without allocation.

Removing recursion must preserve the dialect

The replacement includes an explicit description of the supported syntax. A question mark consumes exactly one byte. A character class consumes one byte from its set, with a leading caret negating the class. Escapes make the following byte literal, and a trailing backslash remains literal. Matching here operates on bytes, which is a more precise description than treating every token as a Unicode character.

Some compatibility cases are surprising. The source explicitly retains the rule that an empty string matches only an empty pattern, so a star does not match an empty string in this implementation. An unclosed character class runs to the pattern's end. Escaped members within a class compare case-sensitively even when the surrounding match is case-insensitive.

Range handling orders reversed bounds before applying case folding. The tests include [a-Z], whose results depend on that ordering rule. The new code also compares range values as unsigned bytes, avoiding a dependence on whether the platform's plain char is signed for bytes at or above 0x80. Preserving the intended dialect requires these decisions to be explicit alongside the main wildcard algorithm.

The backtracking tests exercise several small examples. *ab matches abab, while *ab*ab does not match aab. Escaped literals immediately after a star also get dedicated cases. These examples probe where the saved retry position moves, rather than merely checking that a wildcard can match an ordinary string.

Regression coverage checks both matching and survival

A unit test repeats *? two thousand times. That pattern requires at least two thousand input bytes, so equal-length and longer inputs match while a shorter input does not. The test checks that removing recursion preserves a valid deep pattern instead of rejecting it at a fixed nesting threshold.

The integration test uses a key containing fifty thousand a bytes and a pattern containing fifty thousand repetitions of *?. It checks that KEYS returns the key and that scanning through SCAN MATCH also finds it. A final PING confirms the server remains responsive after those operations. These are upstream regression tests, not a workload I ran against a deployment.

The scan assertion compares a set of returned keys because scanning may repeat entries. It also iterates to completion rather than assuming one page establishes the result. Those details keep the test focused on matcher correctness and process survival without confusing cursor iteration behaviour with wildcard semantics.

The release says the iterative matcher is also faster, but supplies no universal speedup figure. The implementation provides concrete reasons some paths avoid work, including literal-prefix matching and direct literal search after a star. Stack safety follows from eliminating recursive calls in this path. A claim about throughput for an application still requires a workload-specific comparison.

Migration errors distinguish mode from configuration

The same release fixes a DFLYMIGRATE crash outside cluster mode. The migration patch adds an early mode check. Disabled and emulated modes return Cluster is disabled. Use --cluster_mode=yes to enable. before entering migration handling. Those modes do not have the cluster configuration needed by the migration protocol.

Real cluster mode with configuration still propagating is a different state. The source comments explain that the migration source polls while allowing an UNKNOWN_MIGRATION grace period. The patch preserves that reply for initialization and acknowledgement when no configuration has arrived, instead of treating every absent configuration as disabled clustering. Its tests cover both mode rejection and the real-mode pending-configuration replies.

That distinction is a useful complement to the glob change. Both fixes remove crashes, but each preserves meaningful existing behaviour. Deep valid patterns still receive a matching result, while a migration target awaiting configuration keeps the reply its source understands. My interest in the patch is that the implementation addresses failure mechanisms without discarding those specific command contracts.