Skip to content

zvol: return EBUSY when setting volmode/snapdev on in-use device - #18843

Open
lhjnano wants to merge 1 commit into
openzfs:masterfrom
lhjnano:fix/zvol-volmode-snapdev-hang
Open

zvol: return EBUSY when setting volmode/snapdev on in-use device#18843
lhjnano wants to merge 1 commit into
openzfs:masterfrom
lhjnano:fix/zvol-volmode-snapdev-hang

Conversation

@lhjnano

@lhjnano lhjnano commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Motivation and Context

Setting volmode or snapdev=hidden on a zvol whose block device is held open
(e.g. by a mounted filesystem) causes the calling process to enter an
uninterruptible D-state sleep. zvol_remove_minors_impl() blocks indefinitely
on cv_wait() because zv_open_count never reaches zero while the device
stays open. On Linux, zvol_wait_close() is a no-op, so the blocking wait
never completes. The only recovery is a system reboot.

The same cv_wait in zvol_remove_minors_impl() is shared code
(module/zfs/zvol.c); FreeBSD has a 10-second msleep delay in
zvol_wait_close(), but the subsequent cv_wait still has no timeout.

This is consistent with how zpool export already handles in-use zvols
(spa_export_common() returns EBUSY when zvol minors are in use).

Description

Add two non-blocking helpers:

  • zvol_minor_in_use(name) — checks if a specific zvol minor has
    zv_open_count > 0.
  • zvol_snapshot_minors_in_use(name) — checks if any snapshot minor under
    the given dataset is open.

Call them in zvol_set_common() before the property is written. If the
relevant minor is in use, return EBUSY immediately.

Defensive checks in zvol_set_volmode_impl() and zvol_set_snapdev_cb()
cover the race window between the pre-check and the async taskq execution:
if zv_open_count > 0 is detected at that point, the task records EBUSY
instead of blocking on cv_wait.

How Has This Been Tested?

New tests:

  • zvol_misc_volmode_inuse.ksh — creates a zvol, formats and mounts it,
    verifies volmode=none/dev fails with EBUSY, then unmounts and verifies
    volmode can be changed.
  • zvol_misc_snapdev_inuse.ksh — creates a zvol with a snapshot, holds the
    snapshot device open, verifies snapdev=hidden fails with EBUSY, then
    releases and verifies snapdev=hidden succeeds.

Both pass on Rocky Linux 8.10 (kernel 4.18.0-553.111.1.el8_10.x86_64),
OpenZFS master (2.4.99).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

@amotin

amotin commented Jul 22, 2026

Copy link
Copy Markdown
Member

I haven't looked deep, but IIRC unlike Linux FreeBSD should actually be able to destroy open device. So I have feeling this might be a step in wrong direction.

@lhjnano

lhjnano commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for taking a look.

My reasoning was that zvol_remove_minors_impl() in the shared code uses a timeout-less cv_wait(&zv->zv_removing_cv) while zv_open_count > 0, and on Linux zvol_wait_close() is a no-op, so nothing signals that CV while the device stays open. I thought FreeBSD might hit the same cv_wait since it also has no timeout there, but I wasn't able to test on FreeBSD and I may well be missing how the GEOM layer handles this.

Would it make sense to limit the pre-check to Linux for now, so FreeBSD behavior stays as-is? I'd appreciate any thoughts.

@amotin

amotin commented Jul 23, 2026

Copy link
Copy Markdown
Member

I thought FreeBSD might hit the same cv_wait since it also has no timeout there

Unfortunately the last time I saw it on FreeBSD, the ZFS ZVOL side was no implemented right to work. So the problem may exist there too, but could be (and I'd prefer it to be) fixed in a better way.

@lhjnano
lhjnano force-pushed the fix/zvol-volmode-snapdev-hang branch from 8a4fbab to bd9f617 Compare July 23, 2026 23:07
@lhjnano

lhjnano commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Sounds good. I'll limit the pre-check to Linux for now so FreeBSD behavior stays unchanged.

@lhjnano
lhjnano force-pushed the fix/zvol-volmode-snapdev-hang branch 4 times, most recently from f42d7e7 to c894c33 Compare July 24, 2026 05:32
@amotin

amotin commented Jul 24, 2026

Copy link
Copy Markdown
Member

Sounds good. I'll limit the pre-check to Linux for now so FreeBSD behavior stays unchanged.

That's not exactly what I meant. More of provoking somebody to look on the original problem.

@lhjnano
lhjnano force-pushed the fix/zvol-volmode-snapdev-hang branch 3 times, most recently from ffeda30 to 5083ebe Compare July 25, 2026 10:09
@lhjnano

lhjnano commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the regression. zvol_set_common() now checks descendant zvols and returns EBUSY for permanent opens (mount). Transient opens (udev/blkid) are left to the existing cv_wait() as before. volmode_impl and remove_minors_impl are unchanged.

Changing volmode or snapdev on a dataset with open descendant zvols
would cause zvol_remove_minors_impl() to block indefinitely waiting
for the open count to reach zero, resulting in a D-state hang.

Add zvol_minors_in_use() to check if the target dataset or any of its
descendant zvols are currently open.  Return EBUSY from
zvol_set_common() before modifying the property on disk, allowing the
caller to close the device and retry.

For transient opens (e.g., udev/blkid), the existing cv_wait() in
zvol_remove_minors_impl() handles the brief wait as before.

Signed-off-by: HeonJe LEE <lhjnano@gmail.com>
@lhjnano
lhjnano force-pushed the fix/zvol-volmode-snapdev-hang branch from 5083ebe to 39ebfd0 Compare July 25, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants