Skip to content

fix(command): SCAN stable-key guarantee — hash-ordered cursor replaces the positional re-sort (#368) - #379

Merged
pilotspacex-byte merged 1 commit into
mainfrom
fix/scan-hash-cursor
Jul 17, 2026
Merged

fix(command): SCAN stable-key guarantee — hash-ordered cursor replaces the positional re-sort (#368)#379
pilotspacex-byte merged 1 commit into
mainfrom
fix/scan-hash-cursor

Conversation

@pilotspacex-byte

@pilotspacex-byte pilotspacex-byte commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the semantics half of #368: SCAN's cursor was a positional index into a per-page re-collected+re-sorted snapshot, so churn between pages could skip keys that existed for the entire scan — violating Redis's documented contract on the exact backup/migration path #364/#367 serve.

Pages now iterate in stable 48-bit key-hash order; the cursor is a position in hash space. A key's hash never changes, so churn cannot displace it — a key present throughout the scan is returned exactly once.

Key properties:

  • Client + multi-shard compatible: cursors stay numeric and ≤48 bits, so the composite cursor (16-bit shard | 48-bit per-shard in coordinate_scan) and the admin scan_fanout opaque-cursor plane work unchanged.
  • Cheaper pages: one walk over both planes with a bounded COUNT-min heap — no O(n log n) sort, no second per-key lookup pass, no full-keyspace lazy-expiry probe per page (new Database::iter_live_keys judges liveness during iteration). Reclamation is active-expiry's job (100ms cadence), not SCAN's.
  • Collision-safe paging: a full page defers a possibly-incomplete trailing equal-hash group (COUNT is a hint — Redis parity); the pathological all-one-hash page emits the whole group and steps past it.

Follow-up left open in #368: true O(COUNT) pages via a DashTable bucket-order cursor + shared cold live-count primitive for logical_len.

Testing

  • Red/green churn test: deletes already-returned keys and inserts new ones between every page; asserts all 100 stable keys returned. The old positional cursor fails this.
  • Duplicate-free exact full drain; MATCH paging termination; legacy exact-COUNT test corrected to the COUNT-is-a-hint contract.
  • scan_offload_visibility (cold plane + restart) green; 4381 lib tests; fmt + clippy both feature sets.
  • Multi-shard e2e (Linux VM, 4 shards, 500 keys): redis-cli --scan drains 500/500 unique; --pattern "key:1*" returns exactly 111.

refs #368

Summary by CodeRabbit

  • Bug Fixes

    • Improved SCAN reliability during key insertions, deletions, and expiration activity.
    • Prevented keys from being skipped or returned more than once across scan pages.
    • Kept scan cursors numeric and client-compatible.
    • Aligned read-only and standard scan behavior.
    • Updated paging behavior to consistently honor COUNT and filtering options.
  • Documentation

    • Added details about the SCAN correctness improvements to the unreleased changelog.

…s the positional re-sort (#368)

SCAN's cursor was a positional index into a keyspace snapshot that every
page re-collected and re-sorted: any insert/delete (or cold-plane spill/
promotion/TTL churn) between pages shifted positions, so a key present
for the ENTIRE scan could be skipped — a violation of Redis's documented
contract, hitting exactly the backup/migration-via-SCAN use case #364/
#367 exist to serve. On top of that, every page paid collect + sort
O(hot+cold log) plus a second get_if_alive() lookup pass over every key,
and the write path additionally probed exists() on the full keyspace per
page.

SCAN pages now iterate in (hash48(key), key) order and the cursor is a
position in hash space (`scan_core` in src/command/key.rs):

- Guarantee: a key's hash never changes, so churn cannot displace another
  key's position. A key present throughout the scan is returned exactly
  once. New red/green unit test deletes already-returned keys and inserts
  new ones between every page and asserts all 100 stable keys are
  returned (the old positional cursor fails this).
- Cursor stays NUMERIC and 48-bit — the multi-shard composite cursor
  (upper 16 bits shard / lower 48 per-shard, coordinate_scan) and the
  admin scan_fanout opaque-cursor plane both work unchanged, and integer-
  parsing clients (redis-rs, redis-py, redis-cli --scan) stay compatible.
- Cost per page: ONE walk over both planes with a bounded COUNT-min
  selection heap (BinaryHeap, capacity COUNT+1) — no full sort, no second
  lookup pass, no full-keyspace lazy-expiry probe. New
  Database::iter_live_keys judges liveness from the entry during
  iteration instead of a per-key hash lookup.
- Hash-collision safety: a full page never advances the cursor past a
  hash whose key group might be partially selected — the trailing
  equal-hash group defers to the next page (COUNT is a hint, Redis
  parity); the all-one-hash pathological page emits the entire group and
  steps past it.
- Hash: FNV-1a 64 truncated to 48 bits, fixed seed — deliberately NOT
  the tables' randomized hashers (the cursor must be stable across pages).

The old exists()-per-key reclamation side effect on the SCAN write path
is gone by design: active expiry (100ms cadence) owns reclamation; SCAN
no longer walks the whole keyspace to do it as a side job.

Follow-up (tracked in #368, open): O(COUNT) pages via a DashTable
bucket-order cursor; shared cold-only live-count primitive for
logical_len (#355/#362 class).

Gates:
- New unit tests: churn stable-key guarantee (red vs old impl),
  duplicate-free exact full drain, MATCH paging termination; legacy
  exact-COUNT assertion corrected to the documented COUNT-is-a-hint
  contract. 4381 lib tests green.
- scan_offload_visibility (cold plane + restart) green.
- Multi-shard e2e (Linux VM, 4 shards, 500 keys): redis-cli --scan
  drains 500/500 unique keys; --pattern "key:1*" returns exactly 111.
- fmt, clippy (default + tokio,jemalloc) green.

refs #368
author: Tin Dang <tindang.ht97@gmail.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SCAN now uses a numeric cursor representing stable 48-bit key-hash space. Shared paging logic scans live hot and cold-only keys with bounded selection, applies filters, and is reused by read-only scans. Tests cover churn, draining, duplicates, filtering, and COUNT behavior.

Changes

SCAN cursor correctness

Layer / File(s) Summary
Live key selection and hash paging
src/storage/db.rs, src/command/key.rs
Adds non-expired hot-key iteration and replaces positional SCAN paging with bounded hash-ordered selection across hot and cold-only planes, including TYPE and MATCH filtering.
Read-only integration and regression coverage
src/command/key.rs, CHANGELOG.md
Routes read-only SCAN through the shared cursor logic and adds coverage and release notes for churn safety, duplicate-free draining, filtering, and revised COUNT paging.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SCAN
  participant Database
  Client->>SCAN: SCAN cursor with COUNT, TYPE, MATCH
  SCAN->>Database: Read live hot keys and cold-only keys
  Database-->>SCAN: Candidate keys
  SCAN-->>Client: Next hash cursor and filtered keys
Loading

Possibly related issues

  • pilotspace/moon#368 — The PR implements the issue’s stable hash-ordered SCAN cursor and bounded selection approach.

Suggested reviewers: tindang97

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary and testing, but it omits the required Checklist, Performance Impact, and Notes sections. Add the missing Checklist, Performance Impact, and Notes sections, and include the required verification items from the template.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the SCAN cursor semantics change and stays specific to the main change.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/scan-hash-cursor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/command/key.rs (1)

993-999: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Bound the client-controlled heap capacity.

A request such as SCAN 0 COUNT 9223372036854775807 reaches BinaryHeap::with_capacity(count + 1), allowing an unauthenticated client to trigger an enormous allocation and potentially abort the process. Reject or clamp unreasonable COUNT hints before allocating.

Proposed guard
                 if let Some(c) = parse_int(&args[i]) {
                     if c > 0 {
-                        count = c as usize;
+                        count = (c as usize).min(MAX_SCAN_COUNT);
                     }
                 }

Also applies to: 1062-1064

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/command/key.rs` around lines 993 - 999, Bound the positive COUNT value
parsed in the COUNT option handling and the corresponding logic around the
additional referenced location before it reaches BinaryHeap::with_capacity(count
+ 1). Reject or clamp values above a reasonable maximum while preserving valid
COUNT behavior, ensuring client-controlled input cannot request an enormous heap
allocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 20-25: Update the CHANGELOG cost description to remove the
unconditional “one walk” claim and accurately state that full COUNT 1 pages
currently perform two complete walks due to collision-boundary handling; retain
the existing complexity and Redis-parity details.

In `@src/command/key.rs`:
- Line 1101: Update the hot-path logic around the result vectors at the
referenced locations to preallocate capacity instead of using Vec::new, sizing
each vector from the expected result count. Replace cursor to_string formatting
with the project’s allocation-conscious integer-buffer approach, preserving the
existing output and behavior.
- Around line 1090-1119: Update the paging logic around heap selection and the
full_page equal-hash branch in src/command/key.rs:1090-1119 to retain one extra
candidate or equivalent boundary evidence, so COUNT 1 pages do not rescan both
key planes unless a genuine hash-boundary collision is detected; preserve
complete equal-hash-group emission when a collision exists. Update the
complexity claim in CHANGELOG.md:20-25 to match the corrected bounded
single-walk behavior, or retain it only if the implementation now guarantees it.
- Around line 1118-1119: Update the cursor assignment after extending selected
results so the terminal hash value 0xFFFF_FFFF_FFFF produces cursor 0 instead of
incrementing to 1 << 48. Preserve the existing h_last + 1 behavior for all
smaller hashes, keeping the composite cursor within 48 bits.

---

Outside diff comments:
In `@src/command/key.rs`:
- Around line 993-999: Bound the positive COUNT value parsed in the COUNT option
handling and the corresponding logic around the additional referenced location
before it reaches BinaryHeap::with_capacity(count + 1). Reject or clamp values
above a reasonable maximum while preserving valid COUNT behavior, ensuring
client-controlled input cannot request an enormous heap allocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c898c5a-08b4-46c3-b529-ead7799327b9

📥 Commits

Reviewing files that changed from the base of the PR and between 0835ba5 and f401833.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/command/key.rs
  • src/storage/db.rs

Comment thread CHANGELOG.md
Comment on lines +20 to +25
unchanged) and remain client-compatible. Page cost drops from
`collect + sort O(n log n)` plus a second full-table lookup pass (and,
on the write path, a full-keyspace lazy-expiry probe per page) to one
walk with a bounded COUNT-min selection heap. COUNT stays a hint (Redis
parity): a full page may defer a trailing equal-hash group to the next
page. Follow-up tracked in #368: O(COUNT)-per-page via a DashTable

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the cost claim with the implemented collision path.

The implementation currently performs two complete walks for every full COUNT 1 page, so the unconditional “one walk” claim is inaccurate until the boundary detection is fixed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 20 - 25, Update the CHANGELOG cost description to
remove the unconditional “one walk” claim and accurately state that full COUNT 1
pages currently perform two complete walks due to collision-boundary handling;
retain the existing complexity and Redis-parity details.

Comment thread src/command/key.rs
Comment on lines +1090 to +1119
let full_page = heap.len() == count;
let mut selected = heap.into_sorted_vec();
let mut next_cursor: u64 = 0;
if full_page {
#[allow(clippy::unwrap_used)] // full_page ⇒ count ≥ 1 entries
let h_last = selected.last().unwrap().0;
let h_first = selected.first().map(|e| e.0).unwrap_or(h_last);
if h_first == h_last {
// Whole page one hash value: emit the ENTIRE equal-hash group
// (second filtered pass; unreachable in practice at 48 bits)
// so the cursor may step past it.
let mut extra: Vec<(u64, CompactKey, bool)> = Vec::new();
for key in db.iter_live_keys(now_ms) {
if scan_hash48(key.as_bytes()) == h_last
&& !selected.iter().any(|(_, k, _)| k == key)
{
extra.push((h_last, key.clone(), false));
}
}
for key in db.cold_only_keys(now_ms) {
if scan_hash48(key.as_ref()) == h_last
&& !selected
.iter()
.any(|(_, k, _)| k.as_bytes() == key.as_ref())
{
extra.push((h_last, CompactKey::from(key.as_ref()), true));
}
}
selected.extend(extra);
next_cursor = h_last + 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

COUNT 1 defeats the claimed bounded single-walk paging behavior.

Because a one-entry page always has h_first == h_last, every full COUNT 1 page rescans both planes, making a complete drain quadratic.

  • src/command/key.rs#L1090-L1119: distinguish a genuine boundary collision without automatically rescanning every one-entry page, for example by retaining one extra candidate.
  • CHANGELOG.md#L20-L25: update the complexity claim, or retain it only after fixing the implementation.
📍 Affects 2 files
  • src/command/key.rs#L1090-L1119 (this comment)
  • CHANGELOG.md#L20-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/command/key.rs` around lines 1090 - 1119, Update the paging logic around
heap selection and the full_page equal-hash branch in
src/command/key.rs:1090-1119 to retain one extra candidate or equivalent
boundary evidence, so COUNT 1 pages do not rescan both key planes unless a
genuine hash-boundary collision is detected; preserve complete equal-hash-group
emission when a collision exists. Update the complexity claim in
CHANGELOG.md:20-25 to match the corrected bounded single-walk behavior, or
retain it only if the implementation now guarantees it.

Comment thread src/command/key.rs
// Whole page one hash value: emit the ENTIRE equal-hash group
// (second filtered pass; unreachable in practice at 48 bits)
// so the cursor may step past it.
let mut extra: Vec<(u64, CompactKey, bool)> = Vec::new();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Remove the newly introduced hot-path allocation patterns.

Preallocate the result vectors and format the cursor with an allocation-conscious integer buffer rather than to_string().

As per coding guidelines, src/{command,protocol,shard,io}/**/*.rs must avoid Vec::new and to_string() on hot paths.

Also applies to: 1128-1128, 1150-1154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/command/key.rs` at line 1101, Update the hot-path logic around the result
vectors at the referenced locations to preallocate capacity instead of using
Vec::new, sizing each vector from the expected result count. Replace cursor
to_string formatting with the project’s allocation-conscious integer-buffer
approach, preserving the existing output and behavior.

Source: Coding guidelines

Comment thread src/command/key.rs
Comment on lines +1118 to +1119
selected.extend(extra);
next_cursor = h_last + 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the terminal cursor within 48 bits.

When h_last == 0xFFFF_FFFF_FFFF, adding one returns 1 << 48, violating the composite cursor contract. Since no greater 48-bit hash exists after emitting the entire group, return cursor 0.

Proposed fix
             selected.extend(extra);
-            next_cursor = h_last + 1;
+            next_cursor = if h_last == 0x0000_FFFF_FFFF_FFFF {
+                0
+            } else {
+                h_last + 1
+            };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
selected.extend(extra);
next_cursor = h_last + 1;
selected.extend(extra);
next_cursor = if h_last == 0x0000_FFFF_FFFF_FFFF {
0
} else {
h_last + 1
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/command/key.rs` around lines 1118 - 1119, Update the cursor assignment
after extending selected results so the terminal hash value 0xFFFF_FFFF_FFFF
produces cursor 0 instead of incrementing to 1 << 48. Preserve the existing
h_last + 1 behavior for all smaller hashes, keeping the composite cursor within
48 bits.

@pilotspacex-byte
pilotspacex-byte merged commit 0557c50 into main Jul 17, 2026
13 checks passed
@TinDang97
TinDang97 deleted the fix/scan-hash-cursor branch July 17, 2026 14:25
TinDang97 added a commit that referenced this pull request Jul 17, 2026
…th invariant

Adversarial-review follow-ups on the O(COUNT) page walk:

- SCAN/SCAN-readonly now mask the parsed cursor to the low 48 bits.
  Legitimate resumed cursors always fit (multi-shard composites are
  unpacked by coordinate_scan before dispatch), but at --shards 1 a
  client-supplied out-of-range cursor (e.g. a composite cursor replayed
  from a multi-shard server, or garbage) previously filtered out every
  key — h48 is always < 2^48 — and returned an empty page with cursor
  0: a false "scan complete" on a non-empty keyspace. Pre-existing
  behavior (same comparison since PR #379), closed here because this PR
  owns the cursor-semantics surface. Red/green:
  scan_out_of_range_cursor_clamps_to_hash_space.

- debug_assert in Database::scan_hot_page that the DashTable directory
  depth is <= 48 (new DashTable::directory_depth accessor). The h64→h48
  bridge and the "equal-h48 group never straddles a page" guarantee are
  load-bearing on this; depth > 48 needs a 2^48-entry directory —
  unreachable in practice, but now asserted rather than assumed.

author: Tin Dang <tindang.ht97@gmail.com>
pilotspacex-byte added a commit that referenced this pull request Jul 17, 2026
…gment walk (#368) (#380)

* perf(storage): SCAN hot-plane pages are true O(COUNT) — hash-range segment walk (#368)

Completes the second half of #368. PR #379 made the SCAN cursor a
position in stable hash space (fixing the stable-key guarantee); this
change makes each page's hot-plane cost independent of keyspace size.

The cursor hash becomes the DashTable's own fixed-seed xxh64 key hash
truncated to its top 48 bits. Because the extendible-hashing directory
is indexed by the hash's top `global_depth` bits, ascending directory
order IS ascending hash order and directory entry d covers exactly the
hash range [d << (64-D), (d+1) << (64-D)) — segments are
range-partitioned in hash space. The new `DashTable::hash_page` starts
at the segment covering the cursor and walks segments in ascending
range order, stopping at the first segment boundary once COUNT entries
are collected: later segments can only hold larger hashes, so the page
is complete without touching the rest of the table. A segment with
local_depth < global_depth occupies a contiguous directory run, so
alias-dedup is a consecutive store-index comparison.

Split/merge/directory-doubling between pages stays safe by
construction: the cursor is a position in hash space, and structural
churn only changes WHICH segment covers that position, never the set of
keys at or above it. Equal-hash groups can never straddle a page
(equal top-48 bits => same segment; pages are whole-segment granular),
which preserves the PR #379 collision boundary rule unchanged.

`Database::scan_hot_page` wraps the walk (maps 64-bit table hashes to
the 48-bit cursor space via >> 16, judges liveness from the entry
during iteration), and `scan_core` feeds its selection heap from that
pruned page instead of a full `iter_live_keys` walk. The pathological
all-one-hash second pass now rescans only the already-fetched page.
The cold plane keeps its filtered in-RAM index walk (bounded by
spilled-key count); ordered cold-side paging remains a follow-up.

Red/green coverage: new DashTable unit tests (empty-table terminal
page, alive-filter + more-flag, 4000-key drain in (hash, key) order
under forced split churn between pages) plus the existing six SCAN
semantics tests (churn stability, exact duplicate-free drain, MATCH
paging, cold-plane enumeration x2, COUNT-as-hint) all green on the new
path.

Verified on Linux VM (4 shards, real server): 500/500 keys drained in
12 pages, MATCH key:1* = 111/111; scaling proof — 500 SCAN pages at
COUNT 100 take 0.10s on a 10k-key DB and 0.11s on a 1,000,000-key DB
(flat per-page cost across a 100x keyspace; the previous full-walk
design re-hashed every live key on every page).

SCAN cursors from before this change are invalidated (cursor hash
function changed); cursors are documented as ephemeral — restart scans
at 0. Multi-shard composite packing (upper 16 shard | lower 48
per-shard) is unchanged.

Refs #368
author: Tin Dang <tindang.ht97@gmail.com>

* fix(command): clamp SCAN cursor to 48-bit hash space + assert the depth invariant

Adversarial-review follow-ups on the O(COUNT) page walk:

- SCAN/SCAN-readonly now mask the parsed cursor to the low 48 bits.
  Legitimate resumed cursors always fit (multi-shard composites are
  unpacked by coordinate_scan before dispatch), but at --shards 1 a
  client-supplied out-of-range cursor (e.g. a composite cursor replayed
  from a multi-shard server, or garbage) previously filtered out every
  key — h48 is always < 2^48 — and returned an empty page with cursor
  0: a false "scan complete" on a non-empty keyspace. Pre-existing
  behavior (same comparison since PR #379), closed here because this PR
  owns the cursor-semantics surface. Red/green:
  scan_out_of_range_cursor_clamps_to_hash_space.

- debug_assert in Database::scan_hot_page that the DashTable directory
  depth is <= 48 (new DashTable::directory_depth accessor). The h64→h48
  bridge and the "equal-h48 group never straddles a page" guarantee are
  load-bearing on this; depth > 48 needs a 2^48-entry directory —
  unreachable in practice, but now asserted rather than assumed.

author: Tin Dang <tindang.ht97@gmail.com>

---------

Co-authored-by: Tin Dang <tindang.ht97@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants