# Promises — change 62757: LU-12669 ec: recover data from parity
Branch master · PS113 · MERGED · https://review.whamcloud.com/c/fs/lustre-release/+/62757
Harvested 2026-11-05
Checked at PS113 (783c13bb0b8f), master 550451c0e4b9
Refer to items by id, e.g. "address item 62757-6eca2ddb".

## Open (8)

### 62757-1782765d — COMMIT_MSG:38 [severity: high]
*The thread is marked resolved, but the item is still open.*
**Summary:** Ensure EC read reconstruction falls back to waiting for OST recovery instead of returning -EIO; tracked/merged via another patch
**Origin:** comment on PS95, thread resolved; promise by Zhenyu Xu: "This part is merged from patch https://review.whamcloud.com/c/fs/lustre-release/+/63956"
**Thread:**
> [Andreas Dilger, 2026-08-10] Is this -EIO handled internally, or is it returned to userspace?  The EC feature **should never make reliability *worse* than not having it at all**.  If EC read reconstruction fails for any reason, then the client should fall back to waiting for the failed OST to recover and occasionally retry the reconstruction.
> 
> This is important in case too many OSTs were offline at once to do the rebuild (e.g. whole server cluster failed), but then enough OSTs return to successfully rebuild the data but one OSS is down because of a hardware problem or similar.  This is a fairly common scenario in production.
> 
> If EC read reconstruction doesn't fall back to waiting for OST recovery, please push a follow-up patch to handle these cases.  At a very minimum, please file an LU ticket that is tracking this and should be marked Critical for the 2.18.0 release.
> [Zhenyu Xu, 2026-08-11] This part is merged from patch https://review.whamcloud.com/c/fs/lustre-release/+/63956
> [Zhenyu Xu, 2026-08-17] Acknowledged
**Check (AI, 2026-09-28, verdict still-open):** The reply points to 63956, but that change is ABANDONED. It never asked for a fallback anyway: candidates/63956.diff only fixes an infinite loop in ll_readpage by returning -EIO for CIT_EC_RD pages past lcc_end_index, which is the -EIO Andreas asked about. On master (550451c0e4b9, which contains 783c13bb0b8f) a failed reconstruction still ends in an error. lov_io.c:3005-3011 sets ec_recovery_failed and returns -EAGAIN when err_nr > pcount. lov_io.c:3052-3059 returns -EIO when there are not enough parity stripes. vvp_io.c:1520-1525 then skips the read and lets the -EIO out. cl_io.c:875-881 only restarts on -EAGAIN while ci_ndelay is set and ci_tried_all_mirrors is false, and after that it returns -EIO. file.c:2293-2306 restarts at most RETRY_ATTEMPTS times. lov_io_mirror_init (lov_io.c:561-571) returns -EIO straight away for CIT_EC_RD when no parity is in sync. Nothing switches a failed EC_RD back to a blocking read (ci_ndelay=0) that waits for the OST to recover and retries the rebuild from time to time. The history and ticket files have no such commit. The closest candidate is 64919 (NEW). It adds LOV_EC_WAIT and ci_ndelay=0, but only for slow-marked stripes and for files with no usable parity. When hard-dead stripes exceed the parity count it still picks LOV_EC_RECONSTRUCT (candidates/64919.diff:633-643), so the whole-cluster-restart case with one OSS still down still fails. 68339 only adds a retry budget for concurrent EC readers (EC_RECOVERY_RETRY_MAX).
**Related work:** 64919 (NEW): LU-12669 lov: slow and dead OST detection for degraded reads, 68339 (ABANDONED): LU-12669 ec: serialize concurrent EC recovery readers, 63956 (ABANDONED): LU-12669 llite: fix infinite loop in ll_readpage for EC reads
**Suggested action:** When CIT_EC_RD fails with ec_recovery_failed, or finds no in-sync parity, don't return -EIO. Restart the read as a blocking CIT_READ (ci_ndelay=0, switch_ec cleared) that waits for the failed OST's import to recover, and retry the reconstruction periodically. Add a sanity-ec test that takes down more OSTs than the parity count, brings all but one back, and checks the read succeeds. If this can't be done now, file an LU ticket marked Critical for 2.18.0 as the reviewer asked.
**Possible follow-ups:** 63956 (ABANDONED, linked in the thread)
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/1782765d_11e1cd00/

### 62757-809d5884 — lustre/lov/lov_io.c:3011 [severity: med]
**Summary:** Fix CWARN/return value mismatch (rc=-EIO vs -EAGAIN) when patch is refreshed
**Origin:** comment on PS112; promise by Marc Vef: "`CWARN` can be fixed if this patch is refreshed."
**Thread:**
> [Gerrit AI review for Lustre, 2026-09-08] (defect) The CWARN above prints `rc = -EIO` but this returns -EAGAIN, so the log and the return value disagree. Setting `rc = -EIO;` first and using `rc` in both places would keep them from drifting.
> 
> The value matters here beyond the message. -EAGAIN out of cio_start() is the FLR "try the next mirror" signal, and cl_io_loop() acts on it:
> 
>     if (result == -EAGAIN && io->ci_ndelay && !io->ci_iocb_nowait) {
>         if (!io->ci_tried_all_mirrors) {
>             io->ci_need_restart = 1;
>             result = 0;
> 
> ll_io_init() sets `ci_ndelay = !(iot == CIT_WRITE)`, so it is 1 for CIT_EC_RD too, and ci_tried_all_mirrors only becomes true once ci_ndelay_tried reaches a multiple of `lo_mirror_count * 4` in lov_io_mirror_init(). So a permanently unrecoverable raid set does not fail here: ll_file_io_generic() re-drives the whole EC read several more times - each round paying a synchronous vvp_update_inode_size() getattr, a fresh lock enqueue against the dead OSTs and a re-read of every live data and parity stripe - before cl_io_loop() finally turns it into -EIO.
> 
> The comment just above says "we return -EAGAIN instead of zeros", which suggests -EAGAIN was expected to reach the caller. Would -EIO here (as the other unrecoverable exits in this function already use) be what you want?
> 
> The -EAGAIN at the `vmpage->mapping != inode->i_mapping` check in lov_ec_read_stripe_pages() looks intentional by contrast - there a restart is the right answer.
> [Marc Vef, 2026-09-08] Not a defect but "minor". The goto was changed from `-EIO` to `-EAGAIN` deliberately in previous patchsets. The CWARN was missed (see comment above for intent). `-EIO` will happen eventually (see sanity-ec 44a/52b/52c which expect EIO).
> 
> `CWARN` can be fixed if this patch is refreshed.
> [Andreas Dilger, 2026-09-27] This is why I recommend setting `rc = -ERRNO`, and then using it consistently in the `CERROR()` and `RETURN(rc)` or `GOTO(rc)` calls.  Otherwise there is a chance they will drift during code changes.
**Code context:**
```
    3006: 			CWARN("%s: "DFID": too many unrecoverable stripes in raid set %d: err_nr %d > pcount %d: rc = %d\n",
    3007: 			      lov2obd(dev->ld_lov)->obd_name,
    3008: 			      PFID(lu_object_fid(lov2lu(lov))),
    3009: 			      raid_set, err_nr, pcount, -EIO);
    3010: 			io->u.ci_ec.ec_recovery_failed = true;
>>> 3011: 			GOTO(rg_cleanup, rc = -EAGAIN);
    3012: 		}
    3013: 
    3014: 		/* Batch parity reads if needed. par_vmpages/par_clpages
    3015: 		 * indexed as [parity_stripe * npages + page_pos].
    3016: 		 */
```
**Check (AI, 2026-09-28, verdict still-open):** The mismatch is still there. Both change/lustre/lov/lov_io.c:3006-3011 (PS113) and master/lustre/lov/lov_io.c:3006-3011 (550451c0e4b9) still call CWARN(... rc = %d ..., -EIO) and then GOTO(rg_cleanup, rc = -EAGAIN). No commit in history/809d5884_40b8de78.txt changes it. In the candidates, 58097.diff only shows the unchanged GOTO line as context, and 67878.diff only touches the separate i_mapping -EAGAIN exit. No follow-up changes the CWARN argument. Marc Vef called this minor and deferred it to a refresh that never happened. Andreas Dilger asked for rc to be set once and used in both places.
**Suggested action:** In lov_io_ec_rd_start(), set rc = -EAGAIN before the 'too many unrecoverable stripes' CWARN, pass rc to the CWARN instead of the hard-coded -EIO, then GOTO(rg_cleanup, rc). This makes the log and the return value agree.
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/809d5884_40b8de78/

### 62757-cd25cc44 — lustre/lov/lov_io.c:2759 [severity: med]
*The thread is marked resolved, but the item is still open.*
**Summary:** fix kmap_local unmap ordering/LIFO violation and KM_MAX_IDX overrun risk
**Origin:** comment on PS89, thread resolved; promise by Zhenyu Xu: "Acknowledged"
**Thread:**
> [Gerrit AI review for Lustre, 2026-08-06] (minor) Not a bug on the 64-bit configs Lustre normally builds for, where kmap_local_page() is just page_address() and kunmap_local() is a no-op, but the kmap_local usage here does not follow the documented contract.
> 
> Two things: the mappings are unmapped in ascending index order (parity 0..d_j-1, then data 0..p_j-1, then recov_ptrs 0..d_j-1) rather than strict LIFO, and up to `dcount + pcount` mappings are held at once. On a CONFIG_KMAP_LOCAL kernel the first trips the WARN in kunmap_local_indexed() and the second exceeds KM_MAX_IDX (16) once dcount grows - LOV_EC_MAX_DATA_STRIPES is 255.
> 
> Since the pages here are all kernel pages, plain page_address() would sidestep the whole question.
> [Zhenyu Xu, 2026-08-07] Acknowledged
**Code context:**
```
    2754: 				case EC_DPG_ZERO:
    2755: 					ptrs[p_j++] = zerobuf;
    2756: 					break;
    2757: 				case EC_DPG_UPTODATE:
    2758: 				case EC_DPG_READ:
>>> 2759: 					ptrs[p_j++] = kmap_local_page(
    2760: 						all_vmpages[idx]);
    2761: 					break;
    2762: 				case EC_DPG_ERROR:
    2763: 					err_array_j[d_j] = k;
    2764: 					/* vmpage is still alive (the submit
```
**Check (AI, 2026-09-28, verdict still-open):** Only half of the item is fixed. The LIFO ordering was fixed at PS113. In change/lustre/lov/lov_io.c:2673-2681 the parity mappings are now released in reverse (k = r_j-1..0). At :2684-2720 the forward data/error loop is walked again in reverse to unmap them, and :3179-3189 has the same fix. master/ is identical. The KM_MAX_IDX overrun is not fixed. lov_ec_recover_page_pos() (:2608-2650) still keeps a kmap_local_page() mapping open for every available data page and every error page. That can be up to dcount mappings, and then r_j more for parity, all held at once. Nothing limits that to KM_MAX_IDX (16), while LOV_EC_MAX_DATA_STRIPES is 255. The reviewer's suggestion to use page_address() was not taken. No master commit fixes this. Of the candidates, 58097 only swaps in cl_kmap_local/kmap_local_folio, and 66481/67878 keep the same pattern of holding every mapping at once.
**Related work:** 58097 (MERGED): LU-17916 clio: switch to struct folio, 66481 (NEW): LU-12669 ec: fix EC recovery read for encrypted files, 67878 (NEW): LU-20566 ec: recover O_DIRECT reads with transient pages
**Suggested action:** In lov_ec_recover_page_pos(), stop holding up to dcount+pcount kmap_local mappings at the same time. Either use page_address() (these are lowmem page-cache/kernel pages) or kmap() for the data, error and parity pages. Otherwise, reject or cap dcount+pcount at KM_MAX_IDX when kmap_local_page() is not a no-op.
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/cd25cc44_7c22d0fa/

### 62757-e84f19f2 — lustre/tests/sanity-ec.sh:4021 [severity: low]
*The thread is marked resolved, but the item is still open.*
**Summary:** Add --printf option support for lfs getstripe to print single-component OST indexes
**Origin:** comment on PS92, thread resolved; promise by Andreas Dilger: "This is implemented in patch https://review.whamcloud.com/68989 ("LU-20645 utils: add --printf and --ls opts to lfs-getstripe")"
**Thread:**
> [Andreas Dilger, 2026-08-08] (aside) it would be nice if `lfs getstripe --printf "%Lo" $tf` worked to print the OST indexes, like `lfs find --printf "%Lo" $tf` does.  Unfortunately, that does not allow printing parameters for only a single component.
> [Zhenyu Xu, 2026-08-12] Acknowledged
> [Andreas Dilger, 2026-09-27] This is implemented in patch https://review.whamcloud.com/68989 ("LU-20645 utils: add --printf and --ls opts to lfs-getstripe")
**Code context:**
```
    4016: 	local sum1=$(md5sum $tf | awk '{print $1}')
    4017: 
    4018: 	# collect objects
    4019: 	local ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' |
    4020: 		tr '\n' ' '))
>>> 4021: 	local osts=($($LFS getstripe -I${ids[0]} $tf |
    4022: 		awk '/l_ost_idx:/ { print $5 }' | tr -d "," ))
    4023: 
    4024: 	# randomly disable 2 OSTs
    4025: 	local idx1=$((RANDOM % ${#osts[@]}))
    4026: 	local idx2=$((RANDOM % ${#osts[@]}))
```
**Check (AI, 2026-09-28, verdict still-open):** Master has no getstripe --printf: in master/lustre/utils/lfs.c, fp_format_printf_str is only set by the lfs find option parser (lines 7446 and 8161). Change 68989 (NEW) adds 'lfs getstripe --printf', so '%Lo' would print the OST indexes of the whole file. But its lfs_getstripe_internal hunk (candidates/68989.diff:485-516) rejects -I/--component-id, --comp-flags, -E, --mirror-id and similar with 'can't be used with --printf. prefer lfs find'. So it still can't print one component's OST indexes, which was both the limitation Andreas named and the thing sanity-ec.sh:4021 needs ('getstripe -I${ids[0]}' piped to awk on l_ost_idx). Andreas's reply saying this 'is implemented' in 68989 is only partly right.
**Related work:** 68989 (NEW): LU-20645 utils: add --printf and --ls opts to lfs-getstripe
**Suggested action:** Extend 68989, or add a follow-up, so 'lfs getstripe --printf' honours -I/--component-id (and the other component and mirror selectors) and prints %Lo for just the chosen component, instead of rejecting them. Then switch sanity-ec.sh to use 'lfs getstripe -I<id> --printf "%Lo"' in place of the awk parsing of l_ost_idx.
**Possible follow-ups:** 68989 (NEW, linked in the thread)
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/e84f19f2_56397664/

### 62757-27caf5f8 — lustre/lov/lov_io.c:2743 [severity: low]
**Summary:** initialize lsme_p/pindex defensively when patch is refreshed
**Origin:** comment on PS112; promise by Marc Vef: "nevertheless, this can be addressed if the patch is refreshed"
**Thread:**
> [Gerrit AI review for Lustre, 2026-09-08] (minor) `lsme_p` and `pindex` are only assigned under `if (lsme_d->lsme_dstripe_count > 0)`, while their consumers sit under `if (err_nr > 0 && err_nr <= pcount)`. That is safe only via the non-local chain dstripe_count == 0 -> pcount == 0 -> err_nr == 0 (enforced by the check a few lines earlier). `lle_p` right above is initialised to NULL; if the patch is refreshed, initialising these two the same way would make the invariant local and keep -Wmaybe-uninitialized quiet.
> [Marc Vef, 2026-09-08] no path reads below them uninitialized. nevertheless, this can be addressed if the patch is refreshed
**Code context:**
```
    2738: 	struct lov_device *dev = lov_object_dev(lov);
    2739: 	struct lov_layout_composite *comp = &lov->u.composite;
    2740: 	struct lov_layout_entry *lle_d = NULL;	/* data component entry */
    2741: 	struct lov_layout_entry *lle_p = NULL;	/* parity component entry */
    2742: 	struct lov_stripe_md_entry *lsme_d;
>>> 2743: 	struct lov_stripe_md_entry *lsme_p;
    2744: 	int data_mir_idx;	/* data mirror index */
    2745: 	int parity_mir_idx;	/* parity mirror index */
    2746: 	int index;	/* data component index */
    2747: 	int pindex;	/* parity component index */
    2748: 
```
**Check (AI, 2026-09-28, verdict still-open):** In change/lustre/lov/lov_io.c:2743 and :2747 (PS113, 783c13bb0b8f), `lsme_p` and `pindex` are still declared without initialisers. The same lines are unchanged on master/lustre/lov/lov_io.c:2743/2747 (550451c0e4b9), and `lle_p` just above is still the only one set to NULL. None of the candidate diffs touch these declarations. 67878 only adds an argument to a call that uses lsme_p, and the other matches are unrelated `lsme->lsme_pattern` hits. The change merged at PS113 without the refresh the promise depended on. The reviewer's point holds: this is not a bug, since the reads are safe only because dstripe_count == 0 leads to pcount == 0 and so err_nr == 0. It is a readability and -Wmaybe-uninitialized clean-up that nobody has done yet.
**Suggested action:** In the EC recovery function in lustre/lov/lov_io.c, initialise `lsme_p` to NULL and `pindex` to -1 where they are declared, as `lle_p` already is. That makes the safety of the reads obvious locally and keeps -Wmaybe-uninitialized quiet.
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/27caf5f8_55b3360c/

### 62757-2fc4a8fa — lustre/include/cl_object.h:442 [severity: low]
*The thread is marked resolved, but the item is still open.*
**Summary:** Fix inverted top-to-bottom/bottom-to-top doc wording via follow-up change 68339
**Origin:** comment on PS112, thread resolved; promise by Marc Vef: "Already addressed in follow-up: 68339 (direct child) flips the wording in cl_object.h and cl_io.c and makes cl_io_set_range static."
**Thread:**
> [Gerrit AI review for Lustre, 2026-09-08] (minor) The direction here looks inverted. cl_io_set_range() walks the layers with cl_object_for_each(), which this header documents as top-to-bottom:
> 
>     /**
>      * Helper macro: iterate over all layers of the object \a obj, assigning every
>      * layer top-to-bottom to \a slice.
>      */
>     #define cl_object_for_each(slice, obj)
> 
> cl_object_for_each_reverse() is the bottom-to-top one (cl_object_attr_update(), cl_object_glimpse()). The same wording appears on the cl_io_set_range() kerneldoc in cl_io.c.
> [Marc Vef, 2026-09-08] Already addressed in follow-up: 68339 (direct child) flips the wording in cl_object.h and cl_io.c and makes cl_io_set_range static.
**Code context:**
```
     437: 	 */
     438: 	void (*coo_req_projid_set)(const struct lu_env *env,
     439: 				   struct cl_object *obj, __u32 *projid);
     440: 	/**
     441: 	 * Set the coverage of the \a io, used by CIT_EC_RD, called from
>>>  442: 	 * bottom to top.
     443: 	 */
     444: 	void (*coo_io_set_range)(const struct lu_env *env,
     445: 				 struct cl_object *obj, struct cl_io *io);
     446: };
     447: 
```
**Check (AI, 2026-09-27, verdict in-followup):** The review comment was correct. On master, cl_io_set_range() at lustre/obdclass/cl_io.c:1202 walks the layers with cl_object_for_each(), which cl_object.h:484 describes as top-to-bottom. The docs still say the opposite: cl_object.h:442 has 'bottom to top' and cl_io.c:1191 has 'bottom-to-top'. change/ (PS113) has the same wording. candidates/68339.diff:115-116 changes cl_object.h to 'top to bottom', lines 506/533 change the cl_io.c kerneldoc to 'top-to-bottom', and line 511 makes cl_io_set_range static. 68339 is still NEW, so this fix has not landed.
**Suggested action:** Land 68339. It changes coo_io_set_range and cl_io_set_range() docs to say top-to-bottom and makes cl_io_set_range static.
**Possible follow-ups:** 68339 (ABANDONED, linked in the thread)
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/2fc4a8fa_24a3a046/

### 62757-cd85c64f — lustre/tests/sanity-ec.sh:4242 [severity: low]
**Summary:** Clean up duplicate/unnecessary local EC stripe-count variables in favor of EC_DSTRIPE if patch is refreshed
**Origin:** comment on PS112; promise by Marc Vef: "Can be updated if refreshed"
**Thread:**
> [Andreas Dilger, 2026-09-07] I don't see where these EC values are set? Do they predate the patch?
> [Marc Vef, 2026-09-07] Yes, this was actually duplicate. Those are defined in `enable_ec()`. hmm, these `local` variables are not really required and could use `EC_DSTRIPE` directly. Can be updated if refreshed
**Code context:**
```
    4237: 	local tf=$DIR/$tfile
    4238: 
    4239: 	stack_trap "rm -f $tf"
    4240: 	enable_ec
    4241: 	local dstripe=$EC_DSTRIPE
>>> 4242: 	local cstripe=$EC_CSTRIPE
    4243: 	# Create a simple EC file with data mirror + parity mirror
    4244: 	$LFS setstripe -E eof -S 1M -c $dstripe --ec $dstripe+$cstripe $tf ||
    4245: 		error "setstripe --ec $dstripe+$cstripe failed"
    4246: 
    4247: 	# Write some data to the file
```
**Check (AI, 2026-09-28, verdict still-open):** The redundant locals are still there at patchset 113: change/lustre/tests/sanity-ec.sh:4241-4242 has `local dstripe=$EC_DSTRIPE` and `local cstripe=$EC_CSTRIPE`. They also landed unchanged on master in test_40a (master/lustre/tests/sanity-ec.sh:4745-4746), where they are used in the setstripe call at line 4748. No commit in the history removes them, and no diff in candidates/ deletes either line. The reviewer's point is valid, because enable_ec() already sets EC_DSTRIPE and EC_CSTRIPE. This is a minor cleanup.
**Suggested action:** In test_40a of lustre/tests/sanity-ec.sh, drop the `local dstripe=$EC_DSTRIPE` and `local cstripe=$EC_CSTRIPE` lines and use $EC_DSTRIPE and $EC_CSTRIPE directly in the setstripe command and its error message.
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/cd85c64f_f108c15c/

### 62757-740d8821 — lustre/tests/sanity-ec.sh:4030 [severity: low]
*The thread is marked resolved, but the item is still open.*
**Summary:** Make EC stripe counts global variables across all sanity-ec.sh subtests
**Origin:** comment on PS110, thread resolved; promise by Marc Vef: "This needs to be done in other places too, but this is deferred to LU-20565"
**Thread:**
> [Andreas Dilger, 2026-08-25] Nice.  These should be global variables set at the start of this script so that they can be used in all of these subtests instead of hard-coding `--ec 4+2` everywhere.  That maximizes the test coverage (e.g. will run with 3+ OSTs) while not restricting the counts for other configs.
> [Marc Vef, 2026-09-07] Done. This needs to be done in other places too, but this is deferred to LU-20565
**Code context:**
```
    4025: test_40a() {
    4026: 	(( $OSTCOUNT >= 3 )) || skip_env "needs >= 3 OSTs for EC rebuild"
    4027: 
    4028: 	local tf=$DIR/$tfile
    4029: 	local dstripe=$((OSTCOUNT > 4 ? OSTCOUNT - 2 : 2))
>>> 4030: 	local cstripe=$((OSTCOUNT - dstripe >= 2 ? 2 : 1))
    4031: 
    4032: 	stack_trap "rm -f $tf"
    4033: 	enable_ec
    4034: 	# Create a simple EC file with data mirror + parity mirror
    4035: 	$LFS setstripe -E -1 -S 1M -c $dstripe --ec $dstripe+$cstripe $tf ||
```
**Check (AI, 2026-09-28, verdict still-open):** The EC stripe-count globals exist: master/lustre/tests/sanity-ec.sh:130-133 sets EC_CSTRIPE, EC_DSTRIPE, EC_MINSTRIPE and EC_ARG. But only a few subtests use them (lines 337-1648 and 4745-4746, where test_40a now reads EC_DSTRIPE/EC_CSTRIPE). master/lustre/tests/sanity-ec.sh still has 221 hard-coded `--ec 4+2` and 335 hard-coded `--ec N+M` in total. The deferred work, converting the other subtests, has not been done. The only commit on master under LU-20565 is 9e6ca9155dd3 (change 68278). It changes one line to lower the OSTCOUNT requirement for sanity-ec/7d and does not switch any tests to the globals. None of the listed candidate diffs adds EC_ARG or EC_DSTRIPE use apart from 63809, which is not in the follow-up list. Its content (introducing the globals) already matches master.
**Related work:** 68278 (MERGED): LU-20565 tests: lower required OSTCOUNT for sanity-ec/7d
**Suggested action:** Under LU-20565, replace the remaining hard-coded `--ec 4+2` and `-c 4`/`-c 6` values in lustre/tests/sanity-ec.sh with $EC_ARG, $EC_DSTRIPE, $EC_CSTRIPE and $EC_MINSTRIPE. Change each subtest's OSTCOUNT skip check to use EC_MINSTRIPE, so the tests can run with 3 or more OSTs.
**Possible follow-ups:** 70273 (NEW, names LU-20565), 70272 (NEW, names LU-20565), 70213 (NEW, names LU-20565), 70212 (NEW, names LU-20565), 70175 (NEW, names LU-20565), 70278 (NEW, names LU-20565), 70277 (NEW, names LU-20565), 70276 (NEW, names LU-20565), 70275 (NEW, names LU-20565), 70274 (NEW, names LU-20565)
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/740d8821_34f10986/


## In flight (3)

### 62757-9b234885 — lustre/llite/file.c:2213 [severity: med]
**Summary:** Track FLR-EC DIO+AIO reconstruction work via filed critical ticket LU-20566 for 2.18.0
**Origin:** comment on PS92, thread resolved; promise by Andreas Dilger: "I filed LU-20566 "FLR-EC: recover data from parity code for DIO+AIO" for this, and Keguang has already pushed a patch for it."
**Thread:**
> [Andreas Dilger, 2026-08-08] Please file a ticket in Jira to track this work and mark it Critical for Release version 2.18.0, so it is not missed before the 2.18.0 release.
> [Andreas Dilger, 2026-08-10] I filed LU-20566 "FLR-EC: recover data from parity code for DIO+AIO" for this, and Keguang has already pushed a patch for it.
**Code context:**
```
    2208: 		 * from parity (buffered IO).
    2209: 		 *
    2210: 		 * This only handles sync DIO (!is_aio).  For true
    2211: 		 * AIO, the completion fires externally via cl_dio_aio_end
    2212: 		 * after the syscall returns, so we cannot restart the
>>> 2213: 		 * IO from that context.  AIO EC recovery is not yet
    2214: 		 * implemented.
    2215: 		 */
    2216: 		if (rc && iot == CIT_READ && io->ci_cross_ec &&
    2217: 		    !io->ci_switch_ec_io) {
    2218: 			CDEBUG(D_VFSTRACE,
```
**Check (AI, 2026-09-27, verdict in-followup):** The ticket was filed, but no commit on master names LU-20566 (tickets/LU-20566.txt). master/lustre/llite/file.c:2222-2225 still says 'AIO EC recovery is not yet implemented.' Candidate 67878 (NEW, LU-20566) rewrites that comment and adds DIO recovery using transient pages, but its commit message says 'AIO EC recovery remains unsupported.' The AIO part is only in 65219 (NEW, LU-12669), which makes AIO reads on EC files run synchronously so they can be recovered (65219.diff:68-92). 67998 (NEW, LU-20566) reuses surviving DIO bounce pages. None of these has merged.
**Suggested action:** Land 67878/67998 under LU-20566 for DIO EC recovery. Also land 65219, or equivalent AIO recovery, and link it to LU-20566 so the ticket covers both DIO and AIO before 2.18.0.
**Also raised in:** 62757-b5a2bc02 (change-level, PS92)
**In flight:** change 67878 (NEW): LU-20566 ec: recover O_DIRECT reads with transient pages
**Possible follow-ups:** 67998 (NEW, names LU-20566), 67878 (NEW, names LU-20566)
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/9b234885_fa33f403/

### 62757-c40ee645 — lustre/lov/lov_io.c:2168 [severity: low]
**Summary:** Clear cp_sync_io on all exit paths (addressed in follow-up change)
**Origin:** comment on PS112, thread resolved; promise by Marc Vef: "Addressed in followup: 67878 (LU-20566, higher in the stack) replaces the block with lov_ec_submit_transient, which clears it on every path."
**Thread:**
> [Gerrit AI review for Lustre, 2026-09-08] (minor) cp_sync_io is set here but never cleared again on any path out of this function. cl_io_submit_sync() clears it on both the success and the failure path:
> 
>     cl_page_list_for_each(pg, &queue->c2_qin) {
>         pg->cp_sync_io = NULL;
>         cl_sync_io_note(env, anchor, 1);
>     }
> 
> Any page that osc_io_submit() leaves behind in c2_qin is noted manually below and then spliced into @plist, so it reaches cl_page_list_fini() still holding a pointer to this stack-allocated @anchor. Nothing dereferences it today because such a page was never submitted, but mirroring the cl_io_submit_sync() handling would remove the stale pointer.
> [Marc Vef, 2026-09-08] Addressed in followup: 67878 (LU-20566, higher in the stack) replaces the block with lov_ec_submit_transient, which clears it on every path.
**Code context:**
```
    2163: 	 */
    2164: 	queue = &lov_env_info(sub->sub_env)->lti_cl2q;
    2165: 	cl_2queue_init(queue);
    2166: 	for (j = 0; j < npages; j++) {
    2167: 		out_clpages[j]->cp_sync_io = &anchor;
>>> 2168: 		cl_page_list_add(&queue->c2_qin, out_clpages[j], false);
    2169: 	}
    2170: 
    2171: 	cl_sync_io_init(&anchor, npages);
    2172: 	rc = cl_io_submit_rw(sub->sub_env, &sub->sub_io, CRT_READ, queue);
    2173: 	if (rc == 0) {
```
**Check (AI, 2026-09-27, verdict in-followup):** Not fixed in the current patchset or on master. change/lustre/lov/lov_io.c:2167 and master/lustre/lov/lov_io.c:2167 still have `out_clpages[j]->cp_sync_io = &anchor;`, and nothing in that function clears it afterwards. The fix is only in candidates/67878.diff:462-493, which is still NEW (unmerged). It adds lov_ec_submit_transient(), which sets cp_sync_io to NULL for pages left in qin before cl_sync_io_note (line 480), for qout pages after cl_sync_io_wait (line 485), and for qin pages when submit fails (line 489). Hunk 931-960 in the same diff removes the old inline block and calls the new helper instead. The review comment was correct: the old code left a stale pointer to the on-stack anchor on the pages. It is minor because nothing reads that pointer today.
**Suggested action:** Land 67878 (lov_ec_submit_transient). If it stalls, clear cp_sync_io on every qin, qout and error path in lov_ec_read_parity_stripe(), the way cl_io_submit_sync() does.
**In flight:** change 67878 (NEW): LU-20566 ec: recover O_DIRECT reads with transient pages
**Possible follow-ups:** 67878 (NEW, linked in the thread)
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/c40ee645_43feb712/

### 62757-d6703eee — (change-level) [severity: low]
**Summary:** Address remaining EC/test issues in later follow-up patches
**Origin:** comment on PS110, thread resolved; promise by Marc Vef: "There are some issues that are addressed in later patches (some are already inflight)."
**Thread:**
> [Marc Vef, 2026-08-26] All tests are passing. There are some issues that are addressed in later patches (some are already inflight). The remaining issues pointed out are either in the EC part or test related. Both are addressed in a later patch. Therefore, I think this patch should be landed.
> 
> I'm keeping track of the comments so none get lost.
**Check (AI, 2026-09-27, verdict in-followup):** The promise doesn't name the issues. It only says they are 'in the EC part or test related', and the comments it refers to aren't in this thread, so I can't check each one. What I can see: all the follow-ups that fit are still open (status NEW) and stacked on 62757. 68339 serializes concurrent EC recovery readers, 66481 fixes EC recovery reads for encrypted files, 68453 adds lock placeholders for rebuilt pages, 65219 makes AIO reads on EC files synchronous, 67878 and 67998 fix O_DIRECT recovery, and 64921 adds the EC failure test orchestrator. The stacked changes that did merge (63809, 66113, 64620) are about the stripe allocator and lfs, not these EC/test fixes. The author says he is tracking the comments himself, so I can't confirm the open patches cover every item.
**Suggested action:** Write down the unresolved PS110 review comments and link each one to its follow-up (64921, 68339, 66481, 68453, 65219, 67878). Then land those changes, or file an LU ticket for any comment that no follow-up covers.
**Also raised in:** 62757-d2885d10 (change-level, PS110)
**In flight:** change 64921 (NEW): LU-12668 tests: add EC failure orchestrator
**Gerrit:** https://review.whamcloud.com/c/fs/lustre-release/+/62757/comment/d6703eee_25e4baa0/


## Kept (21)

- **62757-6eca2ddb** (change-level) — Fix LU-19536 (change 64359) which removed code needed for EC transient-page IO, causing crashes, instead of this patch (kept in change 64359 — 64359 was reworked (Marc Vef co-signed) to keep the CPT_TRANSIENT branch in osc_io_submit() that skips cl_page_prep(), and merged in that form)
- **62757-7fadcf08** lustre/tests/sanity-ec.sh:29 — Fix or file tickets for the mmap-read hang and the intermittent wrong-data reconstruction bugs in the disabled EC subtests (kept in patchset 113 — The shared LU-12668 exclusion was split into two new tickets: 41d is now under LU-20708 and 53a under LU-20709)
- **62757-801a75d5** lustre/lov/lov_io.c:1867 — Add bounds check/LASSERT on k against MAX_OST_UNAVAIL to prevent stack buffer overflow (kept in patchset 113 — The stack array indexed by k is gone. Recovery arrays are now heap-allocated to LOV_EC_MAX_CODING_STRIPES, with an explicit bounds check and a LASSERT.)
- **62757-974bfc5c** (change-level) — Fix reference leak during read reconstruction causing client OOM (kept in patchset 113 — The merged read-reconstruction path now drops the extra page-cache (vmpage) reference and releases cl_page refs on every error path.)
- **62757-c99b9df0** lustre/obdclass/cl_page.c:375 — Verify that NULL cp_vmpage on parity pages is safe through all downstream cl_page code paths (kept in patchset 113 — Parity pages now get a real vmpage from alloc_page(GFP_NOFS) instead of NULL, so cp_vmpage is never NULL)
- **62757-0a801803** lustre/lov/lov_io.c:2858 — EAGAIN path can silently return EOF instead of error on unreadable stripe (kept in patchset 113 — CIT_EC_RD in lov_io_mirror_init() jumps to ndelay_retry, so ci_ndelay_tried goes up and ci_tried_all_mirrors is eventually set; cl_io_loop() then turns -EAGAIN into -EIO)
- **62757-fa183cc8** COMMIT_MSG:48 — Fix lov_io_rw_iter_init() LASSERTF that never actually validates CIT_EC_RD iter_init path (kept in patchset 113 — The widened LASSERTF was dropped. lov_io_rw_iter_init() now asserts only CIT_READ or CIT_WRITE.)
- **62757-78cf8c08** lustre/llite/file.c:2182 — Fix or document that true AIO O_DIRECT reads on EC files get no reconstruction at all (kept in patchset 113 — The misleading comment was rewritten to say that true AIO EC recovery is not implemented and only sync DIO is handled.)
- **62757-b3d9b1fa** lustre/llite/vvp_io.c:1877 — Add cio_fini for CIT_EC_RD so ci_restore_needed / layout restore actually run (kept in patchset 113 — CIT_EC_RD io ops now set .cio_fini = vvp_io_fini)
- **62757-bcf46539** lustre/lov/lov_io.c:2116 — Fix wrong stripe index computation for multi-raid-set EC layouts (parity_foff % pcount) (kept in patchset 113 — The stripe is now computed modulo r0->lo_nr, which is the parity component's lsme_stripe_count, instead of pcount.)
- **62757-34f2b48f** lustre/lov/lov_io.c:379 — Fix wrong error code -EINVAL to -EIO (or fallback) for unavailable EC data OST (kept in patchset 113 — The CIT_EC_RD branch that finds no updated parity component now returns -EIO instead of -EINVAL, and the warning includes the rc.)
- **62757-bada4a43** lustre/lov/lov_io.c:2138 — Fix uninitialized 'raid_set' symbol in lov_io_ec_rd_start (kept in patchset 113 — In lov_io_ec_rd_start, raid_set is now declared as 'unsigned int raid_set = 0;', so it is always initialized.)
- **62757-6d2aef7e** (change-level) — Verify the assumption that the data component is always one before the EC component is safe (kept in patchset 113 — Parity is now matched to its data component by mirror_link_id and an exact extent match, not by the component that sits just before it.)
- **62757-0931272e** (change-level) — Clarify/verify EC reconstruction handles multiple simultaneous OST/stripe failures and document reconstruction-trigger timeout logic (kept in patchset 113 — Merged commit 783c13bb0b8f explains how whole-RG recovery works and what triggers it, and the code handles up to pcount failures while skipping parity stripes that cannot be read)
- **62757-ffe35467** lustre/lov/lov_lock.c:172 — fix RAID-set math for short RAID sets (kept in patchset 113 — The parity-lock extent math was removed from lov_lock.c. Raid-set boundaries now come from ec_split_stripes(), which handles two raid-set sizes (k0 and k1), so short raid sets are covered.)
- **62757-74efec3f** lustre/lov/lov_io.c:2029 — Handle possible NULL 'ptrs' pointer in lov_io_ec_rd_start (kept in patchset 113 — The cleanup path in lov_io_ec_rd_start now checks state.ecr_ptrs and state.ecr_recov_ptrs for NULL before indexing them.)
- **62757-b85b74f4** lustre/lov/lov_io.c:2031 — Handle possible NULL 'recov_ptrs' pointer (kept in patchset 113 — The cleanup path now checks that state.ecr_recov_ptrs is non-NULL before it walks the array and unmaps entries.)
- **62757-bf3b4d96** lustre/tests/sanity-ec.sh:29 — Use separate tracking ticket(s) for excepted/skipped EC tests instead of the main LU-12668 tracker (kept in patchset 113 — The excepted tests now use their own tickets: 12a→LU-19631, 41d→LU-20708, 53a→LU-20709; none use LU-12668 anymore.)
- **62757-e1e4e7a5** lustre/tests/sanity-ec.sh:32 — Point excluded-test comments at LU-20566 (AIO EC recovery gap) instead of LU-12669 (kept in patchset 113 — sanity-ec.sh now uses always_except LU-20566 for 41j-41n; the change is merged to master)
- **62757-2f65925b** lustre/lov/lov_io.c:2135 — Avoid unbounded stack usage for large dcount arrays (kept in patchset 113 — The stack array err_array[MAX_OST_UNAVAIL] and all arrays sized by dcount now live in heap buffers allocated by lov_ec_bufs_alloc().)
- **62757-0a8220ed** lustre/llite/vvp_io.c:1944 — Remove redundant vvp_io_set_range() call/field already set in vvp_io_init (kept in patchset 113 — vvp_io_set_range() was removed; vui_tot_bytes is now set only in vvp_io_init, including for CIT_EC_RD)

## Set aside (1)

- **62757-9fd7144a** (change-level) — Pull the sanity-ec ZFS seek-hole test skip out into a separate patch if the allocator dependency delays landing (kept in change 63809 — The raidset-aware stripe allocator patch that carries the ZFS seek-hole skip for sanity-ec is MERGED, so splitting the skip out is moot)
