On 9/22/26 12:27, Yifan Li wrote:
> We found libvirt has a race condition bug and it causes systemd
> timeout during system shutdown. This issue does not exist on
> libvirt 10.0.0 with Ubuntu 24.04 but found in Ubuntu 26.04 which
> uses libvirt 12.0.0.
> 
> nodeStateShutdownWait() joins the udev event thread while holding
> @priv's object lock:
> 
>     VIR_WITH_OBJECT_LOCK_GUARD(priv) {
>         if (priv->udevThread)
>             virThreadJoin(priv->udevThread);
>     }
> 
> but udevEventHandleThread() needs that same lock to make progress. Its
> only clean exit path is
> 
>     VIR_WITH_OBJECT_LOCK_GUARD(priv) {
>         while (!priv->udevDataReady && !priv->udevThreadQuit) {
>             if (virCondWait(&priv->udevThreadCond, &priv->parent.lock))
>                 ...
>         }
>         if (priv->udevThreadQuit)
>             return;
> 
> so the thread has to re-acquire the lock - either on return from
> virCondWait() or to re-enter the guard at the top of its loop - before
> it can observe @priv->udevThreadQuit and return.
> 
> nodeStateShutdownPrepare() sets udevThreadQuit, signals the condition
> and drops the lock. Which thread grabs the lock next then decides the
> outcome:
> 
>   - the udev thread wins: it sees udevThreadQuit, returns, and the
>     subsequent join completes immediately.
>   - nodeStateShutdownWait() wins: it takes the lock and blocks in
>     virThreadJoin(), so the udev thread can never re-acquire the lock,
>     never observes udevThreadQuit and never returns. Both threads are
>     then stuck for good.
> 
> Holding the lock across the join serves no purpose - udevThreadQuit has
> already been published by nodeStateShutdownPrepare(), @priv->udevThread
> is only assigned in nodeStateInitialize() and released by
> udevEventDataDispose(), and the udev thread holds a reference to @priv.
> Join outside the lock guard instead, which also restores the ordering
> nodeStateCleanup() used before commit e89d39f5b8f3 ("node_device_udev:
> Introduce and use `stateShutdownPrepare` and `stateShutdownWait`"):
> signal under the lock, release it, then join.
> 
> Fixes: e89d39f5b8f30f0b3284ed2bd71cee1a8a3d707d
> Signed-off-by: Yifan Li <[email protected]>
> Signed-off-by: Jianfeng Gao <[email protected]>
> Signed-off-by: Lili Li <[email protected]>
> ---
> How found: shutdown on Ubuntu 26.04 with libvirt 12.0.0(monolithic
> libvirtd, socket activated), found it was killed by systemd due to
> timeout. In our testing(repeated S5 cycles), it is about 4% hit rate.
> 
> State of a hung daemon:
> 
>     tid     comm              wchan
>     14039   libvirtd          poll_schedule_timeout
>     14108   udev-event        futex_do_wait
>     14165   daemon-shutdown   futex_do_wait
> 
>     Thread "daemon-shutdown" (LWP 14165):
>       #5  __pthread_clockjoin_ex ()
>       #6  libvirt_driver_nodedev.so
>       #7  virStateShutdownWait () from libvirt.so.0
> 
>     Thread "udev-event" (LWP 14108):
>       #1  __GI___lll_lock_wait (futex=0x7d55d001cc58)
>       #2  __pthread_mutex_cond_lock (mutex=0x7d55d001cc58)
>       #4  ___pthread_cond_wait ()
>       #5  virCondWait () from libvirt.so.0
>       #6  libvirt_driver_nodedev.so
> 
>     (gdb) x/4dw 0x7d55d001cc58
>     0x7d55d001cc58:  2   0   14165   2
>                   __lock  __count  __owner  __nusers
> 
> __owner is the daemon-shutdown thread itself, parked in pthread_join()
> waiting for the very thread that needs that lock in order to exit.
> 
>  src/node_device/node_device_udev.c | 15 +++++++++++----
>  1 file changed, 11 insertions(+), 4 deletions(-)

Reviewed-by: Michal Privoznik <[email protected]>

and merged. Congratulations on your first libvirt contribution!

Michal

Reply via email to