bdev/nvme: fail I/O over to another path on generic errors#58
Open
schmidt-scaled wants to merge 1 commit into
Open
bdev/nvme: fail I/O over to another path on generic errors#58schmidt-scaled wants to merge 1 commit into
schmidt-scaled wants to merge 1 commit into
Conversation
When an I/O completes with a non-path (generic) NVMe error on a path
whose qpair and controller still look healthy, bdev_nvme_check_retry_io()
retried it on the *same* path and charged the attempt against the shared
bdev_retry_count budget (default 3). In a multipath configuration where
each path leads to an independent controller, such an error is frequently
specific to the failing path/controller while other paths are healthy
(e.g. a target node is going down but its connection has not been torn
down yet, so the completion arrives before the qpair is destroyed and is
not classified as a path error).
The result is that the retries burn the same budget the cross-path
failover also needs: with a fast-failing dead path the counter reaches
bdev_retry_count before the controller reset marks the qpair down, and
the I/O is failed upward even though a healthy alternate path existed the
whole time.
Add a bounded failover path for this case:
- On a retryable non-path error, if a *different* io_path is available,
fail the I/O over to it instead of retrying the same path. This is
counted against a new per-I/O path_failover_count, not retry_count,
so dead-end attempts do not consume the same-path retry budget.
- Skip the just-failed path on the next selection (io_path_to_avoid),
since otherwise active-passive re-selection would simply re-pick the
same still-"available" path and never reach the alternate. If the
avoided path turns out to be the only usable one, fall back to it
rather than failing the I/O.
- Bound the number of failovers by the number of io_paths, and only
fail over while another path is available, so a genuinely
deterministic error (e.g. a media error returned identically by every
path) still terminates: once every path has been tried the I/O falls
through to the existing same-path retry branch and is reported to the
upper layer as before.
Single-path configurations are unaffected: with no other available path
the failover branch is skipped and behavior is identical to before.
The queue-depth (min_qd) selector is intentionally left unchanged; it
already balances by outstanding-request count and the reported issue is
specific to the active-passive / round-robin selectors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
bdev/nvme: fail I/O over to another path on generic errors
Problem
In a multipath setup where each path leads to an independent controller (primary / secondary / tertiary over a distributed backend), an I/O can complete with a non-path (generic) NVMe error on a path whose qpair and controller still look healthy — the completion arrives before the qpair is torn down, so it is not classified as a path error.
bdev_nvme_check_retry_io()handled this in itselsebranch by retrying on the same path and charging the attempt against the sharedbdev_retry_countbudget (default 3).The reroute decision hinges entirely on
spdk_nvme_cpl_is_path_error(), which is juststatus.sct == SPDK_NVME_SCT_PATH. A path that is dying but reports a generic status (and whose qpair/ctrlr liveness flags have not flipped yet) never trips that check, so:bdev_nvme_io_complete_nvme_status()(give-up) and the-ENXIOreroute inbdev_nvme_io_complete()gate onretry_count < bdev_retry_count;retry_countreachesbdev_retry_countbefore the controller reset marks the qpair down;Observed in a tertiary (
lvs_31) hub-lvol topology: one I/O landed on the primary connection to a node that had just gone offline, completed in failure before the qpair was destroyed, triggered a controller reset, and was aborted upward instead of being retried on the healthy secondary.Fix
Add a bounded, path-aware failover branch to
bdev_nvme_check_retry_io():io_pathis available, fail the I/O over to it.path_failover_count, notretry_count, so dead-end attempts no longer consume the same-path retry budget.io_path_to_avoidis honored by_bdev_nvme_find_io_path()for exactly one (synchronous) selection — otherwise active-passive would just re-pick the same still-"available" path and never reach the alternate. If the avoided path turns out to be the only usable one, it falls back to it rather than failing the I/O.io_paths, and we only fail over while another path is available. A genuinely deterministic error (e.g. a media error returned identically by every path) still terminates: once every path has been tried the I/O falls through to the existing same-path retry branch and is reported upward as before.Safety / scope
DNR/ aborted-by-request / accel-sequence (still filtered by the caller) and hard path errors (unchanged branch).io_path_to_avoidon the channel is a transient hint, set and cleared around a single synchronousfind_io_path()call inbdev_nvme_submit_request(), so it can never leak into another I/O's selection.Files
module/bdev/nvme/bdev_nvme.h— transientio_path_to_avoidonnvme_bdev_channel.module/bdev/nvme/bdev_nvme.c—path_failover_count+io_path_to_avoidonnvme_bdev_io;bdev_nvme_num_io_paths()/bdev_nvme_another_io_path_is_available()helpers; skip logic in_bdev_nvme_find_io_path(); failover branch inbdev_nvme_check_retry_io(); hint plumbing inbdev_nvme_submit_request(); field resets on completion.Testing
makein a Linux SPDK tree) — not run in the authoring environment.test/unit/lib/bdev/nvme/bdev_nvme.c/bdev_nvme_ut— the existing "generic error retries on the same path with two paths" expectations will change and need updating to assert failover + counter separation.bdev_retry_countis not exhausted by the dead path.