[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[libvirt] [PATCH] Java bindings for domain events



The attached patch (against libvirt-java) contains Java bindings for the
new domain event code.  It works (see EventTest.java), but there's a
certain amount of hokiness regarding the EventImpl stuff that I'd like
to discuss.

Unlike the C and Python interfaces, the Java interface does not
currently allow the client to supply an EventImpl.  The problem is that
Java really has no way to interact with unix file descriptors so there's
no reasonable way to implement a fd-watching EventImpl in pure Java
(java.io.FileDescriptor doesn't do the trick since there's no way of
constructing one from raw (int) unix fd)[**].

So for now, I've had the Java bindings register an EventImpl when the
Connect class is loaded.  This EventImpl is a Java class, with native
methods implementing it.  In fact, I've simply stolen (verbatim) the
EventImpl from libvirt/qemud/events.c and made the native methods call
it.  [If we stick with this solution, it would obviously be better to
somehow share this code with libvirtd rather than copy it.]

The other tricky subject is multi-threading.  For now, I've avoided it
by exposing Connect.eventImpl.run_once() and forcing the client to call
it from their "event loop".  But the normal Java way of doing things
would simply run the EventImpl in a separate thread.  In fact, this
EventImpl does implement Runnable, which makes it trivial to run in a
separate thread -- but I don't declare that it implements Runnable yet
because it's not safe to run in a thread while another thread might be
making libvirt calls.

It shouldn't be hard to make this thread-safe using Java synchronized
methods and statements, but I haven't done that yet.  Should I??

** java.nio.Channel and friends seem to be the "right" interface for
exposing abstract "selectable channels" in Java.  It's just complicated
enough that I've avoided it for now.  But I can look into going this way
for allowing Java to provide an EventImpl in the future ..

Cheers,
Dave

diff --git a/EventTest.java b/EventTest.java
new file mode 100644
index 0000000..dc01f8b
--- /dev/null
+++ b/EventTest.java
@@ -0,0 +1,35 @@
+import org.libvirt.*;
+
+class TestDomainEventListener implements DomainEventListener {
+
+    String name;
+
+    TestDomainEventListener(String name) {
+	this.name = name;
+    }
+
+    public void handle(Domain dom, int event) {
+	try {
+	    System.out.println(name + ": dom " + dom.getName() + " got event " + event);
+	} catch (LibvirtException e) {
+	    System.out.println(e);
+	    System.out.println(name + ": unknown dom got event " + event);
+	}
+    }
+}
+
+class EventTest {
+
+    public static void main(String args[]) throws LibvirtException {
+	String URI = "qemu:///system";
+	if (args.length > 0)
+	    URI = args[0];
+	Connect conn = new Connect(URI);
+	conn.domainEventRegister(new TestDomainEventListener("Test 1"));
+	conn.domainEventRegister(new TestDomainEventListener("Test 2"));
+
+	while (true) {
+	    conn.eventImpl.run_once();
+	}
+    }
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index 5200c1d..a265de9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,9 +7,11 @@ java_libvirt_source_files = \
   org/libvirt/ConnectAuthDefault.java \
   org/libvirt/ConnectAuth.java \
   org/libvirt/DomainBlockStats.java \
+  org/libvirt/DomainEventListener.java \
   org/libvirt/DomainInfo.java \
   org/libvirt/DomainInterfaceStats.java \
   org/libvirt/Domain.java \
+  org/libvirt/EventImpl.java \
   org/libvirt/ErrorException.java \
   org/libvirt/Error.java \
   org/libvirt/Network.java \
diff --git a/src/jni/Makefile.am b/src/jni/Makefile.am
index c894024..829298a 100644
--- a/src/jni/Makefile.am
+++ b/src/jni/Makefile.am
@@ -5,6 +5,7 @@ GENERATED = \
   org_libvirt_Domain_CreateFlags.h \
   org_libvirt_Domain_MigrateFlags.h \
   org_libvirt_Domain_XMLFlags.h \
+  org_libvirt_EventImpl.h \
   org_libvirt_StoragePool_BuildFlags.h \
   org_libvirt_StoragePool_DeleteFlags.h \
   org_libvirt_StoragePool.h \
@@ -25,6 +26,9 @@ org_libvirt_Network.h: $(JAVA_CLASS_ROOT)/org/libvirt/Network.class
 org_libvirt_Domain.h org_libvirt_Domain_CreateFlags.h org_libvirt_Domain_MigrateFlags.h org_libvirt_Domain_XMLFlags.h : $(JAVA_CLASS_ROOT)/org/libvirt/Domain.class
 	$(JAVAH) -classpath $(JAVA_CLASS_ROOT) org.libvirt.Domain
 
+org_libvirt_EventImpl.h : $(JAVA_CLASS_ROOT)/org/libvirt/EventImpl.class
+	$(JAVAH) -classpath $(JAVA_CLASS_ROOT) org.libvirt.EventImpl
+
 org_libvirt_StoragePool.h org_libvirt_StoragePool_BuildFlags.h org_libvirt_StoragePool_DeleteFlags.h : $(JAVA_CLASS_ROOT)/org/libvirt/StoragePool.class
 	$(JAVAH) -classpath $(JAVA_CLASS_ROOT) org.libvirt.StoragePool
 
@@ -36,6 +40,7 @@ libvirt_jni_la_SOURCES = \
   org_libvirt_Network.c \
   org_libvirt_Connect.c \
   org_libvirt_Domain.c \
+  org_libvirt_EventImpl.c \
   org_libvirt_StoragePool.c \
   org_libvirt_StorageVol.c \
   generic.h \
diff --git a/src/jni/org_libvirt_Connect.c b/src/jni/org_libvirt_Connect.c
index cbf437c..3107159 100644
--- a/src/jni/org_libvirt_Connect.c
+++ b/src/jni/org_libvirt_Connect.c
@@ -1,3 +1,4 @@
+#include <jni.h>
 #include "org_libvirt_Connect.h"
 #include <libvirt/libvirt.h>
 #include <stdlib.h>
@@ -361,3 +362,76 @@ JNIEXPORT jlong JNICALL Java_org_libvirt_Connect__1virStoragePoolLookupByName
 	GENERIC_LOOKUPBY_STRING(env, obj, (virConnectPtr)VCP, j_name, virStoragePoolLookupByName)
 }
 
+
+static JavaVM *jvm;
+
+jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
+        jvm = vm;
+        return JNI_VERSION_1_4;
+}
+
+static int domainEventCallback(virConnectPtr conn, virDomainPtr dom,
+                               int event, void *opaque)
+{
+        jobject conn_obj = (jobject)opaque;
+        JNIEnv * env;
+        jobject dom_obj;
+        jclass dom_cls, conn_cls;
+        jmethodID ctorId, methId;
+    
+        // Invoke  conn.fireDomainEventCallbacks(dom, event)
+
+        if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4) != JNI_OK || env == NULL) {
+                printf("error getting JNIEnv\n");
+                return -1;
+        }
+    
+        dom_cls = (*env)->FindClass(env, "org/libvirt/Domain");
+        if (dom_cls == NULL) {
+                printf("error finding class Domain\n");
+                return -1;
+        }
+
+        ctorId = (*env)->GetMethodID(env, dom_cls, "<init>", "(Lorg/libvirt/Connect;J)V");
+        if (ctorId == NULL) {
+                printf("error finding Domain constructor\n");
+                return -1;
+        }
+
+        dom_obj = (*env)->NewObject(env, dom_cls, ctorId, conn_obj, dom);
+        if (dom_obj == NULL) {
+                printf("error constructing Domain\n");
+                return -1;
+        }
+
+        conn_cls = (*env)->FindClass(env, "org/libvirt/Connect");
+        if (conn_cls == NULL) {
+                printf("error finding class Connect\n");
+                return -1;
+        }
+
+        methId = (*env)->GetMethodID(env, conn_cls, "fireDomainEventCallbacks", "(Lorg/libvirt/Domain;I)V");
+        if (methId == NULL) {
+                printf("error finding Connect.fireDomainEventCallbacks\n");
+                return -1;
+        }
+    
+        (*env)->CallVoidMethod(env, conn_obj, methId, dom_obj, event);
+
+        return 0;        
+}
+
+
+JNIEXPORT void JNICALL Java_org_libvirt_Connect_registerForDomainEvents
+(JNIEnv *env, jobject obj, jlong VCP){
+        // TODO: Need to DeleteGlobalRef(obj) when deregistering for callbacks.
+        //       But then need to track global obj per Connect object.
+        obj = (*env)->NewGlobalRef(env, obj);
+        virConnectDomainEventRegister((virConnectPtr)VCP, domainEventCallback, obj);
+}
+
+JNIEXPORT void JNICALL Java_org_libvirt_Connect_deregisterForDomainEvents
+(JNIEnv *env, jobject obj, jlong VCP){
+        virConnectDomainEventDeregister((virConnectPtr)VCP, domainEventCallback);
+}
+
diff --git a/src/jni/org_libvirt_EventImpl.c b/src/jni/org_libvirt_EventImpl.c
new file mode 100644
index 0000000..8569d2c
--- /dev/null
+++ b/src/jni/org_libvirt_EventImpl.c
@@ -0,0 +1,542 @@
+#include <stdlib.h>
+#include <poll.h>
+#include <string.h>
+#include <errno.h>
+
+#include "org_libvirt_EventImpl.h"
+#include <libvirt/libvirt.h>
+
+#define EVENT_DEBUG(fmt, ...) //do { printf("%s:%d (" fmt ")\n", __FUNCTION__, __LINE__, __VA_ARGS__); } while (0);
+
+// Copied from libvirt/src/memory.h:
+
+#define VIR_ALLOC_N(ptr, count) __virAllocN(&(ptr), sizeof(*(ptr)), (count))
+#define VIR_REALLOC_N(ptr, count) __virReallocN(&(ptr), sizeof(*(ptr)), (count))
+#define VIR_FREE(ptr) __virFree(&(ptr))
+
+// Copied from libvirt/src/memory.c:
+
+static int __virAllocN(void *ptrptr, size_t size, size_t count)
+{
+    *(void**)ptrptr = calloc(count, size);
+    if (*(void**)ptrptr == NULL)
+        return -1;
+    return 0;
+}
+
+static int __virReallocN(void *ptrptr, size_t size, size_t count)
+{
+    void *tmp;
+    tmp = realloc(*(void**)ptrptr, size * count);
+    if (!tmp && (size * count))
+        return -1;
+    *(void**)ptrptr = tmp;
+    return 0;
+}
+
+static void __virFree(void *ptrptr)
+{
+    free(*(void**)ptrptr);
+    *(void**)ptrptr = NULL;
+}
+
+
+// START Copied from libvirt/qemud/events.c
+
+/* State for a single file handle being monitored */
+struct virEventHandle {
+    int fd;
+    int events;
+    virEventHandleCallback cb;
+    void *opaque;
+    int deleted;
+};
+
+/* State for a single timer being generated */
+struct virEventTimeout {
+    int timer;
+    int frequency;
+    unsigned long long expiresAt;
+    virEventTimeoutCallback cb;
+    void *opaque;
+    int deleted;
+};
+
+/* Allocate extra slots for virEventHandle/virEventTimeout
+   records in this multiple */
+#define EVENT_ALLOC_EXTENT 10
+
+/* State for the main event loop */
+struct virEventLoop {
+    int handlesCount;
+    int handlesAlloc;
+    struct virEventHandle *handles;
+    int timeoutsCount;
+    int timeoutsAlloc;
+    struct virEventTimeout *timeouts;
+};
+
+/* Only have one event loop */
+static struct virEventLoop eventLoop;
+
+/* Unique ID for the next timer to be registered */
+static int nextTimer = 0;
+
+/*
+ * Register a callback for monitoring file handle events.
+ * NB, it *must* be safe to call this from within a callback
+ * For this reason we only ever append to existing list.
+ */
+int virEventAddHandleImpl(int fd, int events, virEventHandleCallback cb,
+                          void *opaque) {
+    EVENT_DEBUG("Add handle %d %d %p %p", fd, events, cb, opaque);
+    if (eventLoop.handlesCount == eventLoop.handlesAlloc) {
+        EVENT_DEBUG("Used %d handle slots, adding %d more",
+                    eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT);
+        if (VIR_REALLOC_N(eventLoop.handles,
+                          (eventLoop.handlesAlloc + EVENT_ALLOC_EXTENT)) < 0)
+            return -1;
+        eventLoop.handlesAlloc += EVENT_ALLOC_EXTENT;
+    }
+
+    eventLoop.handles[eventLoop.handlesCount].fd = fd;
+    eventLoop.handles[eventLoop.handlesCount].events =
+                                         virEventHandleTypeToPollEvent(events);
+    eventLoop.handles[eventLoop.handlesCount].cb = cb;
+    eventLoop.handles[eventLoop.handlesCount].opaque = opaque;
+    eventLoop.handles[eventLoop.handlesCount].deleted = 0;
+
+    eventLoop.handlesCount++;
+
+    return 0;
+}
+
+void virEventUpdateHandleImpl(int fd, int events) {
+    int i;
+    for (i = 0 ; i < eventLoop.handlesCount ; i++) {
+        if (eventLoop.handles[i].fd == fd) {
+            eventLoop.handles[i].events =
+                    virEventHandleTypeToPollEvent(events);
+            break;
+        }
+    }
+}
+
+/*
+ * Unregister a callback from a file handle
+ * NB, it *must* be safe to call this from within a callback
+ * For this reason we only ever set a flag in the existing list.
+ * Actual deletion will be done out-of-band
+ */
+int virEventRemoveHandleImpl(int fd) {
+    int i;
+    EVENT_DEBUG("Remove handle %d", fd);
+    for (i = 0 ; i < eventLoop.handlesCount ; i++) {
+        if (eventLoop.handles[i].deleted)
+            continue;
+
+        if (eventLoop.handles[i].fd == fd) {
+            EVENT_DEBUG("mark delete %d", i);
+            eventLoop.handles[i].deleted = 1;
+            return 0;
+        }
+    }
+    return -1;
+}
+
+
+/*
+ * Register a callback for a timer event
+ * NB, it *must* be safe to call this from within a callback
+ * For this reason we only ever append to existing list.
+ */
+int virEventAddTimeoutImpl(int frequency, virEventTimeoutCallback cb, void *opaque) {
+    struct timeval now;
+    EVENT_DEBUG("Adding timer %d with %d ms freq", nextTimer, frequency);
+    if (gettimeofday(&now, NULL) < 0) {
+        return -1;
+    }
+
+    if (eventLoop.timeoutsCount == eventLoop.timeoutsAlloc) {
+        EVENT_DEBUG("Used %d timeout slots, adding %d more",
+                    eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT);
+        if (VIR_REALLOC_N(eventLoop.timeouts,
+                          (eventLoop.timeoutsAlloc + EVENT_ALLOC_EXTENT)) < 0)
+            return -1;
+        eventLoop.timeoutsAlloc += EVENT_ALLOC_EXTENT;
+    }
+
+    eventLoop.timeouts[eventLoop.timeoutsCount].timer = nextTimer++;
+    eventLoop.timeouts[eventLoop.timeoutsCount].frequency = frequency;
+    eventLoop.timeouts[eventLoop.timeoutsCount].cb = cb;
+    eventLoop.timeouts[eventLoop.timeoutsCount].opaque = opaque;
+    eventLoop.timeouts[eventLoop.timeoutsCount].deleted = 0;
+    eventLoop.timeouts[eventLoop.timeoutsCount].expiresAt =
+        frequency >= 0 ? frequency +
+        (((unsigned long long)now.tv_sec)*1000) +
+        (((unsigned long long)now.tv_usec)/1000) : 0;
+
+    eventLoop.timeoutsCount++;
+
+    return nextTimer-1;
+}
+
+void virEventUpdateTimeoutImpl(int timer, int frequency) {
+    struct timeval tv;
+    int i;
+    EVENT_DEBUG("Updating timer %d timeout with %d ms freq", timer, frequency);
+    if (gettimeofday(&tv, NULL) < 0) {
+        return;
+    }
+
+    for (i = 0 ; i < eventLoop.timeoutsCount ; i++) {
+        if (eventLoop.timeouts[i].timer == timer) {
+            eventLoop.timeouts[i].frequency = frequency;
+            eventLoop.timeouts[i].expiresAt =
+                frequency >= 0 ? frequency +
+                (((unsigned long long)tv.tv_sec)*1000) +
+                (((unsigned long long)tv.tv_usec)/1000) : 0;
+            break;
+        }
+    }
+}
+
+/*
+ * Unregister a callback for a timer
+ * NB, it *must* be safe to call this from within a callback
+ * For this reason we only ever set a flag in the existing list.
+ * Actual deletion will be done out-of-band
+ */
+int virEventRemoveTimeoutImpl(int timer) {
+    int i;
+    EVENT_DEBUG("Remove timer %d", timer);
+    for (i = 0 ; i < eventLoop.timeoutsCount ; i++) {
+        if (eventLoop.timeouts[i].deleted)
+            continue;
+
+        if (eventLoop.timeouts[i].timer == timer) {
+            eventLoop.timeouts[i].deleted = 1;
+            return 0;
+        }
+    }
+    return -1;
+}
+
+/* Iterates over all registered timeouts and determine which
+ * will be the first to expire.
+ * @timeout: filled with expiry time of soonest timer, or -1 if
+ *           no timeout is pending
+ * returns: 0 on success, -1 on error
+ */
+static int virEventCalculateTimeout(int *timeout) {
+    unsigned long long then = 0;
+    int i;
+    EVENT_DEBUG("Calculate expiry of %d timers", eventLoop.timeoutsCount);
+    /* Figure out if we need a timeout */
+    for (i = 0 ; i < eventLoop.timeoutsCount ; i++) {
+        if (eventLoop.timeouts[i].deleted || eventLoop.timeouts[i].frequency < 0)
+            continue;
+
+        EVENT_DEBUG("Got a timeout scheduled for %llu", eventLoop.timeouts[i].expiresAt);
+        if (then == 0 ||
+            eventLoop.timeouts[i].expiresAt < then)
+            then = eventLoop.timeouts[i].expiresAt;
+    }
+
+    /* Calculate how long we should wait for a timeout if needed */
+    if (then > 0) {
+        struct timeval tv;
+
+        if (gettimeofday(&tv, NULL) < 0) {
+            return -1;
+        }
+
+        *timeout = then -
+            ((((unsigned long long)tv.tv_sec)*1000) +
+             (((unsigned long long)tv.tv_usec)/1000));
+
+        if (*timeout < 0)
+            *timeout = 0;
+    } else {
+        *timeout = -1;
+    }
+
+    EVENT_DEBUG("Timeout at %llu due in %d ms", then, *timeout);
+
+    return 0;
+}
+
+/*
+ * Allocate a pollfd array containing data for all registered
+ * file handles. The caller must free the returned data struct
+ * returns: the pollfd array, or NULL on error
+ */
+static int virEventMakePollFDs(struct pollfd **retfds) {
+    struct pollfd *fds;
+    int i, nfds = 0;
+
+    for (i = 0 ; i < eventLoop.handlesCount ; i++) {
+        if (eventLoop.handles[i].deleted)
+            continue;
+        nfds++;
+    }
+    *retfds = NULL;
+    /* Setup the poll file handle data structs */
+    if (VIR_ALLOC_N(fds, nfds) < 0)
+        return -1;
+
+    for (i = 0, nfds = 0 ; i < eventLoop.handlesCount ; i++) {
+        if (eventLoop.handles[i].deleted)
+            continue;
+        fds[nfds].fd = eventLoop.handles[i].fd;
+        fds[nfds].events = eventLoop.handles[i].events;
+        fds[nfds].revents = 0;
+        //EVENT_DEBUG("Wait for %d %d", eventLoop.handles[i].fd, eventLoop.handles[i].events);
+        nfds++;
+    }
+
+    *retfds = fds;
+    return nfds;
+}
+
+
+/*
+ * Iterate over all timers and determine if any have expired.
+ * Invoke the user supplied callback for each timer whose
+ * expiry time is met, and schedule the next timeout. Does
+ * not try to 'catch up' on time if the actual expiry time
+ * was later than the requested time.
+ *
+ * This method must cope with new timers being registered
+ * by a callback, and must skip any timers marked as deleted.
+ *
+ * Returns 0 upon success, -1 if an error occurred
+ */
+static int virEventDispatchTimeouts(void) {
+    struct timeval tv;
+    unsigned long long now;
+    int i;
+    /* Save this now - it may be changed during dispatch */
+    int ntimeouts = eventLoop.timeoutsCount;
+
+    if (gettimeofday(&tv, NULL) < 0) {
+        return -1;
+    }
+    now = (((unsigned long long)tv.tv_sec)*1000) +
+        (((unsigned long long)tv.tv_usec)/1000);
+
+    for (i = 0 ; i < ntimeouts ; i++) {
+        if (eventLoop.timeouts[i].deleted || eventLoop.timeouts[i].frequency < 0)
+            continue;
+
+        if (eventLoop.timeouts[i].expiresAt <= now) {
+            (eventLoop.timeouts[i].cb)(eventLoop.timeouts[i].timer,
+                                       eventLoop.timeouts[i].opaque);
+            eventLoop.timeouts[i].expiresAt =
+                now + eventLoop.timeouts[i].frequency;
+        }
+    }
+    return 0;
+}
+
+
+/* Iterate over all file handles and dispatch any which
+ * have pending events listed in the poll() data. Invoke
+ * the user supplied callback for each handle which has
+ * pending events
+ *
+ * This method must cope with new handles being registered
+ * by a callback, and must skip any handles marked as deleted.
+ *
+ * Returns 0 upon success, -1 if an error occurred
+ */
+static int virEventDispatchHandles(struct pollfd *fds) {
+    int i;
+    virEventHandleType hEvents;
+    /* Save this now - it may be changed during dispatch */
+    int nhandles = eventLoop.handlesCount;
+
+    for (i = 0 ; i < nhandles ; i++) {
+        if (eventLoop.handles[i].deleted) {
+            EVENT_DEBUG("Skip deleted %d", eventLoop.handles[i].fd);
+            continue;
+        }
+
+        if (fds[i].revents) {
+            hEvents = virPollEventToEventHandleType(fds[i].revents);
+            EVENT_DEBUG("Dispatch %d %d %p", fds[i].fd, fds[i].revents,
+                        eventLoop.handles[i].opaque);
+            (eventLoop.handles[i].cb)(fds[i].fd, hEvents,
+                                      eventLoop.handles[i].opaque);
+        }
+    }
+
+    return 0;
+}
+
+
+/* Used post dispatch to actually remove any timers that
+ * were previously marked as deleted. This asynchronous
+ * cleanup is needed to make dispatch re-entrant safe.
+ */
+static int virEventCleanupTimeouts(void) {
+    int i;
+
+    /* Remove deleted entries, shuffling down remaining
+     * entries as needed to form contiguous series
+     */
+    for (i = 0 ; i < eventLoop.timeoutsCount ; ) {
+        if (!eventLoop.timeouts[i].deleted) {
+            i++;
+            continue;
+        }
+
+        EVENT_DEBUG("Purging timeout %d with id %d", i, eventLoop.timeouts[i].timer);
+        if ((i+1) < eventLoop.timeoutsCount) {
+            memmove(eventLoop.timeouts+i,
+                    eventLoop.timeouts+i+1,
+                    sizeof(struct virEventTimeout)*(eventLoop.timeoutsCount-(i+1)));
+        }
+        eventLoop.timeoutsCount--;
+    }
+
+    /* Release some memory if we've got a big chunk free */
+    if ((eventLoop.timeoutsAlloc - EVENT_ALLOC_EXTENT) > eventLoop.timeoutsCount) {
+        EVENT_DEBUG("Releasing %d out of %d timeout slots used, releasing %d",
+                   eventLoop.timeoutsCount, eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT);
+        if (VIR_REALLOC_N(eventLoop.timeouts,
+                          (eventLoop.timeoutsAlloc - EVENT_ALLOC_EXTENT)) < 0)
+            return -1;
+        eventLoop.timeoutsAlloc -= EVENT_ALLOC_EXTENT;
+    }
+    return 0;
+}
+
+/* Used post dispatch to actually remove any handles that
+ * were previously marked as deleted. This asynchronous
+ * cleanup is needed to make dispatch re-entrant safe.
+ */
+static int virEventCleanupHandles(void) {
+    int i;
+
+    /* Remove deleted entries, shuffling down remaining
+     * entries as needed to form contiguous series
+     */
+    for (i = 0 ; i < eventLoop.handlesCount ; ) {
+        if (!eventLoop.handles[i].deleted) {
+            i++;
+            continue;
+        }
+
+        if ((i+1) < eventLoop.handlesCount) {
+            memmove(eventLoop.handles+i,
+                    eventLoop.handles+i+1,
+                    sizeof(struct virEventHandle)*(eventLoop.handlesCount-(i+1)));
+        }
+        eventLoop.handlesCount--;
+    }
+
+    /* Release some memory if we've got a big chunk free */
+    if ((eventLoop.handlesAlloc - EVENT_ALLOC_EXTENT) > eventLoop.handlesCount) {
+        EVENT_DEBUG("Releasing %d out of %d handles slots used, releasing %d",
+                   eventLoop.handlesCount, eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT);
+        if (VIR_REALLOC_N(eventLoop.handles,
+                          (eventLoop.handlesAlloc - EVENT_ALLOC_EXTENT)) < 0)
+            return -1;
+        eventLoop.handlesAlloc -= EVENT_ALLOC_EXTENT;
+    }
+    return 0;
+}
+
+/*
+ * Run a single iteration of the event loop, blocking until
+ * at least one file handle has an event, or a timer expires
+ */
+int virEventRunOnce(void) {
+    struct pollfd *fds;
+    int ret, timeout, nfds;
+
+    if ((nfds = virEventMakePollFDs(&fds)) < 0)
+        return -1;
+
+    if (virEventCalculateTimeout(&timeout) < 0) {
+        VIR_FREE(fds);
+        return -1;
+    }
+
+ retry:
+    EVENT_DEBUG("Poll on %d handles %p timeout %d", nfds, fds, timeout);
+    ret = poll(fds, nfds, timeout);
+    EVENT_DEBUG("Poll got %d event", ret);
+    if (ret < 0) {
+        if (errno == EINTR) {
+            goto retry;
+        }
+        VIR_FREE(fds);
+        return -1;
+    }
+    if (virEventDispatchTimeouts() < 0) {
+        VIR_FREE(fds);
+        return -1;
+    }
+
+    if (ret > 0 &&
+        virEventDispatchHandles(fds) < 0) {
+        VIR_FREE(fds);
+        return -1;
+    }
+    VIR_FREE(fds);
+
+    if (virEventCleanupTimeouts() < 0)
+        return -1;
+
+    if (virEventCleanupHandles() < 0)
+        return -1;
+
+    return 0;
+}
+
+int
+virEventHandleTypeToPollEvent(int events)
+{
+    int ret = 0;
+    if(events & VIR_EVENT_HANDLE_READABLE)
+        ret |= POLLIN;
+    if(events & VIR_EVENT_HANDLE_WRITABLE)
+        ret |= POLLOUT;
+    if(events & VIR_EVENT_HANDLE_ERROR)
+        ret |= POLLERR;
+    if(events & VIR_EVENT_HANDLE_HANGUP)
+        ret |= POLLHUP;
+    return ret;
+}
+
+int
+virPollEventToEventHandleType(int events)
+{
+    int ret = 0;
+    if(events & POLLIN)
+        ret |= VIR_EVENT_HANDLE_READABLE;
+    if(events & POLLOUT)
+        ret |= VIR_EVENT_HANDLE_WRITABLE;
+    if(events & POLLERR)
+        ret |= VIR_EVENT_HANDLE_ERROR;
+    if(events & POLLHUP)
+        ret |= VIR_EVENT_HANDLE_HANGUP;
+    return ret;
+}
+
+// END Copied from libvirt/qemud/events.c
+
+
+JNIEXPORT void JNICALL Java_org_libvirt_EventImpl_register(JNIEnv *env, jobject obj)
+{
+    virEventRegisterImpl(virEventAddHandleImpl, virEventUpdateHandleImpl,
+                         virEventRemoveHandleImpl, virEventAddTimeoutImpl,
+                         virEventUpdateTimeoutImpl, virEventRemoveTimeoutImpl);
+}
+
+JNIEXPORT jint JNICALL Java_org_libvirt_EventImpl_run_1once(JNIEnv *env, jobject obj)
+{
+    return virEventRunOnce();
+}
diff --git a/src/org/libvirt/Connect.java b/src/org/libvirt/Connect.java
index 4271937..7ccaecd 100644
--- a/src/org/libvirt/Connect.java
+++ b/src/org/libvirt/Connect.java
@@ -3,6 +3,11 @@ package org.libvirt;
 import org.libvirt.LibvirtException;
 import org.libvirt.StoragePool;
 import org.libvirt.StorageVol;
+import org.libvirt.DomainEventListener;
+import org.libvirt.EventImpl;
+
+import java.util.HashSet;
+import java.util.Iterator;
 
 /**
  * The Connect object represents a connection to a local or remote hypervisor/driver.
@@ -12,18 +17,21 @@ import org.libvirt.StorageVol;
  */
 public class Connect {
 
+	static public EventImpl eventImpl;
+
 	// Load the native part
 	static {
 		System.loadLibrary("virt_jni");
 		_virInitialize();
+		eventImpl = new EventImpl();
 	}
 
-
 	/**
 	 * the native virConnectPtr.
 	 */
 	long VCP;
-
+	HashSet<DomainEventListener> domainEventListeners = new HashSet<DomainEventListener>();
+		
 	private static native int _virInitialize();
 
 	/**
@@ -698,4 +706,47 @@ public class Connect {
 	private native long _virStorageVolLookupByPath(long VCP, String path);
 
 
+	/**
+	 * Registers a new DomainEventListener.
+	 *
+	 * @param listener the new listener
+	 */
+	public void domainEventRegister(DomainEventListener listener) throws LibvirtException {
+		if (domainEventListeners.isEmpty()) {
+			// For now, rely on client to invoke eventImpl.run_once().
+			// Need to add synchronization for thread safety to poll
+			// in a different thread like:
+			// if (eventImpl.stopped)
+			//     new Thread(eventImpl).run();
+			registerForDomainEvents(VCP);
+		}
+		domainEventListeners.add(listener);
+	}
+
+	/**
+	 * Deregisters a previously-registered DomainEventListener.
+	 *
+	 * @param listener the listener to be deregistered
+	 */
+	public void domainEventDeregister(DomainEventListener listener) throws LibvirtException {
+		domainEventListeners.remove(listener);
+		if (domainEventListeners.isEmpty()) {
+			if (!eventImpl.stopped)
+				eventImpl.stop();
+			deregisterForDomainEvents(VCP);
+		}
+	}
+
+
+	// Invoked from JNI code by libvirt callback
+	private void fireDomainEventCallbacks(Domain dom, int event) {
+		Iterator<DomainEventListener> iter = domainEventListeners.iterator();
+		while (iter.hasNext()) {
+			DomainEventListener listener = iter.next();
+			listener.handle(dom, event);
+		}
+	}
+
+	private native void registerForDomainEvents(long VCP) throws LibvirtException;
+	private native void deregisterForDomainEvents(long VCP) throws LibvirtException;
 }
diff --git a/src/org/libvirt/DomainEventListener.java b/src/org/libvirt/DomainEventListener.java
new file mode 100644
index 0000000..95b317f
--- /dev/null
+++ b/src/org/libvirt/DomainEventListener.java
@@ -0,0 +1,16 @@
+package org.libvirt;
+
+public interface DomainEventListener
+    extends java.util.EventListener {
+
+    static int VIR_DOMAIN_EVENT_ADDED = 0;
+    static int VIR_DOMAIN_EVENT_REMOVED = 1;
+    static int VIR_DOMAIN_EVENT_STARTED = 2;
+    static int VIR_DOMAIN_EVENT_SUSPENDED = 3;
+    static int VIR_DOMAIN_EVENT_RESUMED = 4;
+    static int VIR_DOMAIN_EVENT_STOPPED = 5;
+    static int VIR_DOMAIN_EVENT_SAVED = 6;
+    static int VIR_DOMAIN_EVENT_RESTORED = 7;
+
+    public void handle(Domain dom, int event);
+}
diff --git a/src/org/libvirt/EventImpl.java b/src/org/libvirt/EventImpl.java
new file mode 100644
index 0000000..1868fe3
--- /dev/null
+++ b/src/org/libvirt/EventImpl.java
@@ -0,0 +1,31 @@
+package org.libvirt;
+
+import java.util.HashMap;
+
+// While EventImpl does implement Runnable, don't declare that until
+// the we add concurrency control for the libvirt Java bindings and
+// the EventImpl callbacks.
+
+final public class EventImpl {
+
+    boolean stopped = false;
+
+    EventImpl() {
+	register();
+    }
+
+    private native void register();
+    public native int run_once();
+
+    public void run() {
+	stopped = false;
+	while (! stopped) {
+	    if (run_once() != 0)
+		stopped = true;
+	}
+    }
+
+    public void stop() {
+	stopped = true;
+    }
+}

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]