From: Joaquin Varela <joaquinvarela@neatech.ar>
To: pve-devel@lists.proxmox.com
Cc: Joaquin Varela <joaquinvarela@neatech.ar>
Subject: [PATCH storage v2 4/7] zfsnvme: make all-path loss policy explicit
Date: Sun, 2 Aug 2026 00:31:38 -0300 [thread overview]
Message-ID: <5dc4f773ff3bcabb4debdeb26815c2acea17b371.1785636979.git.joaquinvarela@neatech.ar> (raw)
In-Reply-To: <cover.1785636979.git.joaquinvarela@neatech.ar>
Expose nvme-fast-io-fail-tmo as an optional storage property.
Leaving it unset preserves queue-until-controller-loss behavior.
Setting it asks the kernel to return I/O errors earlier when all
paths are reconnecting.
Reject values greater than a finite controller-loss timeout.
Pass the policy through libnvme runtime JSON and explicit nvme
connect arguments. Add tests for propagation and validation.
Signed-off-by: Joaquin Varela <joaquinvarela@neatech.ar>
---
src/PVE/Storage/ZFSNVMePlugin.pm | 29 ++++++++++++++++++++++
src/test/zfsnvme_test.pm | 42 ++++++++++++++++++++++++++++++++
2 files changed, 71 insertions(+)
diff --git a/src/PVE/Storage/ZFSNVMePlugin.pm b/src/PVE/Storage/ZFSNVMePlugin.pm
index d02dff4..6513df0 100644
--- a/src/PVE/Storage/ZFSNVMePlugin.pm
+++ b/src/PVE/Storage/ZFSNVMePlugin.pm
@@ -237,6 +237,14 @@ sub properties($class) {
maximum => 86400,
default => 600,
},
+ 'nvme-fast-io-fail-tmo' => {
+ description =>
+ "Optional time before failing I/O on a reconnecting NVMe controller. Unset queues I/O until controller loss timeout.",
+ type => 'integer',
+ minimum => 0,
+ maximum => 86400,
+ optional => 1,
+ },
'nvme-nr-io-queues' => {
description => "Number of NVMe/TCP I/O queues per controller.",
type => 'integer',
@@ -261,6 +269,7 @@ sub options($class) {
'nvme-keep-alive-tmo' => { optional => 1 },
'nvme-reconnect-delay' => { optional => 1 },
'nvme-ctrl-loss-tmo' => { optional => 1 },
+ 'nvme-fast-io-fail-tmo' => { optional => 1 },
'nvme-nr-io-queues' => { optional => 1 },
nodes => { optional => 1 },
disable => { optional => 1 },
@@ -269,6 +278,18 @@ sub options($class) {
};
}
+sub _validate_fail_fast_timeout($config, $default_ctrl_loss_tmo = undef) {
+ my $fast = $config->{'nvme-fast-io-fail-tmo'};
+ return if !defined($fast);
+
+ my $ctrl = $config->{'nvme-ctrl-loss-tmo'};
+ $ctrl = $default_ctrl_loss_tmo if !defined($ctrl);
+ return if !defined($ctrl) || $ctrl < 0;
+
+ die "nvme-fast-io-fail-tmo must not exceed nvme-ctrl-loss-tmo\n"
+ if $fast > $ctrl;
+}
+
sub check_config($class, $section_id, $config, $create, $skip_schema_check) {
if ($create) {
$config->{sparse} = 1 if !defined($config->{sparse});
@@ -285,6 +306,7 @@ sub check_config($class, $section_id, $config, $create, $skip_schema_check) {
} elsif (defined($config->{'nvme-host-ifaces'})) {
parse_nvme_host_ifaces($config->{'nvme-host-ifaces'});
}
+ _validate_fail_fast_timeout($config, $create ? 600 : undef);
return $class->SUPER::check_config($section_id, $config, $create, $skip_schema_check);
}
@@ -380,6 +402,7 @@ sub on_update_hook_full($class, $storeid, $scfg, $update, $delete, $sensitive) {
delete @prospective{$delete->@*} if $delete;
verify_nvme_nqn($prospective{subsysnqn});
_configured_portals(\%prospective);
+ _validate_fail_fast_timeout(\%prospective, 600);
_assert_unique_target($storeid, \%prospective);
my $old_key = file_read_firstline(secret_path($storeid));
@@ -433,6 +456,9 @@ my sub write_runtime_config($storeid, $scfg, $hostnqn, $hostid, $key, $portals)
keep_alive_tmo => $scfg->{'nvme-keep-alive-tmo'} // 5,
reconnect_delay => $scfg->{'nvme-reconnect-delay'} // 2,
ctrl_loss_tmo => $scfg->{'nvme-ctrl-loss-tmo'} // 600,
+ (defined($scfg->{'nvme-fast-io-fail-tmo'})
+ ? (fast_io_fail_tmo => $scfg->{'nvme-fast-io-fail-tmo'})
+ : ()),
($scfg->{'nvme-nr-io-queues'}
? (nr_io_queues => $scfg->{'nvme-nr-io-queues'})
: ()),
@@ -585,6 +611,9 @@ sub _connect_portal($config_path, $scfg, $portal) {
'--ctrl-loss-tmo',
$scfg->{'nvme-ctrl-loss-tmo'} // 600,
];
+ if (defined(my $fast = $scfg->{'nvme-fast-io-fail-tmo'})) {
+ push $cmd->@*, '--fast_io_fail_tmo', $fast;
+ }
if (my $queues = $scfg->{'nvme-nr-io-queues'}) {
push $cmd->@*, '--nr-io-queues', $queues;
}
diff --git a/src/test/zfsnvme_test.pm b/src/test/zfsnvme_test.pm
index edb8b38..e06ac76 100644
--- a/src/test/zfsnvme_test.pm
+++ b/src/test/zfsnvme_test.pm
@@ -244,6 +244,22 @@ is_deeply(
'nvme connect is explicitly bound to the configured data interface',
);
+PVE::Storage::ZFSNVMePlugin::_connect_portal(
+ '/run/pve-storage/test.json',
+ {
+ subsysnqn => 'nqn.2026-07.example:test',
+ 'nvme-ctrl-loss-tmo' => 60,
+ 'nvme-fast-io-fail-tmo' => 15,
+ },
+ { address => '10.90.2.11', port => 4420, host_iface => 'ens21' },
+);
+my $connect_args = join(' ', @connect_cmd);
+like(
+ $connect_args,
+ qr/--fast_io_fail_tmo 15/,
+ 'configured fast I/O fail timeout is passed to nvme connect',
+);
+
$nvme_mock->redefine(
zfs_request => sub {
my ($class, $config, $timeout, $method, @params) = @_;
@@ -329,6 +345,32 @@ eval {
};
is($@, '', 'check_config accepts a valid partial update');
+eval {
+ PVE::Storage::ZFSNVMePlugin::_validate_fail_fast_timeout(
+ {
+ 'nvme-ctrl-loss-tmo' => 30,
+ 'nvme-fast-io-fail-tmo' => 31,
+ },
+ 600,
+ );
+};
+like(
+ $@,
+ qr/must not exceed nvme-ctrl-loss-tmo/,
+ 'fast I/O fail timeout cannot outlive the controller loss timeout',
+);
+
+eval {
+ PVE::Storage::ZFSNVMePlugin::_validate_fail_fast_timeout(
+ {
+ 'nvme-ctrl-loss-tmo' => -1,
+ 'nvme-fast-io-fail-tmo' => 30,
+ },
+ 600,
+ );
+};
+is($@, '', 'fast I/O fail remains valid with infinite controller reconnect');
+
{
no warnings 'redefine';
local *PVE::Storage::config = sub { return { ids => {} } };
--
2.54.0.windows.1
next prev parent reply other threads:[~2026-08-02 3:33 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-02 3:31 [PATCH storage v2 0/7] add native ZFS over NVMe/TCP backend Joaquin Varela
2026-08-02 3:31 ` [PATCH storage v2 1/7] zfs: make LUN provider dispatch overridable Joaquin Varela
2026-08-02 3:31 ` [PATCH storage v2 2/7] zfs: add native NVMe/TCP storage backend Joaquin Varela
2026-08-02 3:31 ` [PATCH storage v2 3/7] zfsnvme: harden node preflight and storage teardown Joaquin Varela
2026-08-02 3:31 ` Joaquin Varela [this message]
2026-08-02 3:31 ` [PATCH storage v2 5/7] zfsnvme: accept activation hints from storage API Joaquin Varela
2026-08-02 3:31 ` [PATCH storage v2 6/7] zfsnvme: accept short volume activation calls Joaquin Varela
2026-08-02 3:31 ` [PATCH storage v2 7/7] zfsnvme: restore ACLs before publishing target Joaquin Varela
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=5dc4f773ff3bcabb4debdeb26815c2acea17b371.1785636979.git.joaquinvarela@neatech.ar \
--to=joaquinvarela@neatech.ar \
--cc=pve-devel@lists.proxmox.com \
/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