[
https://issues.apache.org/jira/browse/HDDS-16438?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Sammi Chen updated HDDS-16438:
------------------------------
Parent: HDDS-8342
Issue Type: Sub-task (was: Bug)
> FSO lifecycle scan can skip an unscanned directory's expired keys after a mid
> scan crash
> ----------------------------------------------------------------------------------------
>
> Key: HDDS-16438
> URL: https://issues.apache.org/jira/browse/HDDS-16438
> Project: Apache Ozone
> Issue Type: Sub-task
> Reporter: Siyao Meng
> Priority: Major
> Attachments: HDDS-16438.001.patch
>
>
> h3. Finding
> Cache-first unordered enumeration breaks canSkipDir's descending-order
> premise, so on crash/resume an unscanned FSO directory is skipped and its
> expired keys are not collected; the demonstrated external effect (expired
> data left uncollected past its lifecycle policy) is currently masked by the
> next-generation fresh rescan, and absent that rescan the skip would persist
> and recur under repeated faults. Reachable through the FSO lifecycle scan on
> any bucket with a concurrent mkdir in the partial cache during a mid-scan
> crash. MASKED: classified by the consequence the defect would expose if the
> rescan mask were absent.
> h3. Classification
> * Verdict: MASKED
> * Severity: High
> * Source: Specula TLA+ model checking and confirmation debate, finding MC-1
> h3. Reproduce
> {noformat}
> Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
> Specula: v1.1.0 (commit c6aa3dfa)
> Target: om-key-lifecycle
> Guidance:
> campaigns/ozone-9fbf9ee/targets/006-om-key-lifecycle/.prompt-extra.md
> {noformat}
> {code:none}
> specula run --agent=claude-code --effort=high --keep-original
> --max-parallel=2 \
> --enable-reviews --confirm-debate --tlc-memory-limit=28G
> --tlc-worker-limit=8 \
> "om-key-lifecycle|apache/ozone|Java|Use the target-specific
> .prompt-extra.md"
> {code}
> Discovered under HDDS-16429 (Specula TLA+ verification effort). The TLA+
> specification, counterexample, and confirmation debate live in the Specula
> run artifacts.
> Generated with Specula (Claude Opus 4.8).
> h3. Steps to reproduce
> # Enable a lifecycle policy on an FSO bucket. Under a directory, siblings
> dirA and dirB exist in the directory table while dirC exists only in the
> partial directory-table cache (for example a concurrent mkdir pending
> double-buffer flush), and dirC sorts lower than dirA or between siblings.
> # The lifecycle scan calls getSubDirectory, which lists cache children first
> (hash-map order) then table children (ascending), producing an unordered
> combined list, and pushes them onto the DFS LIFO stack in list order.
> # Because a cache-resident child is pushed before the table children, it is
> popped last, so siblings are not popped in strictly descending name order.
> # The scan stops (crash, leader loss, suspend, or list-full abort) after a
> higher-named sibling and persists lastScannedDir at that cursor while the
> lower-named cache dir is still unscanned.
> # On resume, canSkipDir compares paths lexicographically and skips any
> directory whose path sorts greater than the cursor. The unscanned directory
> that sorts greater than the cursor is wrongly skipped, so its keys are not
> collected in that scan generation.
> Masked: the next scan generation starts a fresh full scan from the root and
> collects the stranded keys, so the effect is delayed expiration bounded to
> roughly one generation, unless the reorder recurs every generation.
> h3. Root cause
> canSkipDir (KeyLifecycleService) decides a resumed directory was already
> scanned when its path sorts strictly greater than the persisted
> lastScannedDir. That is sound only if the DFS pops siblings in strictly
> descending name order, which requires the collected child list to be sorted
> ascending before it is pushed. getSubDirectory builds the list as cache
> children (ConcurrentHashMap iteration order, unordered) followed by table
> children (ascending) and never sorts the combined result, so the
> descending-pop-order premise is violated for any directory sitting in the
> partial cache.
> h3. Suggested fix
> Sort the sub-directory list by name in ascending order at the end of
> getSubDirectory (a new DirectoryList.sortByName that keeps the parallel key
> and path lists index-aligned). This restores the strictly-descending pop
> order that canSkipDir relies on, so a resumed directory is skipped only when
> it was genuinely already scanned. See [^HDDS-16438.001.patch].
> The added test pins the ordering invariant directly (DirectoryList sorts
> siblings ascending and keeps the parallel lists aligned). A full end-to-end
> reproduction requires a sub-directory that is resident only in the partial
> cache and not yet in the table at scan time, which is not deterministically
> injectable in the in-process test harness; the existing resume tests
> (testDirectorySkippedAfterResume, testNestedFSODirectoryScanResume) continue
> to pass with the fix.
> Patch is an AI-drafted proposal (Specula + Claude); pending human build, full
> test, and review before merge.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]