From: sashiko-bot@kernel.org
To: "Mohamed Khalfella" <mkhalfella@purestorage.com>
Cc: linux-nvme@lists.infradead.org
Subject: Re: [PATCH v7 12/17] nvme-fc: start error recovery instead of aborting timed out IOs
Date: Sat, 14 Nov 2026 03:46:45 +0000 [thread overview]
Message-ID: <sashiko-outbox-214127@kernel.org> (raw)
In-Reply-To: <20261114030454.2836543-13-mkhalfella@purestorage.com>
Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [High] nvme-fc: ignored errors in CONNECTING state allow broken
association to go LIVE
- [Medium] nvme-fc: nvme_fc_timeout() leaves queue live after non-LIVE
command abort
- [Medium] nvme-fc: missing abort drain before queue deletion in connect
error paths
- [Low] nvme-fc: missing Fixes trailer for commit 52793d62a696 in commit
message
--
commit 0a8f4b079afa7a27de6edf488747423c0c7e82ab
Author: Mohamed Khalfella <mkhalfella@purestorage.com>
> nvme-fc: start error recovery instead of aborting timed out IOs
>
> Aborts issued from the timeout handler run outside the FCCTRL_TERMIO
> window, so they are not counted in ctrl->iocnt and
> nvme_fc_delete_association() does not wait for them. The association
> can be torn down while the LLDD is still working on the abort.
>
> Instead of aborting the timed out command, reset the controller like
> the other fabrics transports do. The association teardown aborts the
> outstanding IOs, counts the aborts and waits for them to complete.
>
> Count every abort in ctrl->iocnt, not only the ones issued while
> FCCTRL_TERMIO is set, and do not reset the count when the teardown
> starts. An abort issued before the teardown is then waited for as well.
>
> Only a LIVE controller starts error recovery. In every other state the
> association is being set up, being torn down or already gone, so abort
> the timed out command from the timeout handler instead and return
> BLK_EH_DONE. The command completes through nvme_fc_fcpio_done() once
> the LLDD has finished the abort, and the abort is counted so a teardown
> that follows waits for it. This matters for a controller being deleted.
> The delete path drains the IOs itself and tears the association down in
> nvme_fc_delete_ctrl() once the drain is done, and DELETING cannot move
> to RESETTING, so without the abort a timed out IO would hang the drain
> against a dead target.
>
> The new nvme_fc_start_ioerr_recovery() is the common entry point for IO
> timeouts, transport errors reported by nvme_fc_fcpio_done(), Disconnect
> LS and connectivity loss. It moves the controller to RESETTING and
> queues ioerr_work only if that succeeds, so nothing is ever queued on a
> controller that is CONNECTING or being deleted. A Disconnect LS or a
> transport error received while CONNECTING is resolved when the inflight
> request fails, or times out and is aborted.
>
> ioerr_work still calls nvme_fc_error_recovery(), which returns without
> doing anything for a controller that is not LIVE. With the controller
> already in RESETTING when the work runs, no recovery takes place yet
> and the controller stays in RESETTING. The next patch moves the
> recovery into ioerr_work itself and completes the change.
>
> Link: https://lore.kernel.org/all/20250529214928.2112990-1-mkhalfella@purestorage.com/
> Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com>
[Severity: Low]
Should this bug fix include a Fixes tag for the commit that gated
ctrl->iocnt accounting on the FCCTRL_TERMIO flag:
Fixes: 52793d62a696 ("nvme-fc: fix io timeout to abort I/O")
> diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
> index 0b9ff50d26ff3..c4603be381445 100644
> --- a/drivers/nvme/host/fc.c
> +++ b/drivers/nvme/host/fc.c
[ ... ]
> @@ -1569,7 +1571,8 @@ nvme_fc_ls_disconnect_assoc(struct nvmefc_ls_rcv_op *lsop)
> */
>
> /* fail the association */
> - nvme_fc_error_recovery(ctrl, "Disconnect Association LS received");
> + nvme_fc_start_ioerr_recovery(ctrl,
> + "Disconnect Association LS received");
>
> /* release the reference taken by nvme_fc_match_disconn_ls() */
> nvme_fc_ctrl_put(ctrl);
> @@ -1813,11 +1816,18 @@ __nvme_fc_abort_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_fcp_op *op)
> unsigned long flags;
> int opstate;
>
> + /*
> + * Every abort is counted, not only those issued by the association
> + * teardown: the teardown waits for iocnt to drop to zero before it
> + * deletes the hw queues, and an abort issued earlier, by
> + * nvme_fc_timeout() while deleting or by the CONNECTING recovery
> + * path, must not still be in progress in the LLDD then.
> + */
> spin_lock_irqsave(&ctrl->lock, flags);
> opstate = atomic_xchg(&op->state, FCPOP_STATE_ABORTED);
> - if (opstate != FCPOP_STATE_ACTIVE)
> + if (opstate != FCPOP_STATE_ACTIVE) {
> atomic_set(&op->state, opstate);
> - else if (test_bit(FCCTRL_TERMIO, &ctrl->flags)) {
> + } else {
> op->flags |= FCOP_FLAGS_TERMIO;
> ctrl->iocnt++;
> }
[Severity: Medium]
Can the connect failure unwind paths in nvme_fc_create_association(),
nvme_fc_create_io_queues(), and nvme_fc_recreate_io_queues() delete
hardware queues while an abort or asynchronous request is still active in
the lower-level driver?
Previously, a transport error or Disconnect Association link service while
connecting ran nvme_fc_error_recovery() ->
__nvme_fc_abort_outstanding_ios(), which quiesced the queues, aborted busy
requests on both tagsets, and waited for completion. With
nvme_fc_start_ioerr_recovery() becoming a no-op while connecting and
nvme_fc_timeout() incrementing ctrl->iocnt for timed-out commands, an
in-flight keep-alive command (started by nvme_init_ctrl_finish()) or an
abort from nvme_fc_timeout() can still be running when a later connect step
fails. The error labels in nvme_fc_create_io_queues() and
nvme_fc_create_association() call nvme_fc_delete_hw_io_queues() and
__nvme_fc_delete_hw_queue() and clear the active association flag without
aborting outstanding requests or waiting on ctrl->ioabort_wait for
ctrl->iocnt to reach zero:
drivers/nvme/host/fc.c:nvme_fc_create_association() {
...
out_delete_hw_queue:
__nvme_fc_delete_hw_queue(ctrl, &ctrl->queues[0], 0);
out_free_queue:
nvme_fc_free_queue(&ctrl->queues[0]);
clear_bit(ASSOC_ACTIVE, &ctrl->flags);
...
}
Because the active association flag is cleared there, even a subsequent
nvme_fc_delete_association() returns immediately without waiting for
ctrl->iocnt to drop to zero.
[ ... ]
> @@ -1892,6 +1901,19 @@ char *nvme_fc_io_getuuid(struct nvmefc_fcp_req *req)
> }
> EXPORT_SYMBOL_GPL(nvme_fc_io_getuuid);
>
> +/*
> + * Start recovery only if this caller can claim it with a state change.
> + */
> +static void
> +nvme_fc_start_ioerr_recovery(struct nvme_fc_ctrl *ctrl, char *errmsg)
> +{
> + if (nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_RESETTING)) {
> + dev_warn(ctrl->ctrl.device, "NVME-FC{%d}: starting error recovery %s\n",
> + ctrl->cnum, errmsg);
> + queue_work(nvme_reset_wq, &ctrl->ioerr_work);
> + }
> +}
> +
> static void
> nvme_fc_fcpio_done(struct nvmefc_fcp_req *req)
> {
> @@ -2049,9 +2071,8 @@ nvme_fc_fcpio_done(struct nvmefc_fcp_req *req)
> nvme_fc_complete_rq(rq);
>
> check_error:
> - if (terminate_assoc &&
> - nvme_ctrl_state(&ctrl->ctrl) != NVME_CTRL_RESETTING)
> - queue_work(nvme_reset_wq, &ctrl->ioerr_work);
> + if (terminate_assoc)
> + nvme_fc_start_ioerr_recovery(ctrl, "io error");
> }
[Severity: High]
Can nvme_fc_create_association() transition a broken or disconnected
association to the live state when a timeout, transport error, or
Disconnect Association link service occurs while connecting?
While ctrl->ctrl.state is connecting, nvme_change_ctrl_state() rejects a
transition to resetting, so nvme_fc_start_ioerr_recovery() is a no-op and
neither sets the association failed flag nor calls
__nvme_fc_abort_outstanding_ios() to clear the queue live bit and mark
requests cancelled:
1. When an admin command times out in nvme_fc_timeout() while connecting,
__nvme_fc_abort_op() aborts the command without setting the cancelled
request flag on op->nreq.flags. nvme_fc_fcpio_done() then completes the
request with positive status NVME_SC_HOST_ABORTED_CMD (0x371) instead of
-EINTR. If that command is nvme_set_queue_count() in
nvme_fc_create_io_queues(), nvme_set_queue_count() treats positive status
as non-fatal, sets nr_io_queues to 0, and returns 0, allowing
nvme_fc_create_association() to transition the controller to live with zero
I/O queues.
2. Likewise, optional commands in nvme_init_ctrl_finish() (such as
nvme_get_effects_log() or nvme_configure_apst()) and asynchronous
keep-alive commands ignore positive status codes from transport errors or
aborts, and a Disconnect Association request received in
nvme_fc_ls_disconnect_assoc() after ctrl->association_id is set stores
ctrl->rcv_disconn and calls nvme_fc_start_ioerr_recovery() without setting
the association failed flag. When the remaining setup commands finish,
nvme_fc_create_association() sees no error, moves the controller to live,
and leaves ctrl->rcv_disconn unanswered:
drivers/nvme/host/fc.c:nvme_fc_create_association() {
...
if (!ret && test_bit(ASSOC_FAILED, &ctrl->flags))
ret = -EIO;
if (ret)
goto out_term_aen_ops;
if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_LIVE)) {
...
}
> @@ -2548,24 +2569,32 @@ static enum blk_eh_timer_return nvme_fc_timeout(struct request *rq)
> struct nvme_fc_cmd_iu *cmdiu = &op->cmd_iu;
> struct nvme_command *sqe = &cmdiu->sqe;
>
> - /*
> - * Attempt to abort the offending command. Command completion
> - * will detect the aborted io and will fail the connection.
> - */
> dev_info(ctrl->ctrl.device,
> "NVME-FC{%d.%d}: io timeout: opcode %d fctype %d (%s) w10/11: "
> "x%08x/x%08x\n",
> ctrl->cnum, qnum, sqe->common.opcode, sqe->fabrics.fctype,
> nvme_fabrics_opcode_str(qnum, sqe),
> sqe->common.cdw10, sqe->common.cdw11);
> - if (__nvme_fc_abort_op(ctrl, op))
> - nvme_fc_error_recovery(ctrl, "io timeout abort failed");
> +
> + if (nvme_ctrl_state(&ctrl->ctrl) != NVME_CTRL_LIVE) {
> + /*
> + * The association is being set up, being torn down or is
> + * already gone, so recovery cannot be started from here.
> + * Abort the command so the connect or teardown it would
> + * block can make progress. It completes through
> + * nvme_fc_fcpio_done() once the LLDD has finished the
> + * abort, and the abort is counted so a teardown that
> + * follows waits for it.
> + */
> + __nvme_fc_abort_op(ctrl, op);
> + return BLK_EH_DONE;
> + }
[Severity: Medium]
Does aborting a timed-out request here without clearing the queue live flag
or marking the request cancelled allow the request to be retried against a
dead target while deleting?
During controller deletion, nvme_do_delete_ctrl() runs
nvme_remove_namespaces() before nvme_fc_delete_ctrl() ->
nvme_fc_delete_association(), so the queue live flag is still set on
op->queue->flags. When the abort finishes, nvme_fc_fcpio_done() completes
the request with NVME_SC_HOST_ABORTED_CMD (without do-not-retry), so
non-multipath requests (such as del_gendisk() writeback before the queue is
marked dying) or retryable internal commands are retried by
nvme_decide_disposition(). Because op->queue->flags still has the queue
live bit set, nvme_check_ready() returns true in nvme_fc_queue_rq() and
re-dispatches the request to the dead target for up to nvme_max_retries
additional timeouts instead of fast-failing via
nvme_fail_nonready_command().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261114030454.2836543-1-mkhalfella@purestorage.com?part=12
next prev parent reply other threads:[~2026-11-14 3:46 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 3:04 [PATCH v7 00/17] TP8028 Rapid Path Failure Recovery Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 01/17] nvmet: Rapid Path Failure Recovery set controller identify fields Mohamed Khalfella
2026-11-14 3:17 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 02/17] nvmet/debugfs: Export controller CIU and CIRN via debugfs Mohamed Khalfella
2026-11-14 3:12 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 03/17] nvmet: Implement CCR nvme command Mohamed Khalfella
2026-11-14 3:30 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 04/17] nvmet: Implement CCR logpage Mohamed Khalfella
2026-11-14 3:20 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 05/17] nvmet: Send an AEN on CCR completion Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 06/17] nvme: Rapid Path Failure Recovery read controller identify fields Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 07/17] nvme: Introduce FENCING and FENCED controller states Mohamed Khalfella
2026-11-14 3:29 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 08/17] nvme: Implement cross-controller reset recovery Mohamed Khalfella
2026-11-14 3:26 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 09/17] nvme: Implement cross-controller reset completion Mohamed Khalfella
2026-11-14 3:29 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 10/17] nvme-tcp: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-11-14 3:38 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 11/17] nvme-rdma: " Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 12/17] nvme-fc: start error recovery instead of aborting timed out IOs Mohamed Khalfella
2026-11-14 3:46 ` sashiko-bot [this message]
2026-11-14 3:04 ` [PATCH v7 13/17] nvme-fc: perform error recovery directly from ioerr_work Mohamed Khalfella
2026-11-14 3:31 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 14/17] nvme-fc: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-11-14 3:38 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 15/17] nvme-fc: Hold inflight requests while in FENCING state Mohamed Khalfella
2026-11-14 4:06 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 16/17] nvmet: Add support for CQT to nvme target Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 17/17] nvme: Add support for CQT to nvme host Mohamed Khalfella
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-214127@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=mkhalfella@purestorage.com \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox