[PATCH] nvmet-tcp: report a bounded MDTS instead of "no limit"
Alfonso Kuen <[email protected]>
| Newsgroups | org.infradead.lists.linux-nvme,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
nvmet-tcp does not implement .get_mdts, so nvmet_ctrl_mdts() falls back to 0 and identify-controller advertises "no maximum data transfer size". The initiator takes that literally: max_hw_sectors becomes UINT_MAX, and the block layer then merges requests up to its generic ceiling -- 32 MiB on the hosts we measured. The target cannot actually serve those. nvmet_tcp_map_data() allocates the command scatterlist with cmd->req.sg = sgl_alloc(len, GFP_KERNEL | __GFP_NOWARN, &cmd->req.sg_cnt); if (!cmd->req.sg) return NVME_SC_INTERNAL; For a 32 MiB command that is 8192 scatterlist entries, so sgl_alloc_order() asks kmalloc for an order-5/6 block. Under memory fragmentation that fails, and because of __GFP_NOWARN it fails silently -- nothing is logged on the target side. The initiator gets NVME_SC_INTERNAL, which is a generic status, so NVMe multipath does not fail the command over to another path. A 1 MiB command needs 256 entries, an order-0 allocation, which does not fail. This is why the failure is intermittent and load-dependent rather than deterministic. nvmet-rdma has advertised a bounded MDTS since the series "nvmet: Add mdts setting op for controllers" (Max Gurtovoy, Mar 2020), which deliberately left other transports untouched. Do the same for TCP, using the same 1 MiB value, so initiators size their requests to something the target can allocate. Measured against a Linux nvmet TCP target with three initiators: of ~1040 failed I/O commands, 980 were exactly 65536 blocks (32 MiB). Capping the initiator side with max_sectors_kb=1024 removed them entirely. With the failures reaching a page-cached writer, a 2 TiB image copy lost roughly 98 GiB while the copy tool exited 0. Signed-off-by: Alfonso Kuen <[email protected]> --- drivers/nvme/target/tcp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index e4f603b2a..414ba896e 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -23,6 +23,9 @@ #include "nvmet.h" #define NVMET_TCP_DEF_INLINE_DATA_SIZE (4 * PAGE_SIZE) + +/* Assume mpsmin == device_page_size == 4KB */ +#define NVMET_TCP_MAX_MDTS 8 #define NVMET_TCP_MAXH2CDATA 0x400000 /* 16M arbitrary limit */ #define NVMET_TCP_BACKLOG 128 @@ -2243,6 +2246,11 @@ static ssize_t nvmet_tcp_host_port_addr(struct nvmet_ctrl *ctrl, (struct sockaddr *)&queue->sockaddr_peer); } +static u8 nvmet_tcp_get_mdts(const struct nvmet_ctrl *ctrl) +{ + return NVMET_TCP_MAX_MDTS; +} + static const struct nvmet_fabrics_ops nvmet_tcp_ops = { .owner = THIS_MODULE, .type = NVMF_TRTYPE_TCP, @@ -2254,6 +2262,7 @@ static const struct nvmet_fabrics_ops nvmet_tcp_ops = { .install_queue = nvmet_tcp_install_queue, .disc_traddr = nvmet_tcp_disc_port_addr, .host_traddr = nvmet_tcp_host_port_addr, + .get_mdts = nvmet_tcp_get_mdts, }; static int __init nvmet_tcp_init(void) -- 2.47.3