Linux block layer
 help / color / mirror / Atom feed
* [PATCH] block/050: test deferred blk-mq queue work placement with isolated CPUs
@ 2026-10-22  3:54 Aaron Tomlin
  0 siblings, 0 replies; only message in thread
From: Aaron Tomlin @ 2026-10-22  3:54 UTC (permalink / raw)
  To: shinichiro.kawasaki; +Cc: wagi, axboe, atomlin, sean, linux-block

Add a test case to verify that deferred blk-mq queue dispatch
(&hctx->run_work) is steered to an online housekeeping CPU rather than
executing on an isolated CPU.

When asynchronous queue dispatch is scheduled (e.g. using an I/O
scheduler like mq-deadline or upon hardware queue backpressure), blk-mq
queues &hctx->run_work onto kblockd_workqueue. For single-queue devices
or multi-queue devices where all CPUs mapped to a hardware queue are
isolated, blk-mq falls back to unbound queue placement.

However, because kblockd_workqueue is allocated as a per-CPU workqueue
(WQ_PERCPU), unbound queue requests issued from an isolated core can
resolve to raw_smp_processor_id() and execute on the local worker pool.
In real-time and low-latency environments, running block kworkers on
isolated CPUs introduces unwanted latency jitter and risks stalling
queue dispatch if higher-priority SCHED_FIFO tasks preempt the worker.
Deferred dispatch must instead be directed to an online housekeeping
CPU.

This test:
    1.  Isolates an online CPU using a cgroup v2 isolated partition
        (cpuset.cpus.partition = isolated).

    2.  Tests both single-queue (submit_queues=1) and multi-queue
        (submit_queues=$(nproc)) null_blk configurations with the
        mq-deadline I/O scheduler.

    3.  Creates an isolated tracefs instance to monitor the
        workqueue_execute_start tracepoint filtered on
        blk_mq_run_work_fn.

    4.  Submits concurrent I/O from a workload confined to the isolated
        CPU.

    5.  Verifies that deferred queue executions occurred and that zero
        kblockd worker executions ran on the isolated CPU.

Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
Tests the fix proposed in: https://lore.kernel.org/lkml/20261022034737.47820-1-atomlin@atomlin.com/
---
 tests/block/050     | 191 ++++++++++++++++++++++++++++++++++++++++++++
 tests/block/050.out |   2 +
 2 files changed, 193 insertions(+)
 create mode 100755 tests/block/050
 create mode 100644 tests/block/050.out

diff --git a/tests/block/050 b/tests/block/050
new file mode 100755
index 0000000..3283a04
--- /dev/null
+++ b/tests/block/050
@@ -0,0 +1,191 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0+
+# Copyright (c) 2026 Aaron Tomlin <atomlin@atomlin.com>
+#
+# Test that deferred blk-mq queue dispatch (&hctx->run_work) is not executed
+# by kblockd workers on isolated CPUs.
+#
+# When a hardware queue experiences backpressure or resource starvation (e.g.
+# upon tag exhaustion), blk_mq_delay_run_hw_queue() schedules deferred queue
+# dispatch. When I/O is submitted from an isolated CPU, blk_mq_hctx_next_cpu()
+# must steer &hctx->run_work to an online housekeeping CPU rather than running
+# on the local isolated CPU's worker pool, avoiding SCHED_FIFO worker
+# starvation and latency jitter.
+
+. tests/block/rc
+. common/cgroup
+. common/null_blk
+. common/fio
+
+DESCRIPTION="test deferred blk-mq queue work placement with isolated CPUs"
+QUICK=1
+
+requires() {
+	_have_root
+	_have_null_blk
+	_have_fio
+	_require_min_cpus 2
+	_have_cgroup2_controller_file cpuset cpuset.cpus.partition
+	_have_tracefs
+	_have_tracepoint "workqueue/workqueue_execute_start"
+	modprobe -q mq-deadline 2>/dev/null || true
+}
+
+isolate_cpu() {
+	local cpu="$1"
+	local partition
+
+	_init_cgroup2 || return 1
+	_set_attr "+cpuset" "$(_cgroup2_base_dir)/cgroup.subtree_control"
+	_set_attr "${cpu}" "${CGROUP2_DIR}/cpuset.cpus"
+	_set_attr "isolated" "${CGROUP2_DIR}/cpuset.cpus.partition"
+
+	partition=$(cat "${CGROUP2_DIR}/cpuset.cpus.partition")
+	if [[ "${partition}" != "isolated" ]]; then
+		SKIP_REASONS+=("failed to isolate CPU ${cpu}: ${partition}")
+		_exit_cgroup2
+		return 1
+	fi
+
+	return 0
+}
+
+start_trace() {
+	local trace_dir="$1"
+	local event="${trace_dir}/events/workqueue/workqueue_execute_start"
+
+	_set_attr "" "${trace_dir}/trace"
+	_set_attr "function.function == blk_mq_run_work_fn" "${event}/filter" 2>/dev/null || true
+	_set_attr 1 "${event}/enable"
+}
+
+stop_trace() {
+	local trace_dir="$1"
+	local isolated_cpu="$2"
+
+	_set_attr 0 "${trace_dir}/events/workqueue/workqueue_execute_start/enable"
+
+	local isol_hits=0
+	if [[ -f "${trace_dir}/per_cpu/cpu${isolated_cpu}/trace" ]]; then
+		isol_hits=$(grep -c "blk_mq_run_work_fn" \
+			"${trace_dir}/per_cpu/cpu${isolated_cpu}/trace" 2>/dev/null || true)
+	fi
+
+	local total_hits=0
+	total_hits=$(grep -c "blk_mq_run_work_fn" \
+		"${trace_dir}/trace" 2>/dev/null || true)
+
+	echo "${isol_hits}:${total_hits}"
+}
+
+run_io() {
+	local dev="$1"
+	local cpu="$2"
+
+	(
+		local allowed
+		_set_attr "${BASHPID}" "${CGROUP2_DIR}/cgroup.procs"
+		allowed=$(awk '/^Cpus_allowed_list:/ { print $2 }' "/proc/${BASHPID}/status")
+		if [[ "${allowed}" != "${cpu}" ]]; then
+			echo "FAIL: workload not confined to isolated CPU ${cpu} (runs on ${allowed})"
+			exit 1
+		fi
+
+		# Submit concurrent I/O with iodepth > hw_queue_depth to induce tag
+		# exhaustion and force asynchronous queue dispatch (&hctx->run_work)
+		_run_fio --name=tag_exhaustion \
+			--filename="/dev/${dev}" \
+			--ioengine=psync \
+			--rw=randwrite \
+			--direct=1 \
+			--bs=4k \
+			--size=64k \
+			--numjobs=4 \
+			--iodepth=8 \
+			>>"${FULL}" 2>&1
+	)
+}
+
+test_workqueue_placement() {
+	local dev="$1"
+	local cpu="$2"
+	local test_desc="$3"
+	local trace_dir="$4"
+
+	if ! _write_queue_sched "mq-deadline" "/sys/block/${dev}/queue/scheduler"; then
+		echo "FAIL: ${test_desc}: failed to set mq-deadline scheduler"
+		return 1
+	fi
+
+	start_trace "${trace_dir}"
+	run_io "${dev}" "${cpu}" || echo "FAIL: ${test_desc}: I/O execution failed"
+	udevadm settle
+
+	local res
+	res=$(stop_trace "${trace_dir}" "${cpu}")
+	local isol_hits="${res%%:*}"
+	local total_hits="${res##*:}"
+
+	if [[ -n "${FULL}" ]]; then
+		echo "${test_desc}: isol_hits=${isol_hits} total_hits=${total_hits}" >>"${FULL}"
+	fi
+
+	if [[ "${isol_hits}" -gt 0 ]]; then
+		echo "FAIL: ${test_desc}: ${isol_hits} blk_mq_run_work_fn worker runs on isolated CPU ${cpu}"
+	fi
+
+	if [[ "${total_hits}" -eq 0 ]]; then
+		echo "FAIL: ${test_desc}: no deferred queue executions observed"
+	fi
+}
+
+test() {
+	echo "Running ${TEST_NAME}"
+
+	local cpu
+	local nr_cpus
+	local trace_dir="/sys/kernel/debug/tracing/instances/blktests-${TEST_NAME//\//-}"
+
+	nr_cpus=$(nproc)
+	cpu=$(awk -F'[-,]' '{ print $NF }' /sys/devices/system/cpu/online)
+	if ! isolate_cpu "${cpu}"; then
+		return 1
+	fi
+
+	mkdir "${trace_dir}" || return 1
+
+	# Test 1: Single-queue device (nr_hw_queues == 1)
+	local nullb_sq_params=(
+		submit_queues=1
+		hw_queue_depth=1
+		irqmode=2
+		completion_nsec=10000000
+		power=1
+	)
+	if _configure_null_blk nullb1 "${nullb_sq_params[@]}"; then
+		test_workqueue_placement nullb1 "${cpu}" "single-queue" "${trace_dir}"
+	else
+		echo "FAIL: failed to configure single-queue null_blk"
+	fi
+	_exit_null_blk
+
+	# Test 2: Multi-queue device where isolated CPU maps to empty hctx
+	local nullb_mq_params=(
+		submit_queues="${nr_cpus}"
+		hw_queue_depth=1
+		irqmode=2
+		completion_nsec=10000000
+		power=1
+	)
+	if _configure_null_blk nullb1 "${nullb_mq_params[@]}"; then
+		test_workqueue_placement nullb1 "${cpu}" "multi-queue empty hctx" "${trace_dir}"
+	else
+		echo "FAIL: failed to configure multi-queue null_blk"
+	fi
+	_exit_null_blk
+
+	rmdir "${trace_dir}"
+	_exit_cgroup2
+
+	echo "Test complete"
+}
diff --git a/tests/block/050.out b/tests/block/050.out
new file mode 100644
index 0000000..fc4e537
--- /dev/null
+++ b/tests/block/050.out
@@ -0,0 +1,2 @@
+Running block/050
+Test complete
-- 
2.55.0


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-22  3:54 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22  3:54 [PATCH] block/050: test deferred blk-mq queue work placement with isolated CPUs Aaron Tomlin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox