* [PATCH v2] nvme-multipath: add fail_if_no_path sysfs attribute
@ 2026-09-17 23:16 Krishna Iyer
2026-09-18 8:58 ` Nilay Shroff
0 siblings, 1 reply; 2+ messages in thread
From: Krishna Iyer @ 2026-09-17 23:16 UTC (permalink / raw)
To: linux-nvme, Keith Busch, Christoph Hellwig, Sagi Grimberg, Jens Axboe
Cc: Nilay Shroff, SeongJae Park, Saravanan D, linux-kernel, Krishna Iyer
When no usable path exists, I/O on a multipath namespace is queued
until a path returns. With ctrl_loss_tmo=-1 that can be forever:
during a long fabric outage any process waiting on the I/O is stuck in
D state. We hit this on virtualization hosts, where a SIGKILLed VM
process cannot exit while draining I/O to an unreachable NVMe/TCP
target.
Nothing can fail this I/O without tearing something down: controller
deletion takes every namespace on the controller with it.
Add a fail_if_no_path attribute on the ns-head disk: a persistent
per-namespace policy to fail parked and newly arriving I/O instead of
queueing it when no usable path exists. It is enforced where a path is
known to be unusable: CONNECTING controllers and LIVE controllers with
the path ANA inaccessible or persistent-loss stop counting as
available, RESETTING and ANA change keep queueing, and with no
controllers left the policy overrides the delayed_removal_secs
queueing window. Controller state is untouched and reconnects
continue. Like dm's fail_if_no_path, the policy is transport agnostic.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Krishna Iyer <kiyer@crusoe.ai>
---
Changes since v1 [1]:
- rename fail_io_now -> fail_if_no_path; persistent policy, no
self-clear when a path returns (Nilay)
- enforce inside the nvme_available_path() loop: only CONNECTING and
ANA-unusable LIVE paths stop counting; resets and ANA transitions
queue as before (Nilay)
- override delayed_removal_secs when no controllers remain
- keep visibility transport-agnostic (Nilay)
- rebase onto nvme-7.3 (Nilay)
- add Documentation/ABI entry
[1] https://lore.kernel.org/linux-nvme/20260904032605.65758-1-kiyer@crusoe.ai/
Documentation/ABI/stable/sysfs-nvme | 13 +++++++
drivers/nvme/host/multipath.c | 57 ++++++++++++++++++++++++++++-
drivers/nvme/host/nvme.h | 2 +
drivers/nvme/host/sysfs.c | 4 +-
4 files changed, 74 insertions(+), 2 deletions(-)
diff --git a/Documentation/ABI/stable/sysfs-nvme b/Documentation/ABI/stable/sysfs-nvme
index a2f5d0710db4..57a827235995 100644
--- a/Documentation/ABI/stable/sysfs-nvme
+++ b/Documentation/ABI/stable/sysfs-nvme
@@ -337,6 +337,19 @@ Description:
is deferred. Only visible on multipath head devices.
Requires CONFIG_NVME_MULTIPATH.
+What: /sys/block/nvmeXnY/fail_if_no_path
+Date: September 2026
+KernelVersion: 7.3
+Contact: Krishna Iyer <kiyer@crusoe.ai>
+Description:
+ Shows or sets the fail-if-no-path policy of the multipath
+ head device ("on" or "off", default "off"). When on, I/O
+ queued or arriving while no usable path exists is failed
+ immediately instead of being queued, including during the
+ delayed_removal_secs window. Reconnect attempts are not
+ affected. Only visible on multipath head devices.
+ Requires CONFIG_NVME_MULTIPATH.
+
What: /sys/block/nvmeXnY/csi
What: /sys/block/nvmeXnY/metadata_bytes
What: /sys/block/nvmeXnY/nuse
diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
index 3d46c4f28a47..23aeb1737ab5 100644
--- a/drivers/nvme/host/multipath.c
+++ b/drivers/nvme/host/multipath.c
@@ -499,6 +499,8 @@ inline struct nvme_ns *nvme_find_path(struct nvme_ns_head *head)
static bool nvme_available_path(struct nvme_ns_head *head)
__must_hold_shared(&head->srcu)
{
+ bool fail_if_no_path = test_bit(NVME_NSHEAD_FAIL_IF_NO_PATH,
+ &head->flags);
struct nvme_ns *ns;
if (!test_bit(NVME_NSHEAD_DISK_LIVE, &head->flags))
@@ -510,14 +512,25 @@ static bool nvme_available_path(struct nvme_ns_head *head)
continue;
switch (nvme_ctrl_state(ns->ctrl)) {
case NVME_CTRL_LIVE:
+ if (fail_if_no_path &&
+ (ns->ana_state == NVME_ANA_INACCESSIBLE ||
+ ns->ana_state == NVME_ANA_PERSISTENT_LOSS))
+ continue;
+ return true;
case NVME_CTRL_RESETTING:
- case NVME_CTRL_CONNECTING:
return true;
+ case NVME_CTRL_CONNECTING:
+ if (!fail_if_no_path)
+ return true;
+ continue;
default:
break;
}
}
+ if (fail_if_no_path)
+ return false;
+
/*
* If "head->delayed_removal_secs" is configured (i.e., non-zero), do
* not immediately fail I/O. Instead, requeue the I/O for the configured
@@ -1181,6 +1194,48 @@ static ssize_t delayed_removal_secs_store(struct device *dev,
DEVICE_ATTR_RW(delayed_removal_secs);
+static ssize_t fail_if_no_path_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gendisk *disk = dev_to_disk(dev);
+ struct nvme_ns_head *head = disk->private_data;
+
+ return sysfs_emit(buf, test_bit(NVME_NSHEAD_FAIL_IF_NO_PATH,
+ &head->flags) ? "on\n" : "off\n");
+}
+
+static ssize_t fail_if_no_path_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct gendisk *disk = dev_to_disk(dev);
+ struct nvme_ns_head *head = disk->private_data;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ set_bit(NVME_NSHEAD_FAIL_IF_NO_PATH, &head->flags);
+ else
+ clear_bit(NVME_NSHEAD_FAIL_IF_NO_PATH, &head->flags);
+
+ /*
+ * Ensure that update to NVME_NSHEAD_FAIL_IF_NO_PATH is seen
+ * by its reader.
+ */
+ synchronize_srcu(&head->srcu);
+
+ /* Make already-queued I/O re-evaluate path availability. */
+ if (enable)
+ kblockd_schedule_work(&head->requeue_work);
+
+ return count;
+}
+
+DEVICE_ATTR_RW(fail_if_no_path);
+
static ssize_t multipath_failover_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index e0260f4d24fd..ff886673d7ca 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -589,6 +589,7 @@ struct nvme_ns_head {
#define NVME_NSHEAD_DISK_LIVE 0
#define NVME_NSHEAD_QUEUE_IF_NO_PATH 1
#define NVME_NSHEAD_CDEV_LIVE 2
+#define NVME_NSHEAD_FAIL_IF_NO_PATH 3
struct nvme_ns __rcu_guarded *current_path[];
#endif
};
@@ -1096,6 +1097,7 @@ extern struct device_attribute dev_attr_ana_state;
extern struct device_attribute dev_attr_queue_depth;
extern struct device_attribute dev_attr_numa_nodes;
extern struct device_attribute dev_attr_delayed_removal_secs;
+extern struct device_attribute dev_attr_fail_if_no_path;
extern struct device_attribute dev_attr_multipath_failover_count;
extern struct device_attribute dev_attr_io_requeue_no_usable_path_count;
extern struct device_attribute dev_attr_io_fail_no_available_path_count;
diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c
index e1e3dcfd084b..56e0ce1c9d8a 100644
--- a/drivers/nvme/host/sysfs.c
+++ b/drivers/nvme/host/sysfs.c
@@ -264,6 +264,7 @@ static struct attribute *nvme_ns_attrs[] = {
&dev_attr_queue_depth.attr,
&dev_attr_numa_nodes.attr,
&dev_attr_delayed_removal_secs.attr,
+ &dev_attr_fail_if_no_path.attr,
#endif
&dev_attr_io_passthru_err_log_enabled.attr,
NULL,
@@ -300,7 +301,8 @@ static umode_t nvme_ns_attrs_are_visible(struct kobject *kobj,
if (nvme_disk_is_ns_head(dev_to_disk(dev)))
return 0;
}
- if (a == &dev_attr_delayed_removal_secs.attr) {
+ if (a == &dev_attr_delayed_removal_secs.attr ||
+ a == &dev_attr_fail_if_no_path.attr) {
struct gendisk *disk = dev_to_disk(dev);
if (!nvme_disk_is_ns_head(disk))
--
2.54.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH v2] nvme-multipath: add fail_if_no_path sysfs attribute
2026-09-17 23:16 [PATCH v2] nvme-multipath: add fail_if_no_path sysfs attribute Krishna Iyer
@ 2026-09-18 8:58 ` Nilay Shroff
0 siblings, 0 replies; 2+ messages in thread
From: Nilay Shroff @ 2026-09-18 8:58 UTC (permalink / raw)
To: Krishna Iyer, linux-nvme, Keith Busch, Christoph Hellwig,
Sagi Grimberg, Jens Axboe
Cc: SeongJae Park, Saravanan D, linux-kernel
On 9/18/26 4:46 AM, Krishna Iyer wrote:
> diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
> index 3d46c4f28a47..23aeb1737ab5 100644
> --- a/drivers/nvme/host/multipath.c
> +++ b/drivers/nvme/host/multipath.c
> @@ -499,6 +499,8 @@ inline struct nvme_ns *nvme_find_path(struct nvme_ns_head *head)
> static bool nvme_available_path(struct nvme_ns_head *head)
> __must_hold_shared(&head->srcu)
> {
> + bool fail_if_no_path = test_bit(NVME_NSHEAD_FAIL_IF_NO_PATH,
> + &head->flags);
> struct nvme_ns *ns;
>
> if (!test_bit(NVME_NSHEAD_DISK_LIVE, &head->flags))
> @@ -510,14 +512,25 @@ static bool nvme_available_path(struct nvme_ns_head *head)
> continue;
> switch (nvme_ctrl_state(ns->ctrl)) {
> case NVME_CTRL_LIVE:
> + if (fail_if_no_path &&
> + (ns->ana_state == NVME_ANA_INACCESSIBLE ||
> + ns->ana_state == NVME_ANA_PERSISTENT_LOSS))
> + continue;
I think we have helper nvme_state_is_live() which could be used here.
[...]
> +static ssize_t fail_if_no_path_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t count)
> +{
> + struct gendisk *disk = dev_to_disk(dev);
> + struct nvme_ns_head *head = disk->private_data;
> + bool enable;
> + int ret;
> +
> + ret = kstrtobool(buf, &enable);
> + if (ret < 0)
> + return ret;
> +
> + if (enable)
> + set_bit(NVME_NSHEAD_FAIL_IF_NO_PATH, &head->flags);
> + else
> + clear_bit(NVME_NSHEAD_FAIL_IF_NO_PATH, &head->flags);
> +
> + /*
> + * Ensure that update to NVME_NSHEAD_FAIL_IF_NO_PATH is seen
> + * by its reader.
> + */
> + synchronize_srcu(&head->srcu);
> +
> + /* Make already-queued I/O re-evaluate path availability. */
> + if (enable)
> + kblockd_schedule_work(&head->requeue_work);
> +
If the user stores the same value as the current setting, we could
return immediately instead of waiting for synchronize_srcu() and
scheduling the requeue work.
Otherwise changes look good.
Thanks,
--Nilay
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-18 8:59 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-17 23:16 [PATCH v2] nvme-multipath: add fail_if_no_path sysfs attribute Krishna Iyer
2026-09-18 8:58 ` Nilay Shroff
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®