util: import an event loop impl based on GMainContext

The libvirt-glib project has provided a GMainContext based
event loop impl for applications. This imports it and sets
it up for use by libvirt as the primary event loop. This
remains a private impl detail of libvirt.

IOW, applications must *NOT* assume that a call to
"virEventRegisterDefaultImpl" results in a GLib based
event loop. They should continue to use the libvirt-glib
API gvir_event_register() if they explicitly want to
guarantee a GLib event loop.

This follows the general principle that the libvirt public
API should not expose the fact that GLib is being used
internally.

Reviewed-by: Pavel Hrdina <phrdina@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrangé 2020-01-14 12:34:09 +00:00
parent 7d4350bcac
commit f8ab47cb44
6 changed files with 549 additions and 1 deletions

View File

@ -2176,7 +2176,7 @@ exclude_file_name_regexp--sc_avoid_write = \
exclude_file_name_regexp--sc_bindtextdomain = .*
exclude_file_name_regexp--sc_gettext_init = ^((tests|examples)/|tools/virt-login-shell.c)
exclude_file_name_regexp--sc_gettext_init = ^((tests|examples)/|tools/virt-login-shell.c|src/util/vireventglib\.c)
exclude_file_name_regexp--sc_copyright_format = \
^build-aux/syntax-check\.mk$$

View File

@ -1933,6 +1933,11 @@ virSetErrorLogPriorityFunc;
virStrerror;
# util/vireventglib.h
virEventGLibRegister;
virEventGLibRunOnce;
# util/vireventpoll.h
virEventPollAddHandle;
virEventPollAddTimeout;

View File

@ -15,6 +15,20 @@ provider libvirt {
probe event_poll_run(int nfds, int timeout);
# file: src/util/vireventglib.c
# prefix: event_glib
probe event_glib_add_handle(int watch, int fd, int events, void *cb, void *opaque, void *ff);
probe event_glib_update_handle(int watch, int events);
probe event_glib_remove_handle(int watch);
probe event_glib_remove_handle_idle(int watch, void *ff, void *opaque);
probe event_glib_dispatch_handle(int watch, int events, void *cb, void *opaque);
probe event_glib_add_timeout(int timer, int frequency, void *cb, void *opaque, void *ff);
probe event_glib_update_timeout(int timer, int frequency);
probe event_glib_remove_timeout(int timer);
probe event_glib_remove_timeout_idle(int timer, void *ff, void *opaque);
probe event_glib_dispatch_timeout(int timer, void *cb, void *opaque);
# file: src/util/virdbus.c
# prefix: dbus
probe dbus_method_call(const char *interface, const char *member, const char *object, const char *destination);

View File

@ -61,6 +61,8 @@ UTIL_SOURCES = \
util/virerrorpriv.h \
util/virevent.c \
util/virevent.h \
util/vireventglib.c \
util/vireventglib.h \
util/vireventglibwatch.c \
util/vireventglibwatch.h \
util/vireventpoll.c \

499
src/util/vireventglib.c Normal file
View File

@ -0,0 +1,499 @@
/*
* vireventglib.c: GMainContext based event loop
*
* Copyright (C) 2008 Daniel P. Berrange
* Copyright (C) 2010-2019 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "vireventglib.h"
#include "vireventglibwatch.h"
#include "virerror.h"
#include "virlog.h"
#include "virprobe.h"
#ifdef G_OS_WIN32
# include <io.h>
#endif
#define VIR_FROM_THIS VIR_FROM_EVENT
VIR_LOG_INIT("util.eventglib");
struct virEventGLibHandle
{
int watch;
int fd;
int events;
int removed;
guint source;
virEventHandleCallback cb;
void *opaque;
virFreeCallback ff;
};
struct virEventGLibTimeout
{
int timer;
int interval;
int removed;
guint source;
virEventTimeoutCallback cb;
void *opaque;
virFreeCallback ff;
};
static GMutex *eventlock;
static int nextwatch = 1;
static GPtrArray *handles;
static int nexttimer = 1;
static GPtrArray *timeouts;
static GIOCondition
virEventGLibEventsToCondition(int events)
{
GIOCondition cond = 0;
if (events & VIR_EVENT_HANDLE_READABLE)
cond |= G_IO_IN;
if (events & VIR_EVENT_HANDLE_WRITABLE)
cond |= G_IO_OUT;
if (events & VIR_EVENT_HANDLE_ERROR)
cond |= G_IO_ERR;
if (events & VIR_EVENT_HANDLE_HANGUP)
cond |= G_IO_HUP;
return cond;
}
static int
virEventGLibConditionToEvents(GIOCondition cond)
{
int events = 0;
if (cond & G_IO_IN)
events |= VIR_EVENT_HANDLE_READABLE;
if (cond & G_IO_OUT)
events |= VIR_EVENT_HANDLE_WRITABLE;
if (cond & G_IO_ERR)
events |= VIR_EVENT_HANDLE_ERROR;
if (cond & G_IO_NVAL) /* Treat NVAL as error, since libvirt doesn't distinguish */
events |= VIR_EVENT_HANDLE_ERROR;
if (cond & G_IO_HUP)
events |= VIR_EVENT_HANDLE_HANGUP;
return events;
}
static gboolean
virEventGLibHandleDispatch(int fd G_GNUC_UNUSED,
GIOCondition condition,
gpointer opaque)
{
struct virEventGLibHandle *data = opaque;
int events = virEventGLibConditionToEvents(condition);
VIR_DEBUG("Dispatch handler data=%p watch=%d fd=%d events=%d opaque=%p",
data, data->watch, data->fd, events, data->opaque);
PROBE(EVENT_GLIB_DISPATCH_HANDLE,
"watch=%d events=%d cb=%p opaque=%p",
data->watch, events, data->cb, data->opaque);
(data->cb)(data->watch, data->fd, events, data->opaque);
return TRUE;
}
static int
virEventGLibHandleAdd(int fd,
int events,
virEventHandleCallback cb,
void *opaque,
virFreeCallback ff)
{
struct virEventGLibHandle *data;
GIOCondition cond = virEventGLibEventsToCondition(events);
int ret;
g_mutex_lock(eventlock);
data = g_new0(struct virEventGLibHandle, 1);
data->watch = nextwatch++;
data->fd = fd;
data->events = events;
data->cb = cb;
data->opaque = opaque;
data->ff = ff;
VIR_DEBUG("Add handle data=%p watch=%d fd=%d events=%d opaque=%p",
data, data->watch, data->fd, events, data->opaque);
if (events != 0) {
data->source = virEventGLibAddSocketWatch(
fd, cond, NULL, virEventGLibHandleDispatch, data, NULL);
}
g_ptr_array_add(handles, data);
ret = data->watch;
PROBE(EVENT_GLIB_ADD_HANDLE,
"watch=%d fd=%d events=%d cb=%p opaque=%p ff=%p",
ret, fd, events, cb, opaque, ff);
g_mutex_unlock(eventlock);
return ret;
}
static struct virEventGLibHandle *
virEventGLibHandleFind(int watch)
{
guint i;
for (i = 0; i < handles->len; i++) {
struct virEventGLibHandle *h = g_ptr_array_index(handles, i);
if (h == NULL) {
g_warn_if_reached();
continue;
}
if ((h->watch == watch) && !h->removed)
return h;
}
return NULL;
}
static void
virEventGLibHandleUpdate(int watch,
int events)
{
struct virEventGLibHandle *data;
PROBE(EVENT_GLIB_UPDATE_HANDLE,
"watch=%d events=%d",
watch, events);
g_mutex_lock(eventlock);
data = virEventGLibHandleFind(watch);
if (!data) {
VIR_DEBUG("Update for missing handle watch=%d", watch);
goto cleanup;
}
VIR_DEBUG("Update handle data=%p watch=%d fd=%d events=%d",
data, watch, data->fd, events);
if (events != 0) {
GIOCondition cond = virEventGLibEventsToCondition(events);
if (events == data->events)
goto cleanup;
if (data->source != 0) {
VIR_DEBUG("Removed old handle watch=%d", data->source);
g_source_remove(data->source);
}
data->source = virEventGLibAddSocketWatch(
data->fd, cond, NULL, virEventGLibHandleDispatch, data, NULL);
data->events = events;
VIR_DEBUG("Added new handle watch=%d", data->source);
} else {
if (data->source == 0)
goto cleanup;
VIR_DEBUG("Removed old handle watch=%d", data->source);
g_source_remove(data->source);
data->source = 0;
data->events = 0;
}
cleanup:
g_mutex_unlock(eventlock);
}
static gboolean
virEventGLibHandleRemoveIdle(gpointer data)
{
struct virEventGLibHandle *h = data;
PROBE(EVENT_GLIB_REMOVE_HANDLE_IDLE,
"watch=%d ff=%p opaque=%p",
h->watch, h->ff, h->opaque);
if (h->ff)
(h->ff)(h->opaque);
g_mutex_lock(eventlock);
g_ptr_array_remove_fast(handles, h);
g_mutex_unlock(eventlock);
return FALSE;
}
static int
virEventGLibHandleRemove(int watch)
{
struct virEventGLibHandle *data;
int ret = -1;
PROBE(EVENT_GLIB_REMOVE_HANDLE,
"watch=%d",
watch);
g_mutex_lock(eventlock);
data = virEventGLibHandleFind(watch);
if (!data) {
VIR_DEBUG("Remove of missing handle watch=%d", watch);
goto cleanup;
}
VIR_DEBUG("Remove handle data=%p watch=%d fd=%d",
data, watch, data->fd);
if (data->source != 0) {
g_source_remove(data->source);
data->source = 0;
data->events = 0;
}
/* since the actual watch deletion is done asynchronously, a handleUpdate call may
* reschedule the watch before it's fully deleted, that's why we need to mark it as
* 'removed' to prevent reuse
*/
data->removed = TRUE;
g_idle_add(virEventGLibHandleRemoveIdle, data);
ret = 0;
cleanup:
g_mutex_unlock(eventlock);
return ret;
}
static gboolean
virEventGLibTimeoutDispatch(void *opaque)
{
struct virEventGLibTimeout *data = opaque;
VIR_DEBUG("Dispatch timeout data=%p cb=%p timer=%d opaque=%p",
data, data->cb, data->timer, data->opaque);
PROBE(EVENT_GLIB_DISPATCH_TIMEOUT,
"timer=%d cb=%p opaque=%p",
data->timer, data->cb, data->opaque);
(data->cb)(data->timer, data->opaque);
return TRUE;
}
static int
virEventGLibTimeoutAdd(int interval,
virEventTimeoutCallback cb,
void *opaque,
virFreeCallback ff)
{
struct virEventGLibTimeout *data;
int ret;
g_mutex_lock(eventlock);
data = g_new0(struct virEventGLibTimeout, 1);
data->timer = nexttimer++;
data->interval = interval;
data->cb = cb;
data->opaque = opaque;
data->ff = ff;
if (interval >= 0)
data->source = g_timeout_add(interval,
virEventGLibTimeoutDispatch,
data);
g_ptr_array_add(timeouts, data);
VIR_DEBUG("Add timeout data=%p interval=%d ms cb=%p opaque=%p timer=%d",
data, interval, cb, opaque, data->timer);
ret = data->timer;
PROBE(EVENT_GLIB_ADD_TIMEOUT,
"timer=%d interval=%d cb=%p opaque=%p ff=%p",
ret, interval, cb, opaque, ff);
g_mutex_unlock(eventlock);
return ret;
}
static struct virEventGLibTimeout *
virEventGLibTimeoutFind(int timer)
{
guint i;
g_return_val_if_fail(timeouts != NULL, NULL);
for (i = 0; i < timeouts->len; i++) {
struct virEventGLibTimeout *t = g_ptr_array_index(timeouts, i);
if (t == NULL) {
g_warn_if_reached();
continue;
}
if ((t->timer == timer) && !t->removed)
return t;
}
return NULL;
}
static void
virEventGLibTimeoutUpdate(int timer,
int interval)
{
struct virEventGLibTimeout *data;
PROBE(EVENT_GLIB_UPDATE_TIMEOUT,
"timer=%d interval=%d",
timer, interval);
g_mutex_lock(eventlock);
data = virEventGLibTimeoutFind(timer);
if (!data) {
VIR_DEBUG("Update of missing timeout timer=%d", timer);
goto cleanup;
}
VIR_DEBUG("Update timeout data=%p timer=%d interval=%d ms", data, timer, interval);
if (interval >= 0) {
if (data->source != 0)
g_source_remove(data->source);
data->interval = interval;
data->source = g_timeout_add(data->interval,
virEventGLibTimeoutDispatch,
data);
} else {
if (data->source == 0)
goto cleanup;
g_source_remove(data->source);
data->source = 0;
}
cleanup:
g_mutex_unlock(eventlock);
}
static gboolean
virEventGLibTimeoutRemoveIdle(gpointer data)
{
struct virEventGLibTimeout *t = data;
PROBE(EVENT_GLIB_REMOVE_TIMEOUT_IDLE,
"timer=%d ff=%p opaque=%p",
t->timer, t->ff, t->opaque);
if (t->ff)
(t->ff)(t->opaque);
g_mutex_lock(eventlock);
g_ptr_array_remove_fast(timeouts, t);
g_mutex_unlock(eventlock);
return FALSE;
}
static int
virEventGLibTimeoutRemove(int timer)
{
struct virEventGLibTimeout *data;
int ret = -1;
PROBE(EVENT_GLIB_REMOVE_TIMEOUT,
"timer=%d",
timer);
g_mutex_lock(eventlock);
data = virEventGLibTimeoutFind(timer);
if (!data) {
VIR_DEBUG("Remove of missing timeout timer=%d", timer);
goto cleanup;
}
VIR_DEBUG("Remove timeout data=%p timer=%d",
data, timer);
if (data->source != 0) {
g_source_remove(data->source);
data->source = 0;
}
/* since the actual timeout deletion is done asynchronously, a timeoutUpdate call may
* reschedule the timeout before it's fully deleted, that's why we need to mark it as
* 'removed' to prevent reuse
*/
data->removed = TRUE;
g_idle_add(virEventGLibTimeoutRemoveIdle, data);
ret = 0;
cleanup:
g_mutex_unlock(eventlock);
return ret;
}
static gpointer virEventGLibRegisterOnce(gpointer data G_GNUC_UNUSED)
{
eventlock = g_new0(GMutex, 1);
timeouts = g_ptr_array_new_with_free_func(g_free);
handles = g_ptr_array_new_with_free_func(g_free);
virEventRegisterImpl(virEventGLibHandleAdd,
virEventGLibHandleUpdate,
virEventGLibHandleRemove,
virEventGLibTimeoutAdd,
virEventGLibTimeoutUpdate,
virEventGLibTimeoutRemove);
return NULL;
}
void virEventGLibRegister(void)
{
static GOnce once = G_ONCE_INIT;
g_once(&once, virEventGLibRegisterOnce, NULL);
}
int virEventGLibRunOnce(void)
{
g_main_context_iteration(NULL, TRUE);
return 0;
}

28
src/util/vireventglib.h Normal file
View File

@ -0,0 +1,28 @@
/*
* vireventglib.h: GMainContext based event loop
*
* Copyright (C) 2008 Daniel P. Berrange
* Copyright (C) 2010-2019 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "internal.h"
void virEventGLibRegister(void);
int virEventGLibRunOnce(void);