Skip to content

test: report the vhost-user backend and the guest on failure - #6231

Open
Manciukic wants to merge 4 commits into
firecracker-microvm:mainfrom
Manciukic:fix/vhost-user-blk-diagnostics
Open

Manciukic wants to merge 4 commits into
firecracker-microvm:mainfrom
Manciukic:fix/vhost-user-blk-diagnostics

Conversation

@Manciukic

Copy link
Copy Markdown
Contributor

Changes

Test framework only, in four commits:

  1. Extract Microvm.thread_backtraces into utils.thread_backtraces(pid).
  2. On a failure, ask the guest for its blocked tasks with sysrq-w.
  3. On a failure, report the vhost-user backend's log and the backtraces of its
    process tree. crosvm runs at --log-level debug rather than off.
  4. VhostUserBlkBackend.kill() unlinks the socket even when the backend had
    already exited.

Reason

When a command in the guest does not return, the failure dump covers the host
side only, and only the part of it Firecracker owns: its log, and its threads,
which are parked waiting for something. Nothing says which guest task is stuck
or where, and for a vhost-user drive nothing covers the process actually
serving the queues.

That leaves a specific failure undiagnosable. A vhost-user block test has
timed out four times in CI on a guest waiting for a request that never
completed, and the artifacts have never been enough to say where the request
was lost. The guest sat in WFI, the backend was invisible: spawn() called
Popen without capturing stdout or stderr, and crosvm ran with
--log-level off, so the backend left no record at all.

The three additions each answer a question the dump could not:

  • Backend log. crosvm reports a request it failed to execute at DEBUG, so
    that is the lowest level at which the log says anything about a request that
    did not complete. warn produces zero bytes.
  • Backend process tree. crosvm runs each device in a child process, so the
    thread serving the queue is not in the process we spawned, and backtracing
    self.proc.pid alone would miss it.
  • Guest blocked tasks. sysrq-w reports every task in uninterruptible sleep
    with the stack it is stuck in. The hung-task detector reports the same
    stacks, but only 120s after the fact, which outlives the command timeout that
    triggers the dump.

Testing

I reproduced the failure mode by sending SIGSTOP to the crosvm device process
during mkfs.ext4 on a vhost-user drive, which stalls the queue the same way.
Before these commits the dump showed Firecracker's vCPUs in kvm_vcpu_block
and nothing else. After, it shows:

  • the backend log, including which child process serves the device
  • backtraces for crosvm, blockingPool0 and the block-0 child
  • task:mkfs.ext4 state:D, in io_schedulewait_on_page_bit_common
    blkdev_read_iter

Two things worth flagging about the log level, since debug on a device
backend invites the question:

  • It does not grow with I/O. The log stays at 520 bytes across three rounds of
    roughly 1.5 GB of mixed direct I/O, because crosvm logs nothing per
    successful request. The same backend serves the vhost-user-block
    performance tests, so a per-request log would have been a problem.
  • trace adds nothing over debug here, so there is no reason to go further.

Raising the guest's hung-task threshold instead was the obvious alternative and
does not work: khungtaskd is already asleep on the old timeout when the
sysctl changes, so the current sleep is not shortened. With the threshold at
10s the guest sat in D state for 40s with nothing in dmesg. sysrq-w returns
the same information in about a second, so no command timeout needs raising.

Because the dump runs from the SSH error hook and now issues SSH itself, it
takes a reentrancy guard, and a guest that cannot answer is reported as such
rather than replacing the original failure.

tools/devtool checkbuild --all is unchecked below because no Rust changed;
the diff is three Python files under tests/.

What this does not cover

The queue state itself. Knowing avail.idx, used.idx, used_event and
avail_event would say directly whether the backend published a completion and
whether a notification was suppressed, but the rings live in guest memory,
crosvm exposes no control-socket command for them, and vhost-user-blk has no
snapshot support, so there is no memory image to read them from. That needs a
change outside the test framework.

License Acceptance

By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache 2.0 license. For more information on following Developer
Certificate of Origin and signing off your commits, please check
CONTRIBUTING.md.

PR Checklist

  • I have read and understand CONTRIBUTING.md.
  • I have run tools/devtool checkbuild --all to verify that the PR passes
    build checks on all supported architectures.
  • I have run tools/devtool checkstyle --no-clippy to verify that the PR
    passes the automated style checks.
  • I have described what is done in these changes, why they are needed, and
    how they are solving the problem in a clear and encompassing way.
  • I have updated any relevant documentation (both in code and in the docs)
    in the PR.
  • I have mentioned all user-facing changes in CHANGELOG.md.
  • If a specific issue led to this PR, this PR closes the issue.
  • When making API changes, I have followed the
    Runbook for Firecracker API changes.
  • I have tested all new and changed functionalities in unit tests and/or
    integration tests.
  • I have linked an issue to every new TODO.

  • This functionality cannot be added in rust-vmm.

The next commit needs the same per-thread /proc/<pid>/stack dump for the
vhost-user backend, which is not a Microvm.

Signed-off-by: Riccardo Mancini <mancio@amazon.com>
When a command in the guest never returns, the failure dump covers the
host side only: Firecracker's log and its threads, which are simply
waiting. Nothing says which guest task is stuck, or where.

Ask the guest instead. sysrq-w reports every task in uninterruptible
sleep along with the stack it is stuck in. The hung-task detector
reports the same stacks, but 120s after the fact, which outlives the
command timeout that brings us here.

The dump runs from the SSH error hook and now issues SSH of its own,
hence the reentrancy guard.

Signed-off-by: Riccardo Mancini <mancio@amazon.com>
A vhost-user drive is served by a separate process, so nothing in the
failure dump covers the host side of its queues. When the guest waits
on a request that never completes, that is exactly what is missing.
This has been seen four times in CI without ever establishing where the
request was lost.

Add the backend's own log and the backtraces of its process tree.
crosvm runs each device in a child process, hence the tree rather than
the spawned process alone, and it reports a request it failed to
execute at DEBUG, hence the level change from off. It logs nothing per
successful request, so the volume stays flat under load.

Signed-off-by: Riccardo Mancini <mancio@amazon.com>
kill() removed the socket only on the branch where it terminated a live
backend, then asserted the socket was gone. A backend that had exited on
its own therefore failed teardown with "socket still exists", which says
nothing about the exit that caused it. Unlink either way and report the
exit code with the log.

Signed-off-by: Riccardo Mancini <mancio@amazon.com>
@codecov

codecov Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.05%. Comparing base (23b09b9) to head (e77fd5d).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6231   +/-   ##
=======================================
  Coverage   83.05%   83.05%           
=======================================
  Files         277      277           
  Lines       31481    31481           
=======================================
  Hits        26147    26147           
  Misses       5334     5334           
Flag Coverage Δ
5.10-m5n.metal 83.28% <ø> (-0.01%) ⬇️
5.10-m6a.metal 82.66% <ø> (ø)
5.10-m6g.metal 80.14% <ø> (ø)
5.10-m6i.metal 83.28% <ø> (-0.01%) ⬇️
5.10-m7a.metal-48xl 82.65% <ø> (ø)
5.10-m7g.metal 80.14% <ø> (ø)
5.10-m7i.metal-24xl 83.26% <ø> (-0.01%) ⬇️
5.10-m7i.metal-48xl 83.26% <ø> (ø)
5.10-m8g.metal-24xl 80.14% <ø> (ø)
5.10-m8g.metal-48xl 80.14% <ø> (ø)
5.10-m8i.metal-48xl 83.26% <ø> (ø)
5.10-m8i.metal-96xl 83.26% <ø> (-0.01%) ⬇️
5.10-m9g.metal-48xl 80.14% <ø> (ø)
6.1-m5n.metal 83.31% <ø> (-0.01%) ⬇️
6.1-m6a.metal 82.68% <ø> (ø)
6.1-m6g.metal 80.13% <ø> (-0.01%) ⬇️
6.1-m6i.metal 83.31% <ø> (ø)
6.1-m7a.metal-48xl 82.67% <ø> (ø)
6.1-m7g.metal 80.13% <ø> (-0.01%) ⬇️
6.1-m7i.metal-24xl 83.32% <ø> (-0.01%) ⬇️
6.1-m7i.metal-48xl 83.32% <ø> (-0.01%) ⬇️
6.1-m8g.metal-24xl 80.14% <ø> (ø)
6.1-m8g.metal-48xl 80.14% <ø> (ø)
6.1-m8i.metal-48xl 83.33% <ø> (ø)
6.1-m8i.metal-96xl 83.33% <ø> (ø)
6.1-m9g.metal-48xl 80.14% <ø> (ø)
6.18-m5n.metal 83.30% <ø> (ø)
6.18-m6a.metal 82.68% <ø> (-0.01%) ⬇️
6.18-m6g.metal 80.24% <ø> (-0.01%) ⬇️
6.18-m6i.metal 83.31% <ø> (+<0.01%) ⬆️
6.18-m7a.metal-48xl 82.67% <ø> (ø)
6.18-m7g.metal 80.24% <ø> (-0.01%) ⬇️
6.18-m7i.metal-24xl 83.32% <ø> (ø)
6.18-m7i.metal-48xl 83.33% <ø> (ø)
6.18-m8g.metal-24xl 80.24% <ø> (-0.01%) ⬇️
6.18-m8g.metal-48xl 80.24% <ø> (ø)
6.18-m8i.metal-48xl 83.33% <ø> (+<0.01%) ⬆️
6.18-m8i.metal-96xl 83.32% <ø> (ø)
6.18-m9g.metal-48xl 80.24% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Manciukic
Manciukic marked this pull request as ready for review September 18, 2026 15:35
@Manciukic Manciukic added the Status: Awaiting review Indicates that a pull request is ready to be reviewed label Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Awaiting review Indicates that a pull request is ready to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant