[libvirt] [PATCH v4 4/7] udev: Convert udevEventHandleThread to an actual thread routine

John Ferlan jferlan at redhat.com
Wed Sep 20 13:28:04 UTC 2017



On 09/18/2017 12:34 PM, Erik Skultety wrote:
> Adjust udevEventHandleThread to be a proper thread routine running in an
> infinite loop handling devices. Also introduce udevEventThreadData
> private structure.
> Every time there's and incoming event from udev, udevEventHandleCallback
> only increments the number of events queuing on the monitor and signals
> the worker thread to handle them.
> 
> Signed-off-by: Erik Skultety <eskultet at redhat.com>
> ---
>  src/node_device/node_device_udev.c | 125 +++++++++++++++++++++++++++++++------
>  1 file changed, 107 insertions(+), 18 deletions(-)
> 
> diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c
> index e144472f1..96760d1e4 100644
> --- a/src/node_device/node_device_udev.c
> +++ b/src/node_device/node_device_udev.c
> @@ -59,6 +59,54 @@ struct _udevPrivate {
>      bool privileged;
>  };
>  
> +typedef struct _udevEventThreadData udevEventThreadData;
> +typedef udevEventThreadData *udevEventThreadDataPtr;
> +
> +struct _udevEventThreadData {
> +    virMutex lock;
> +    virCond threadCond;
> +
> +    bool threadQuit;
> +    int monitor_fd;
> +    unsigned long long nevents; /* number of udev events queuing on monitor */
> +};
> +
> +
> +static udevEventThreadDataPtr
> +udevEventThreadDataNew(int fd)
> +{
> +    udevEventThreadDataPtr ret = NULL;
> +
> +    if (VIR_ALLOC_QUIET(ret) < 0)
> +        return NULL;
> +
> +    if (virMutexInit(&ret->lock) < 0)
> +        goto cleanup;
> +
> +    if (virCondInit(&ret->threadCond) < 0)
> +        goto cleanup_mutex;
> +
> +    ret->monitor_fd = fd;
> +
> +    return ret;
> +
> + cleanup_mutex:
> +    virMutexDestroy(&ret->lock);
> +
> + cleanup:
> +    VIR_FREE(ret);
> +    return NULL;
> +}
> +
> +
> +static void
> +udevEventThreadDataFree(udevEventThreadDataPtr data)
> +{
> +    virMutexDestroy(&data->lock);
> +    virCondDestroy(&data->threadCond);
> +    VIR_FREE(data);
> +}
> +
>  
>  static bool
>  udevHasDeviceProperty(struct udev_device *dev,
> @@ -1648,29 +1696,51 @@ udevEventCheckMonitorFD(struct udev_monitor *udev_monitor,
>  static void
>  udevEventHandleThread(void *opaque)
>  {
> +    udevEventThreadDataPtr privateData = opaque;
>      struct udev_device *device = NULL;
>      struct udev_monitor *udev_monitor = NULL;
> -    int fd = (intptr_t) opaque;
>  
> -    nodeDeviceLock();
> -    udev_monitor = DRV_STATE_UDEV_MONITOR(driver);
> +    /* continue rather than break from the loop on non-fatal errors */
^^
This comment could go to [1]

> +    while (1) {
> +        virMutexLock(&privateData->lock);
> +        while (privateData->nevents == 0 && !privateData->threadQuit) {
> +            if (virCondWait(&privateData->threadCond, &privateData->lock)) {
> +                virReportSystemError(errno, "%s",
> +                                     _("handler failed to wait on condition"));

Is a virMutexUnlock required before eventually calling virMutexDestroy...

> +                goto cleanup;
> +            }
> +        }
>  
> -    if (!udevEventCheckMonitorFD(udev_monitor, fd)) {
> +        privateData->nevents--;
> +        virMutexUnlock(&privateData->lock);

If we get here, then either nevents > 0 || threadQuit == true, but we
don't check for threadQuit here before the fetch/check of monitor_fd,
e.g. the reason for threadQuit = true, so although the following
udev_monitor check "works", the question thus becomes is it necessary if
threadQuit == true?  I suppose it could be, but we could also jump to
cleanup if threadQuit == true || !udevEventCheckMonitorFD

> +
> +        nodeDeviceLock();
> +        udev_monitor = DRV_STATE_UDEV_MONITOR(driver);
> +
> +        if (!udevEventCheckMonitorFD(udev_monitor, privateData->monitor_fd)) {

This accesses privateData->monitor_fd without the mutex. So we don't
have too many lock/unlock - consider a local @monitor_fd which is
fetched while the lock is held.

> +            nodeDeviceUnlock();
> +            goto cleanup;
> +        }
> +
> +        device = udev_monitor_receive_device(udev_monitor);
>          nodeDeviceUnlock();
> -        return;
> -    }
>  
> -    device = udev_monitor_receive_device(udev_monitor);
> -    nodeDeviceUnlock();

[1]  could move the comment here since that's what I believe it's meant
to describe...

> +        if (!device) {
> +            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                           _("udev_monitor_receive_device returned NULL"));

Perhaps a VIR_WARN? Doesn't perhaps really matter, but it's not an error
it's just a condition we didn't expect that we're continuing on...


> +            goto next;

This should just be a continue; instead of needing next... Not clear
what happens if udev_device_unref(NULL) is called.

> +        }
> +
> +        udevHandleOneDevice(device);
>  
> -    if (!device) {
> -        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> -                       _("udev_monitor_receive_device returned NULL"));
> -        return;
> +    next:
> +        udev_device_unref(device);
>      }
>  
> -    udevHandleOneDevice(device);
> + cleanup:
>      udev_device_unref(device);

Should this be:

    if (device)
        udev_device_unref(device)


I think the cleanups are obvious, so

Reviewed-by: John Ferlan <jferlan at redhat.com>

John

> +    udevEventThreadDataFree(privateData);
> +    return;
>  }
>  
>  
> @@ -1678,20 +1748,29 @@ static void
>  udevEventHandleCallback(int watch ATTRIBUTE_UNUSED,
>                          int fd,
>                          int events ATTRIBUTE_UNUSED,
> -                        void *data ATTRIBUTE_UNUSED)
> +                        void *opaque)
>  {
>      struct udev_monitor *udev_monitor = NULL;
> +    udevEventThreadDataPtr threadData = opaque;
>  
>      nodeDeviceLock();
>      udev_monitor = DRV_STATE_UDEV_MONITOR(driver);
>  
>      if (!udevEventCheckMonitorFD(udev_monitor, fd)) {
> +        virMutexLock(&threadData->lock);
> +        threadData->threadQuit = true;
> +        virCondSignal(&threadData->threadCond);
> +        virMutexUnlock(&threadData->lock);
> +
>          nodeDeviceUnlock();
>          return;
>      }
>      nodeDeviceUnlock();
>  
> -    udevEventHandleThread((void *)(intptr_t) fd);
> +    virMutexLock(&threadData->lock);
> +    threadData->nevents++;
> +    virCondSignal(&threadData->threadCond);
> +    virMutexUnlock(&threadData->lock);
>  }
>  
>  
> @@ -1818,6 +1897,9 @@ nodeStateInitialize(bool privileged,
>  {
>      udevPrivate *priv = NULL;
>      struct udev *udev = NULL;
> +    int monitor_fd = -1;
> +    virThread th;
> +    udevEventThreadDataPtr threadData = NULL;
>  
>      if (VIR_ALLOC(priv) < 0)
>          return -1;
> @@ -1878,6 +1960,14 @@ nodeStateInitialize(bool privileged,
>                                               128 * 1024 * 1024);
>  #endif
>  
> +    monitor_fd = udev_monitor_get_fd(priv->udev_monitor);
> +    if (!(threadData = udevEventThreadDataNew(monitor_fd)) ||
> +        virThreadCreate(&th, false, udevEventHandleThread, threadData) < 0) {
> +        virReportSystemError(errno, "%s",
> +                             _("failed to create udev handling thread"));
> +        goto cleanup;
> +    }
> +
>      /* We register the monitor with the event callback so we are
>       * notified by udev of device changes before we enumerate existing
>       * devices because libvirt will simply recreate the device if we
> @@ -1886,9 +1976,8 @@ nodeStateInitialize(bool privileged,
>       * enumeration.  The alternative is to register the callback after
>       * we enumerate, in which case we will fail to create any devices
>       * that appear while the enumeration is taking place.  */
> -    priv->watch = virEventAddHandle(udev_monitor_get_fd(priv->udev_monitor),
> -                                    VIR_EVENT_HANDLE_READABLE,
> -                                    udevEventHandleCallback, NULL, NULL);
> +    priv->watch = virEventAddHandle(monitor_fd, VIR_EVENT_HANDLE_READABLE,
> +                                    udevEventHandleCallback, threadData, NULL);
>      if (priv->watch == -1)
>          goto unlock;
>  
> 




More information about the libvir-list mailing list