Linux-NVME Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Guixin Liu" <kanie@linux.alibaba.com>
Cc: linux-nvme@lists.infradead.org
Subject: Re: [PATCH v2 1/3] nvme-multipath: implement service-time iopolicy
Date: Mon, 19 Oct 2026 08:14:50 +0000	[thread overview]
Message-ID: <sashiko-outbox-176784@kernel.org> (raw)
In-Reply-To: <20261019075607.808947-2-kanie@linux.alibaba.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] nvme-multipath: in_flight_size leak in nvme_mpath_end_request() on failover
- [Medium] nvme-multipath: in_flight_size underflow wraps in nvme_st_compare_load()
--

commit 211891d7cca0785689865f3a7650f5305abaef90
Author: Guixin Liu <kanie@linux.alibaba.com>

nvme-multipath: implement service-time iopolicy

This commit adds a service-time multipath I/O policy that selects paths
based on the ratio of in-flight bytes to a configurable relative throughput
weight, exposing in_flight_bytes and relative_throughput sysfs attributes.

> diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
> index 75dbb58286a32..3901654d8226b 100644
> --- a/drivers/nvme/host/multipath.c
> +++ b/drivers/nvme/host/multipath.c
[ ... ]
> @@ -179,11 +180,16 @@ void nvme_mpath_start_request(struct request *rq)
>  {
>  	struct nvme_ns *ns = rq->q->queuedata;
>  	struct gendisk *disk = ns->head->disk;
> +	int iopolicy = READ_ONCE(ns->head->subsys->iopolicy);
>  
> -	if ((READ_ONCE(ns->head->subsys->iopolicy) == NVME_IOPOLICY_QD) &&
> +	if (iopolicy == NVME_IOPOLICY_QD &&
>  	    !(nvme_req(rq)->flags & NVME_MPATH_CNT_ACTIVE)) {
>  		atomic_inc(&ns->ctrl->nr_active);
>  		nvme_req(rq)->flags |= NVME_MPATH_CNT_ACTIVE;
> +	} else if (iopolicy == NVME_IOPOLICY_ST &&
> +		   !(nvme_req(rq)->flags & NVME_MPATH_CNT_ACTIVE_BYTES)) {
> +		atomic64_add(blk_rq_bytes(rq), &ns->ctrl->in_flight_size);
> +		nvme_req(rq)->flags |= NVME_MPATH_CNT_ACTIVE_BYTES;
>  	}
>  
>  	if (!blk_queue_io_stat(disk->queue) ||
> @@ -205,6 +211,10 @@ void nvme_mpath_end_request(struct request *rq)
>  
>  	if (nvme_req(rq)->flags & NVME_MPATH_CNT_ACTIVE)
>  		atomic_dec_if_positive(&ns->ctrl->nr_active);
> +	if (nvme_req(rq)->flags & NVME_MPATH_CNT_ACTIVE_BYTES) {
> +		atomic64_sub(blk_rq_bytes(rq), &ns->ctrl->in_flight_size);
> +		nvme_req(rq)->flags &= ~NVME_MPATH_CNT_ACTIVE_BYTES;
> +	}

[Severity: Medium]
Can this leak bytes in ns->ctrl->in_flight_size when a request fails over to
another path?

When a request fails with a path error (such as NVME_SC_ANA_TRANSITION,
NVME_SC_ANA_INACCESSIBLE, NVME_SC_ANA_PERSISTENT_LOSS,
NVME_SC_HOST_PATH_ERROR, or NVME_SC_HOST_ABORTED_CMD) or on a dying queue,
__nvme_complete_rq() calls nvme_failover_req():

drivers/nvme/host/multipath.c:nvme_failover_req() {
	...
	spin_lock_irqsave(&ns->head->requeue_lock, flags);
	for (bio = req->bio; bio; bio = bio->bi_next)
		bio_set_dev(bio, ns->head->disk->part0);
	blk_steal_bios(&ns->head->requeue_list, req);
	spin_unlock_irqrestore(&ns->head->requeue_lock, flags);

	nvme_req(req)->status = 0;
	nvme_end_req(req);
}

blk_steal_bios() detaches all bios from the request and sets
req->__data_len = 0 before nvme_end_req() -> __nvme_end_req() ->
nvme_mpath_end_request() is called:

block/blk-mq.c:blk_steal_bios() {
	...
	rq->bio = NULL;
	rq->biotail = NULL;
	rq->__data_len = 0;
}

When nvme_mpath_end_request() subsequently calls blk_rq_bytes(rq), it reads
rq->__data_len as 0 and subtracts 0 from ns->ctrl->in_flight_size while
clearing NVME_MPATH_CNT_ACTIVE_BYTES. Whenever failover occurs without a
controller reset (for example during an ANA state transition, or on a
multipath controller without NVME_CTRL_CMIC_ANA where
nvme_mpath_init_identify() returns early), those bytes remain permanently
counted in ns->ctrl->in_flight_size, skewing path selection in
nvme_service_time_path() and leaving in_flight_bytes non-zero when idle.

>  
>  	if (!(nvme_req(rq)->flags & NVME_MPATH_IO_STATS))
>  		return;
> @@ -464,6 +474,109 @@ static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head)
>  	return best_opt ? best_opt : best_nonopt;
>  }
>  
> +#define ST_MAX_RELATIVE_THROUGHPUT	100
> +#define ST_MAX_RELATIVE_THROUGHPUT_SHIFT	7
> +#define ST_MAX_INFLIGHT_SIZE \
> +	((size_t)-1 >> ST_MAX_RELATIVE_THROUGHPUT_SHIFT)
> +
> +/*
> + * Compare estimated service time of two paths.
> + * Returns negative if ns1 is better, positive if ns2 is better, 0 if equal.
> + *
> + * Service time = in_flight_size / relative_throughput
> + * Cross-multiply to avoid division.
> + */
> +static int nvme_st_compare_load(struct nvme_ns *ns1, struct nvme_ns *ns2)
> +{
> +	size_t sz1, sz2, st1, st2;
> +	unsigned int tp1 = ns1->ctrl->relative_throughput;
> +	unsigned int tp2 = ns2->ctrl->relative_throughput;
> +
> +	sz1 = atomic64_read(&ns1->ctrl->in_flight_size);
> +	sz2 = atomic64_read(&ns2->ctrl->in_flight_size);

[Severity: Medium]
What happens here if ns->ctrl->in_flight_size underflows below zero after a
controller reset?

When a request is started under NVME_IOPOLICY_ST, nvme_setup_cmd() calls
nvme_clear_nvme_request() (which sets RQF_DONTPREP), and
nvme_mpath_start_request() adds blk_rq_bytes(rq) to ns->ctrl->in_flight_size
and sets NVME_MPATH_CNT_ACTIVE_BYTES.

If that request is then placed onto q->requeue_list in MQ_RQ_IDLE state
without calling nvme_mpath_end_request() (either via nvme_retry_req() ->
blk_mq_requeue_request(), or when a transport queue_rq callback such as
nvme_rdma_queue_rq(), nvme_fc_start_fcp_op(), or nvme_loop_queue_rq()
returns BLK_STS_RESOURCE after nvme_start_request()) and a controller reset
occurs:

1. nvme_cancel_tagset() -> nvme_cancel_request() skips the request because
   blk_mq_rq_state(req) != MQ_RQ_IN_FLIGHT.
2. nvme_mpath_init_identify() resets ctrl->in_flight_size to 0 via
   atomic64_set(&ctrl->in_flight_size, 0).
3. When the queue is unquiesced and the request is re-dispatched,
   nvme_setup_cmd() preserves nvme_req(req)->flags because RQF_DONTPREP is
   set, and nvme_mpath_start_request() skips adding blk_rq_bytes(rq) because
   NVME_MPATH_CNT_ACTIVE_BYTES is already set.
4. When the request completes, nvme_mpath_end_request() unconditionally
   executes atomic64_sub(blk_rq_bytes(rq), &ns->ctrl->in_flight_size),
   driving ctrl->in_flight_size negative (unlike ctrl->nr_active, which uses
   atomic_dec_if_positive()).

In nvme_st_compare_load(), the negative s64 returned by atomic64_read() is
assigned to the unsigned size_t variables sz1 and sz2, wrapping to a large
positive value near SIZE_MAX and causing nvme_service_time_path() to treat
the path as heavily overloaded until enough new I/O leaks or resets it.
Should in_flight_size be prevented from going negative or clamped when read?

[ ... ]
> @@ -1453,6 +1610,7 @@ int nvme_mpath_init_identify(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id)
>  
>  	/* initialize this in the identify path to cover controller resets */
>  	atomic_set(&ctrl->nr_active, 0);
> +	atomic64_set(&ctrl->in_flight_size, 0);
>  
>  	if (!ctrl->max_namespaces ||
>  	    ctrl->max_namespaces > le32_to_cpu(id->nn)) {
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019075607.808947-1-kanie@linux.alibaba.com?part=1


  reply	other threads:[~2026-10-19  8:14 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19  7:56 [PATCH v2 0/3] nvme: introduce the service-time iopolicy Guixin Liu
2026-10-19  7:56 ` [PATCH v2 1/3] nvme-multipath: implement " Guixin Liu
2026-10-19  8:14   ` sashiko-bot [this message]
2026-10-19  7:56 ` [PATCH v2 2/3] nvme-multipath: document " Guixin Liu
2026-10-19  7:56 ` [PATCH v2 3/3] nvme: document the service-time iopolicy sysfs attributes Guixin Liu
2026-10-19  8:13   ` sashiko-bot
2026-10-19 11:13     ` Guixin Liu
2026-10-19 12:13       ` Martin George
2026-10-19 12:43         ` Laurence Oberman
2026-10-19 13:21           ` Guixin Liu
2026-10-19 14:46           ` Martin George
2026-10-19 14:51             ` Laurence Oberman

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-176784@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=kanie@linux.alibaba.com \
    --cc=linux-nvme@lists.infradead.org \
    --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