On 7 Oct 2024, at 12:20, Kevin Traynor wrote:

> If there is a device notification from the vhost-event thread
> that calls destroy_device() and the user deletes the vhost port at
> the same time, there is a possibility of a deadlock occuring. e.g.
>
> vhost-event thread is blocking on the ovsrcu_synchronize(), waiting
> for main thread to queisce, i.e.
>
> 0x0000000003690c09 in ovsrcu_synchronize () at lib/ovs-rcu.c:237
> 0x00000000037162fa in destroy_device (vid=0) at lib/netdev-dpdk.c:4919
> 0x0000000003238a12 in vhost_destroy_device_notify (dev=0x14ba693c0)
> at ../lib/vhost/vhost.c:756
>
> While main thread is looping in rte_vhost_driver_unregister(), waiting
> for vhost-event callback to finish, meaning it cannot quiesce. i.e.:
>
> 0x00000000032336e8 in rte_vhost_driver_unregister (path=0xd217a30
> "/tmp/vhost0") at ../lib/vhost/socket.c:1075
> 0x000000000370d5f3 in dpdk_vhost_driver_unregister (dev=0x17d6e8b40,
> vhost_id=0xd217a30 "/tmp/vhost0") at lib/netdev-dpdk.c:1811
> 0x000000000370d709 in netdev_dpdk_vhost_destruct
> (netdev=0x17d6e8bc0) at lib/netdev-dpdk.c:1843
>
> In order to prevent this situation, use ovsrcu to postpone the call to
> rte_vhost_driver_unregister(). This means that main thread will not
> block and will quiesce, allowing ovsrcu_synchornize() in destroy_device()
> to complete.
>
> Fixes: afee281f7f4f ("netdev-dpdk: Fix dpdk_watchdog failure to quiesce.")
> Reported-by: Xinxin Zhao <[email protected]>
> Signed-off-by: Kevin Traynor <[email protected]>

Thanks Kevin for this alternative approach. The design looks good to me, some 
comments are below. In addition is it easy to replicate, and maybe possible in 
a unit test? If not in a unit test, it might be good to add a comment to the 
commit message on how to replicate the issue.

//Eelco

> ---
>  lib/netdev-dpdk.c | 66 +++++++++++++++++++++++++++++++++++------------
>  1 file changed, 50 insertions(+), 16 deletions(-)
>
> diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
> index e454a4a5d..ab1cca07c 100644
> --- a/lib/netdev-dpdk.c
> +++ b/lib/netdev-dpdk.c
> @@ -201,4 +201,11 @@ static const struct rte_vhost_device_ops 
> virtio_net_device_ops =
>  };
>
> +/* Information used for postponed vhost driver unregistering. */
> +struct vhost_unregister_info {
> +    char *vhost_id;
> +    char *name;
> +    bool is_server;
> +};
> +
>  /* Custom software stats for dpdk ports */
>  struct netdev_dpdk_sw_stats {
> @@ -1802,11 +1809,40 @@ netdev_dpdk_destruct(struct netdev *netdev)
>   * try to acquire 'dpdk_mutex' and possibly 'dev->mutex'.  To avoid a
>   * deadlock, none of the mutexes must be held while calling this function. */

Is this comment still valid as you removed the excluded below?

> -static int
> -dpdk_vhost_driver_unregister(struct netdev_dpdk *dev OVS_UNUSED,
> -                             char *vhost_id)
> +static void
> +dpdk_vhost_driver_unregister(struct vhost_unregister_info *vhost_info)
>      OVS_EXCLUDED(dpdk_mutex)
> -    OVS_EXCLUDED(dev->mutex)
>  {
> -    return rte_vhost_driver_unregister(vhost_id);
> +    char *vhost_id = vhost_info->vhost_id;
> +    char *name = vhost_info->name;
> +
> +    if (rte_vhost_driver_unregister(vhost_id)) {
> +        VLOG_ERR("%s: Unable to unregister vhost driver for socket '%s'.\n",
> +                 name, vhost_id);
> +    } else if (vhost_info->is_server) {
> +        /* OVS server mode - remove this socket from list for deletion */
> +        fatal_signal_remove_file_to_unlink(vhost_id);
> +    }
> +    free(vhost_id);
> +    free(name);
> +    free(vhost_info);
> +}
> +
> +/* Postpone calling rte_vhost_driver_unregister() so it is not called from 
> the
> + * main thread otherwise it may deadlock with a device notification calling
> + * destroy_device(). i.e. rte_vhost_driver_unregister() from main thread
> + * blocks waiting for destroy_device() to complete, destroy_device() blocks
> + * waiting for main thread to quiesce. */
> +static void
> +dpdk_vhost_driver_unregister_postpone(const char *vhost_id, const char *name,
> +                                      bool is_server)
> +{
> +    struct vhost_unregister_info *vhost_info;
> +
> +    vhost_info = xmalloc(sizeof *vhost_info);
> +    vhost_info->vhost_id = xstrdup(vhost_id);
> +    vhost_info->name = xstrdup(name);
> +    vhost_info->is_server = is_server;
> +
> +    ovsrcu_postpone(dpdk_vhost_driver_unregister, vhost_info);
>  }
>
> @@ -1837,16 +1873,11 @@ netdev_dpdk_vhost_destruct(struct netdev *netdev)
>
>      if (!vhost_id) {
> -        goto out;
> +        free(vhost_id);

Don’t think we need to free the vhost_id here, we need to free it before we 
exit this function.
Or we could re-design dpdk_vhost_driver_unregister_postpone() to take ownership 
of the vhost_id to avoid a strdup().

> +        return;
>      }
>
> -    if (dpdk_vhost_driver_unregister(dev, vhost_id)) {
> -        VLOG_ERR("%s: Unable to unregister vhost driver for socket '%s'.\n",
> -                 netdev->name, vhost_id);
> -    } else if (!(dev->vhost_driver_flags & RTE_VHOST_USER_CLIENT)) {
> -        /* OVS server mode - remove this socket from list for deletion */
> -        fatal_signal_remove_file_to_unlink(vhost_id);
> -    }
> -out:
> -    free(vhost_id);
> +    dpdk_vhost_driver_unregister_postpone(vhost_id, netdev_get_name(netdev),
> +                                          !(dev->vhost_driver_flags
> +                                          & RTE_VHOST_USER_CLIENT));
>  }
>
> @@ -6285,5 +6316,8 @@ netdev_dpdk_vhost_client_reconfigure(struct netdev 
> *netdev)
>
>      if (unregister) {
> -        dpdk_vhost_driver_unregister(dev, vhost_id);
> +        dpdk_vhost_driver_unregister_postpone(vhost_id,
> +                                              netdev_get_name(netdev),
> +                                              !(dev->vhost_driver_flags
> +                                              & RTE_VHOST_USER_CLIENT));
>      }
>
> -- 
> 2.46.2

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to