On 10/11/2017 10:52 AM, Erik Skultety wrote:
Adjust udevEventHandleThread to be a proper thread routine running in
an
infinite loop handling devices. The handler thread pulls all available
data from the udev monitor and only then waits until a wakeup signal for
new incoming data has been emitted by udevEventHandleCallback.
This doing a bit more by removing the driver locks from initialization
too. Perhaps those locks should be kept on Initialization for now and
then in a followup patch remove them since @privateData (or whatever
name it becomes) is then totally self locking.
I don't think we'd run into any deadlocks since Initialization and
Cleanup would still be the only consumers.
Signed-off-by: Erik Skultety <eskultet(a)redhat.com>
---
src/node_device/node_device_udev.c | 125 +++++++++++++++++++++++++++----------
1 file changed, 93 insertions(+), 32 deletions(-)
diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c
index f7646cd8a..a6d7e6d70 100644
--- a/src/node_device/node_device_udev.c
+++ b/src/node_device/node_device_udev.c
@@ -62,6 +62,12 @@ struct _udevEventData {
struct udev_monitor *udev_monitor;
int watch;
bool privileged;
+
+ /* Thread data */
+ virThread th;
+ virCond threadCond;
+ bool threadQuit;
+ bool dataReady;
};
static virClassPtr udevEventDataClass;
@@ -80,6 +86,8 @@ udevEventDataDispose(void *obj)
udev_unref(udev);
udev_monitor_unref(data->udev_monitor);
+
+ virCondDestroy(&data->threadCond);
}
@@ -108,9 +116,15 @@ udevEventDataNew(void)
if (!(ret = virObjectLockableNew(udevEventDataClass)))
return NULL;
+ if (virCondInit(&ret->threadCond) < 0) {
+ virObjectUnref(ret);
+ return NULL;
+ }
+
ret->watch = -1;
return ret;
-}
+};
+
Stray keystrokes that should be removed - blame the gremlins.
static bool
@@ -1625,15 +1639,25 @@ udevPCITranslateDeinit(void)
static int
nodeStateCleanup(void)
{
+ udevEventDataPtr priv = NULL;
+
if (!driver)
return -1;
+ priv = driver->privateData;
+
Although perhaps impossible, better safe than sorry
if (!priv)
return;
Do we even need nodeDeviceLock now? It was removed from Initialize
shouldn't this be after nodeDeviceLock...
nodeDeviceLock();
- virObjectUnref(driver->privateData);
+ virObjectLock(priv);
+ priv->threadQuit = true;
+ virCondSignal(&priv->threadCond);
+ virObjectUnlock(priv);
+ virThreadJoin(&priv->th);
+
+ virObjectUnref(priv);
virObjectUnref(driver->nodeDeviceEventState);
-
virNodeDeviceObjListFree(driver->devs);
+
nodeDeviceUnlock();
virMutexDestroy(&driver->lock);
VIR_FREE(driver);
@@ -1691,30 +1715,60 @@ udevEventMonitorSanityCheck(udevEventDataPtr priv,
static void
-udevEventHandleThread(void *opaque)
+udevEventHandleThread(void *opaque ATTRIBUTE_UNUSED)
{
udevEventDataPtr priv = driver->privateData;
- int fd = (intptr_t) opaque;
struct udev_device *device = NULL;
- virObjectLock(priv);
+ /* continue rather than break from the loop on non-fatal errors */
+ while (1) {
+ virObjectLock(priv);
+ while (!priv->dataReady && !priv->threadQuit) {
+ if (virCondWait(&priv->threadCond, &priv->parent.lock)) {
+ virReportSystemError(errno, "%s",
+ _("handler failed to wait on
condition"));
+ virObjectUnlock(priv);
+ return;
+ }
+ }
- if (!udevEventMonitorSanityCheck(priv, fd)) {
+ if (priv->threadQuit) {
+ virObjectUnlock(priv);
+ return;
+ }
+
+ errno = 0;
+ device = udev_monitor_receive_device(priv->udev_monitor);
virObjectUnlock(priv);
- return;
- }
- device = udev_monitor_receive_device(priv->udev_monitor);
- virObjectUnlock(priv);
+ if (!device) {
+ if (errno == 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("udev_monitor_receive_device failed"));
+ return;
+ }
- if (device == NULL) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("udev_monitor_receive_device returned NULL"))
- return
- }
+ /* POSIX allows both EAGAIN and EWOULDBLOCK to be used
+ * interchangeably when the read would block or timeout was fired
+ */
+ VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ VIR_WARNINGS_RESET
+ virReportSystemError(errno, "%s",
+ _("udev_monitor_receive_device failed"));
how about "unexpected error receiving udev device"
+ return;
+ }
+
+ virObjectLock(priv);
+ priv->dataReady = false;
+ virObjectUnlock(priv);
- udevHandleOneDevice(device);
- udev_device_unref(device);
+ continue;
+ }
+
+ udevHandleOneDevice(device);
+ udev_device_unref(device);
+ }
}
@@ -1722,18 +1776,19 @@ static void
udevEventHandleCallback(int watch ATTRIBUTE_UNUSED,
int fd,
int events ATTRIBUTE_UNUSED,
- void *data ATTRIBUTE_UNUSED)
+ void *opaque ATTRIBUTE_UNUSED)
{
udevEventDataPtr priv = driver->privateData;
virObjectLock(priv);
- if (!udevEventMonitorSanityCheck(priv, fd)) {
- virObjectUnlock(priv);
- return;
- }
+
+ if (!udevEventMonitorSanityCheck(priv, fd))
+ priv->threadQuit = true;
+ else
+ priv->dataReady = true;
+
+ virCondSignal(&priv->threadCond);
virObjectUnlock(priv);
-
- udevEventHandleThread((void *)(intptr_t) fd);
}
@@ -1875,29 +1930,29 @@ nodeStateInitialize(bool privileged,
return -1;
}
- nodeDeviceLock();
-
And now the only time we ever use nodeDeviceLock/Unlock in
node_device_udev is in Cleanup... Does something else prevent Cleanup
from being called while perhaps Initialize is still running? Looking at
libvirtd.c, virStateCleanup can only be called once driversInitialized
is set. That's only set once virStateInitialize has been run successfully.
Perhaps we should keep it just for this patch and remove in the "next"
patch leaving the goto unlock intact, but modifying it to be :
unlock:
if (priv)
virObjectUnlock(priv);
nodeDeviceUnlock();
Then the next patch alters the gots's and unlock: label to what this
patch has. It also removes nodeDeviceLock from Cleanup
With a few tweaks:
Reviewed-by: John Ferlan <jferlan(a)redhat.com>
John
if (!(driver->devs = virNodeDeviceObjListNew()) ||
!(priv = udevEventDataNew()))
- goto unlock;
+ goto cleanup;
driver->privateData = priv;
driver->nodeDeviceEventState = virObjectEventStateNew();
if (udevPCITranslateInit(privileged) < 0)
- goto unlock;
+ goto cleanup;
udev = udev_new();
if (!udev) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("failed to create udev context"));
- goto unlock;
+ goto cleanup;
}
#if HAVE_UDEV_LOGGING
/* cast to get rid of missing-format-attribute warning */
udev_set_log_fn(udev, (udevLogFunctionPtr) udevLogFunction);
#endif
+ virObjectLock(priv);
+
priv->udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (!priv->udev_monitor) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
@@ -1916,6 +1971,12 @@ nodeStateInitialize(bool privileged,
128 * 1024 * 1024);
#endif
+ if (virThreadCreate(&priv->th, true, udevEventHandleThread, NULL) < 0) {
+ virReportSystemError(errno, "%s",
+ _("failed to create udev handler thread"));
+ goto unlock;
+ }
+
/* 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
@@ -1934,7 +1995,7 @@ nodeStateInitialize(bool privileged,
if (udevSetupSystemDev() != 0)
goto unlock;
- nodeDeviceUnlock();
+ virObjectUnlock(priv);
/* Populate with known devices */
if (udevEnumerateDevices(udev) != 0)
@@ -1947,7 +2008,7 @@ nodeStateInitialize(bool privileged,
return -1;
unlock:
- nodeDeviceUnlock();
+ virObjectUnlock(priv);
goto cleanup;
}