Skip to contentSkip to conversation

python: Add non-blocking Xenstore watch bindings

PATCH Superseded Euan Harris 3 participants 8 messages Sep 15, 2017 – Sep 21, 2017 Arm/actions semi architecture

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

cover python: Add non-blocking Xenstore watch bindings

Expose xs_fileno() and xs_check_watch() to Python. These functions
make it posible to write event-driven Xenstore clients in Python:

#!/usr/bin/env python

import xen.lowlevel.xs

import sys
import errno
from select import select
import time

# Connect to XenStore and set watch
xsh = xen.lowlevel.xs.xs()
xsh.watch("/foo", "footoken")
xsh.watch("/bar", "bartoken")

# Start polling loop
xsfd = xsh.fileno()
while True:
readable, writable, exceptional = select([xsfd], [], [xsfd], 1.0) print "%d tick" % time.time()

if readable:
while True:
watch = xsh.check_watch()
if not watch:
break
path, token = watch
print "%d watch fired: path=%s, token=%s" % (time.time(),
path, token)
value = xsh.read("", path)
print "%d read %s = %s" % (time.time(), path, value)

if exceptional:
print "select error"

The polling loop can be simplified further by wrapping the call to xsh.check_watch() in a generator, but this is easier to do in Python than in the C bindings.

Euan Harris (3):
python: Add wrapper for xs_fileno()
python: Extract registered watch search logic from xspy_read_watch()
python: Add binding for non-blocking xs_check_watch()

 tools/python/xen/lowlevel/xs/xs.c | 109 ++++++++++++++++++++++++++++++--------
 1 file changed, 86 insertions(+), 23 deletions(-)
Signature · 7 lines
1.8.3.1


_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel
#1 lore raw

PATCH 1/3 python: Add binding for xs_fileno()

xs_fileno() returns a file descriptor which receives events when Xenstore
watches fire. Exposing this in the Python bindings is a prerequisite
for writing event-driven clients in Python.

---
 tools/python/xen/lowlevel/xs/xs.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
tools/python/xen/lowlevel/xs/xs.c +20 -0
index aba5a20..9f1b916 100644
@@ -453,6 +453,25 @@ static PyObject *xspy_watch(XsHandle *self, PyObject *args)
 }
 
 
+#define xspy_fileno_doc "\n"                              \
+	"Return the FD to poll for notifications when watches fire.\n"   \
+	"Returns: [int] file descriptor.\n"                \
+	"\n"
+
+static PyObject *xspy_fileno(XsHandle *self)
+{
+    struct xs_handle *xh = xshandle(self);
+    int fd;
+
+    if (!xh)
+        return NULL;
+
+    fd = xs_fileno(xh);
+
+    return PyInt_FromLong(fd);
+}
+
+
 #define xspy_read_watch_doc "\n"				\
 	"Read a watch notification.\n"				\
 	"\n"							\
@@ -887,6 +906,7 @@ static PyMethodDef xshandle_methods[] = {
     XSPY_METH(release_domain,    METH_VARARGS),
     XSPY_METH(close,             METH_NOARGS),
     XSPY_METH(get_domain_path,   METH_VARARGS),
+    XSPY_METH(fileno,            METH_NOARGS),
     { NULL /* Sentinel. */ },
 };
 
Signature · 7 lines
1.8.3.1


_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel
Reviewed-by on v1 1/3
#2 lore raw

Re: [PATCH 1/3] python: Add binding for xs_fileno()

Quoted 5 lines · Euan Harris

xs_fileno() returns a file descriptor which receives events when Xenstore
watches fire. Exposing this in the Python bindings is a prerequisite
for writing event-driven clients in Python.

Quoted 50 lines
---
 tools/python/xen/lowlevel/xs/xs.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
tools/python/xen/lowlevel/xs/xs.c +20 -0
index aba5a20..9f1b916 100644
@@ -453,6 +453,25 @@ static PyObject *xspy_watch(XsHandle *self, PyObject *args)
 }
 
 
+#define xspy_fileno_doc "\n"                              \
+	"Return the FD to poll for notifications when watches fire.\n"   \
+	"Returns: [int] file descriptor.\n"                \
+	"\n"
+
+static PyObject *xspy_fileno(XsHandle *self)
+{
+    struct xs_handle *xh = xshandle(self);
+    int fd;
+
+    if (!xh)
+        return NULL;
+
+    fd = xs_fileno(xh);
+
+    return PyInt_FromLong(fd);
+}
+
+
 #define xspy_read_watch_doc "\n"				\
 	"Read a watch notification.\n"				\
 	"\n"							\
@@ -887,6 +906,7 @@ static PyMethodDef xshandle_methods[] = {
     XSPY_METH(release_domain,    METH_VARARGS),
     XSPY_METH(close,             METH_NOARGS),
     XSPY_METH(get_domain_path,   METH_VARARGS),
+    XSPY_METH(fileno,            METH_NOARGS),
     { NULL /* Sentinel. */ },
 };
 
Signature · 7 lines
1.8.3.1


_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel

_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel

Reviewed-by on v1 1/3
#3 lore raw

Re: [PATCH 1/3] python: Add binding for xs_fileno()

In reply to Euan Harris
Quoted 5 lines · Euan Harris

xs_fileno() returns a file descriptor which receives events when Xenstore
watches fire. Exposing this in the Python bindings is a prerequisite
for writing event-driven clients in Python.

_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel

#4 lore raw

PATCH 2/3 python: Extract registered watch search logic from xspy_read_watch()

In reply to Euan Harris

When a watch fires, xspy_read_watch() checks whether the client has registered interest in the path which changed and, if so, returns the
path and a client-supplied token. The binding for xs_check_watch()
needs to do the same, so this patch extracts the search code into a separate function.

---
 tools/python/xen/lowlevel/xs/xs.c | 61 ++++++++++++++++++++++++---------------
 1 file changed, 38 insertions(+), 23 deletions(-)
tools/python/xen/lowlevel/xs/xs.c +38 -23
index 9f1b916..a4b50a0 100644
@@ -77,6 +77,8 @@ static inline struct xs_handle *xshandle(XsHandle *self)
 
 static void remove_watch(XsHandle *xsh, PyObject *token);
 
+static PyObject *match_watch_by_token(XsHandle *self, char **xsval);
+
 static PyObject *none(bool result);
 
 static int parse_transaction_path(XsHandle *self, PyObject *args,
@@ -484,8 +486,6 @@ static PyObject *xspy_read_watch(XsHandle *self, PyObject *args)
     struct xs_handle *xh = xshandle(self);
     PyObject *val = NULL;
     char **xsval;
-    PyObject *token;
-    int i;
     unsigned int num;
 
     if (!xh)
@@ -497,32 +497,20 @@ again:
     Py_END_ALLOW_THREADS
     if (!xsval) {
         PyErr_SetFromErrno(xs_error);
-        goto exit;
-    }
-    if (sscanf(xsval[XS_WATCH_TOKEN], "%li", (unsigned long *)&token) != 1) {
-	xs_set_error(EINVAL);
-        goto exit;
-    }
-    for (i = 0; i < PyList_Size(self->watches); i++) {
-        if (token == PyList_GetItem(self->watches, i))
-            break;
-    }
-    if (i == PyList_Size(self->watches)) {
-      /* We do not have a registered watch for the one that has just fired.
-         Ignore this -- a watch that has been recently deregistered can still
-         have watches in transit.  This is a blocking method, so go back to
-         read again.
-      */
-      free(xsval);
-      goto again;
+        return val;
     }
-    /* Create tuple (path, token). */
-    val = Py_BuildValue("(sO)", xsval[XS_WATCH_PATH], token);
- exit:
+
+    val = match_watch_by_token(self, xsval);
     free(xsval);
+
+    if (!val && errno == EAGAIN) {
+        goto again;
+    }
+
     return val;
 }
 
+
 #define xspy_unwatch_doc "\n"				\
 	"Stop watching a path.\n"			\
 	" path  [string] : xenstore path.\n"		\
@@ -868,6 +856,33 @@ static int parse_transaction_path(XsHandle *self, PyObject *args,
 }
 
 
+static PyObject *match_watch_by_token(XsHandle *self, char **xsval)
+{
+    PyObject *token;
+    int i;
+
+    if (sscanf(xsval[XS_WATCH_TOKEN], "%li", (unsigned long *)&token) != 1) {
+	xs_set_error(EINVAL);
+        return NULL;
+    }
+    for (i = 0; i < PyList_Size(self->watches); i++) {
+        if (token == PyList_GetItem(self->watches, i))
+            break;
+    }
+    if (i == PyList_Size(self->watches)) {
+        /* We do not have a registered watch for the one that has just fired.
+           Ignore this -- a watch that has been recently deregistered can still
+           have watches in transit.
+        */
+        xs_set_error(EAGAIN);
+        return NULL;
+    }
+
+    /* Create tuple (path, token). */
+    return Py_BuildValue("(sO)", xsval[XS_WATCH_PATH], token);
+}
+
+
 static PyObject *none(bool result)
 {
     if (result) {
Signature · 7 lines
1.8.3.1


_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel
Reviewed-by on v1 2/3
#5 lore raw

Re: [PATCH 2/3] python: Extract registered watch search logic from xspy_read_watch()

Quoted 54 lines · Euan Harris
 tools/python/xen/lowlevel/xs/xs.c | 61 ++++++++++++++++++++++++---------------
 1 file changed, 38 insertions(+), 23 deletions(-)
tools/python/xen/lowlevel/xs/xs.c +5 -23
index 9f1b916..a4b50a0 100644
@@ -77,6 +77,8 @@ static inline struct xs_handle *xshandle(XsHandle *self)
 
 static void remove_watch(XsHandle *xsh, PyObject *token);
 
+static PyObject *match_watch_by_token(XsHandle *self, char **xsval);
+
 static PyObject *none(bool result);
 
 static int parse_transaction_path(XsHandle *self, PyObject *args,
@@ -484,8 +486,6 @@ static PyObject *xspy_read_watch(XsHandle *self, PyObject *args)
     struct xs_handle *xh = xshandle(self);
     PyObject *val = NULL;
     char **xsval;
-    PyObject *token;
-    int i;
     unsigned int num;
 
     if (!xh)
@@ -497,32 +497,20 @@ again:
     Py_END_ALLOW_THREADS
     if (!xsval) {
         PyErr_SetFromErrno(xs_error);
-        goto exit;
-    }
-    if (sscanf(xsval[XS_WATCH_TOKEN], "%li", (unsigned long *)&token) != 1) {
-	xs_set_error(EINVAL);
-        goto exit;
-    }
-    for (i = 0; i < PyList_Size(self->watches); i++) {
-        if (token == PyList_GetItem(self->watches, i))
-            break;
-    }
-    if (i == PyList_Size(self->watches)) {
-      /* We do not have a registered watch for the one that has just fired.
-         Ignore this -- a watch that has been recently deregistered can still
-         have watches in transit.  This is a blocking method, so go back to
-         read again.
-      */
-      free(xsval);
-      goto again;
+        return val;
     }
-    /* Create tuple (path, token). */
-    val = Py_BuildValue("(sO)", xsval[XS_WATCH_PATH], token);
- exit:
+
+    val = match_watch_by_token(self, xsval);

tools/python/xen/lowlevel/xs/xs.c again: · line 513

Quoted 10 lines
tools/python/xen/lowlevel/xs/xs.c +6 -0
     free(xsval);
+
+    if (!val && errno == EAGAIN) {
+        goto again;
+    }
+
     return val;
 }
 
+

Stray blank line.

Quoted 4 lines
tools/python/xen/lowlevel/xs/xs.c +0 -0
 #define xspy_unwatch_doc "\n"				\
 	"Stop watching a path.\n"			\
 	" path  [string] : xenstore path.\n"		\

tools/python/xen/lowlevel/xs/xs.c static int parse_transaction_path(XsHandle *self, PyObject *args, · line 865

Quoted 10 lines
tools/python/xen/lowlevel/xs/xs.c +7 -0
@@ -868,6 +856,33 @@ static int parse_transaction_path(XsHandle *self, PyObject *args,
 }
 
 
+static PyObject *match_watch_by_token(XsHandle *self, char **xsval)
+{
+    PyObject *token;
+    int i;
+
+    if (sscanf(xsval[XS_WATCH_TOKEN], "%li", (unsigned long *)&token) != 1) {
+	xs_set_error(EINVAL);

Please fix indentation here.

With the comments addressed:

_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel

#6 lore raw

PATCH 3/3 python: Add binding for non-blocking xs_check_watch()

In reply to Euan Harris

xs_check_watch() checks for watch notifications without blocking. Together with the binding for xs_fileno(), this makes it possible to write event-driven clients in Python.

---
 tools/python/xen/lowlevel/xs/xs.c | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)
tools/python/xen/lowlevel/xs/xs.c +28 -0
index a4b50a0..c9a9259 100644
@@ -474,6 +474,33 @@ static PyObject *xspy_fileno(XsHandle *self)
 }
 
 
+#define xspy_check_watch_doc "\n"				\
+	"Check for watch notifications without blocking.\n"	\
+	"\n"							\
+	"Returns: [tuple] (path, token).\n"			\
+	"         None if no watches have fired.\n"             \
+	"Raises xen.lowlevel.xs.Error on error.\n"	        \
+	"\n"
+
+static PyObject *xspy_check_watch(XsHandle *self, PyObject *args)
+{
+    struct xs_handle *xh = xshandle(self);
+    PyObject *val = NULL;
+    char **xsval;
+
+    if (!xh)
+        return NULL;
+
+    xsval = xs_check_watch(xh);
+    if (!xsval) {
+        return none(errno == EAGAIN);
+    }
+
+    val = match_watch_by_token(self, xsval);
+    free(xsval);
+    return val;
+}
+
 #define xspy_read_watch_doc "\n"				\
 	"Read a watch notification.\n"				\
 	"\n"							\
@@ -912,6 +939,7 @@ static PyMethodDef xshandle_methods[] = {
     XSPY_METH(set_permissions,   METH_VARARGS),
     XSPY_METH(watch,             METH_VARARGS),
     XSPY_METH(read_watch,        METH_NOARGS),
+    XSPY_METH(check_watch,       METH_NOARGS),
     XSPY_METH(unwatch,           METH_VARARGS),
     XSPY_METH(transaction_start, METH_NOARGS),
     XSPY_METH(transaction_end,   METH_VARARGS | METH_KEYWORDS),
Signature · 7 lines
1.8.3.1


_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel
Reviewed-by on v1 3/3
#7 lore raw

Re: [PATCH 3/3] python: Add binding for non-blocking xs_check_watch()

Quoted 5 lines · Euan Harris

xs_check_watch() checks for watch notifications without blocking. Together with the binding for xs_fileno(), this makes it possible to write event-driven clients in Python.

_______________________________________________
Xen-devel mailing list
[email protected]
https://lists.xen.org/xen-devel