pytevent improvements & Python 3 port

Petr Viktorin pviktori at redhat.com
Tue May 26 06:07:45 MDT 2015


Hello,

The first patch is a fix for a bug in the buildsystem changes for py3.
(It only occurs when building for two Pythons, so Samba is safe.)

I've played a bit with the pytevent wrapper, and found that the types
for timers and FDs are not defined, so, for example, trying to print
them out segfaults:

    >>> import tevent
    >>> ctx = tevent.Context()
    >>> timer = ctx.add_timer(0, lambda t: None)
    >>> timer
    Segmentation fault (core dumped)

I'm sending a quick fix for FDs, and a more comprehensive one for timers.

Also, "Context.add_timer" requires passing a struct timeval cast to a
long inside a Python integer, which is very hard to do portably from
Python. I've added "add_timer_offset", which takes a number of seconds
from now.

I've also ported pytevent to Python 3. The library doesn't deal with
strings much, so the port is trivial.

-- 
Petr Viktorin
-------------- next part --------------
From 7fc58897bb6d506790f515233b9aeba03c8f17cc Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 13:06:48 +0200
Subject: [PATCH 1/6] buildtools: Always reset the build environment

In install_library, the Build object's environment was not reset
after an early return, so the extrapython env would be used in
subsequent build steps.
Wrap everything in a try-finally block to make sure the env is reset.

(Almost all of the change is indentation, `git show -w` recommended.)

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 buildtools/wafsamba/samba_install.py | 163 ++++++++++++++++++-----------------
 1 file changed, 82 insertions(+), 81 deletions(-)

diff --git a/buildtools/wafsamba/samba_install.py b/buildtools/wafsamba/samba_install.py
index af8d2ad..3d0c23a 100644
--- a/buildtools/wafsamba/samba_install.py
+++ b/buildtools/wafsamba/samba_install.py
@@ -60,95 +60,96 @@ def install_library(self):
     bld = self.bld
 
     default_env = bld.all_envs['default']
-    if self.env['IS_EXTRA_PYTHON']:
-        bld.all_envs['default'] = bld.all_envs['extrapython']
+    try:
+        if self.env['IS_EXTRA_PYTHON']:
+            bld.all_envs['default'] = bld.all_envs['extrapython']
 
-    install_ldflags = install_rpath(self)
-    build_ldflags   = build_rpath(bld)
+        install_ldflags = install_rpath(self)
+        build_ldflags   = build_rpath(bld)
 
-    if not Options.is_install or not getattr(self, 'samba_install', True):
-        # just need to set the build rpath if we are not installing
-        self.env.RPATH = build_ldflags
-        return
+        if not Options.is_install or not getattr(self, 'samba_install', True):
+            # just need to set the build rpath if we are not installing
+            self.env.RPATH = build_ldflags
+            return
 
-    # setup the install path, expanding variables
-    install_path = getattr(self, 'samba_inst_path', None)
-    if install_path is None:
-        if getattr(self, 'private_library', False):
-            install_path = '${PRIVATELIBDIR}'
+        # setup the install path, expanding variables
+        install_path = getattr(self, 'samba_inst_path', None)
+        if install_path is None:
+            if getattr(self, 'private_library', False):
+                install_path = '${PRIVATELIBDIR}'
+            else:
+                install_path = '${LIBDIR}'
+        install_path = bld.EXPAND_VARIABLES(install_path)
+
+        target_name = self.target
+
+        if install_ldflags != build_ldflags:
+            # we will be creating a new target name, and using that for the
+            # install link. That stops us from overwriting the existing build
+            # target, which has different ldflags
+            self.done_install_library = True
+            t = self.clone(self.env)
+            t.posted = False
+            t.target += '.inst'
+            self.env.RPATH = build_ldflags
         else:
-            install_path = '${LIBDIR}'
-    install_path = bld.EXPAND_VARIABLES(install_path)
-
-    target_name = self.target
-
-    if install_ldflags != build_ldflags:
-        # we will be creating a new target name, and using that for the
-        # install link. That stops us from overwriting the existing build
-        # target, which has different ldflags
-        self.done_install_library = True
-        t = self.clone(self.env)
-        t.posted = False
-        t.target += '.inst'
-        self.env.RPATH = build_ldflags
-    else:
-        t = self
-
-    t.env.RPATH = install_ldflags
-
-    dev_link     = None
-
-    # in the following the names are:
-    # - inst_name is the name with .inst. in it, in the build
-    #   directory
-    # - install_name is the name in the install directory
-    # - install_link is a symlink in the install directory, to install_name
-
-    if getattr(self, 'samba_realname', None):
-        install_name = self.samba_realname
-        install_link = None
-        if getattr(self, 'soname', ''):
+            t = self
+
+        t.env.RPATH = install_ldflags
+
+        dev_link     = None
+
+        # in the following the names are:
+        # - inst_name is the name with .inst. in it, in the build
+        #   directory
+        # - install_name is the name in the install directory
+        # - install_link is a symlink in the install directory, to install_name
+
+        if getattr(self, 'samba_realname', None):
+            install_name = self.samba_realname
+            install_link = None
+            if getattr(self, 'soname', ''):
+                install_link = self.soname
+            if getattr(self, 'samba_type', None) == 'PYTHON':
+                inst_name    = bld.make_libname(t.target, nolibprefix=True, python=True)
+            else:
+                inst_name    = bld.make_libname(t.target)
+        elif self.vnum:
+            vnum_base    = self.vnum.split('.')[0]
+            install_name = bld.make_libname(target_name, version=self.vnum)
+            install_link = bld.make_libname(target_name, version=vnum_base)
+            inst_name    = bld.make_libname(t.target)
+            if not self.private_library:
+                # only generate the dev link for non-bundled libs
+                dev_link     = bld.make_libname(target_name)
+        elif getattr(self, 'soname', ''):
+            install_name = bld.make_libname(target_name)
             install_link = self.soname
-        if getattr(self, 'samba_type', None) == 'PYTHON':
-            inst_name    = bld.make_libname(t.target, nolibprefix=True, python=True)
-        else:
             inst_name    = bld.make_libname(t.target)
-    elif self.vnum:
-        vnum_base    = self.vnum.split('.')[0]
-        install_name = bld.make_libname(target_name, version=self.vnum)
-        install_link = bld.make_libname(target_name, version=vnum_base)
-        inst_name    = bld.make_libname(t.target)
-        if not self.private_library:
-            # only generate the dev link for non-bundled libs
-            dev_link     = bld.make_libname(target_name)
-    elif getattr(self, 'soname', ''):
-        install_name = bld.make_libname(target_name)
-        install_link = self.soname
-        inst_name    = bld.make_libname(t.target)
-    else:
-        install_name = bld.make_libname(target_name)
-        install_link = None
-        inst_name    = bld.make_libname(t.target)
-
-    if t.env.SONAME_ST:
-        # ensure we get the right names in the library
-        if install_link:
-            t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_link)
         else:
-            t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_name)
-        t.env.SONAME_ST = ''
-
-    # tell waf to install the library
-    bld.install_as(os.path.join(install_path, install_name),
-                   os.path.join(self.path.abspath(bld.env), inst_name),
-                   chmod=MODE_755)
-    if install_link and install_link != install_name:
-        # and the symlink if needed
-        bld.symlink_as(os.path.join(install_path, install_link), os.path.basename(install_name))
-    if dev_link:
-        bld.symlink_as(os.path.join(install_path, dev_link), os.path.basename(install_name))
+            install_name = bld.make_libname(target_name)
+            install_link = None
+            inst_name    = bld.make_libname(t.target)
 
-    bld.all_envs['default'] = default_env
+        if t.env.SONAME_ST:
+            # ensure we get the right names in the library
+            if install_link:
+                t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_link)
+            else:
+                t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_name)
+            t.env.SONAME_ST = ''
+
+        # tell waf to install the library
+        bld.install_as(os.path.join(install_path, install_name),
+                       os.path.join(self.path.abspath(bld.env), inst_name),
+                       chmod=MODE_755)
+        if install_link and install_link != install_name:
+            # and the symlink if needed
+            bld.symlink_as(os.path.join(install_path, install_link), os.path.basename(install_name))
+        if dev_link:
+            bld.symlink_as(os.path.join(install_path, dev_link), os.path.basename(install_name))
+    finally:
+        bld.all_envs['default'] = default_env
 
 
 @feature('cshlib')
-- 
2.1.0


From 17a459af8959091ea7c705d998322ae74d50aa20 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Thu, 4 Dec 2014 12:44:56 +0100
Subject: [PATCH 2/6] pytevent: Better error and reference handling

py_backend_list:
- Handle cases of PyString_FromString or PyList_Append failing.
- Properly decrease the reference count of the returned strings.

py_register_backend:
- Decref "name" after use

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tevent/pytevent.c | 31 +++++++++++++++++++++++++------
 1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/lib/tevent/pytevent.c b/lib/tevent/pytevent.c
index af3f9d6..4de0e3d 100644
--- a/lib/tevent/pytevent.c
+++ b/lib/tevent/pytevent.c
@@ -177,14 +177,18 @@ static PyObject *py_register_backend(PyObject *self, PyObject *args)
 
 	if (!PyString_Check(name)) {
 		PyErr_SetNone(PyExc_TypeError);
+		Py_DECREF(name);
 		return NULL;
 	}
 
 	if (!tevent_register_backend(PyString_AsString(name), &py_tevent_ops)) { /* FIXME: What to do with backend */
 		PyErr_SetNone(PyExc_RuntimeError);
+		Py_DECREF(name);
 		return NULL;
 	}
 
+	Py_DECREF(name);
+
 	Py_RETURN_NONE;
 }
 
@@ -684,9 +688,10 @@ static PyObject *py_set_default_backend(PyObject *self, PyObject *args)
 
 static PyObject *py_backend_list(PyObject *self)
 {
-	PyObject *ret;
-	int i;
-	const char **backends;
+	PyObject *ret = NULL;
+	PyObject *string = NULL;
+	int i, result;
+	const char **backends = NULL;
 
 	ret = PyList_New(0);
 	if (ret == NULL) {
@@ -696,16 +701,30 @@ static PyObject *py_backend_list(PyObject *self)
 	backends = tevent_backend_list(NULL);
 	if (backends == NULL) {
 		PyErr_SetNone(PyExc_RuntimeError);
-		Py_DECREF(ret);
-		return NULL;
+		goto err;
 	}
 	for (i = 0; backends[i]; i++) {
-		PyList_Append(ret, PyString_FromString(backends[i]));
+		string = PyString_FromString(backends[i]);
+		if (!string) {
+			goto err;
+		}
+		result = PyList_Append(ret, string);
+		if (result) {
+			goto err;
+		}
+		Py_DECREF(string);
+		string = NULL;
 	}
 
 	talloc_free(backends);
 
 	return ret;
+
+err:
+	Py_XDECREF(ret);
+	Py_XDECREF(string);
+	talloc_free(backends);
+	return NULL;
 }
 
 static PyMethodDef tevent_methods[] = {
-- 
2.1.0


From 82425014c65442f3916358301f770b927dfd5e1e Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 13:29:11 +0200
Subject: [PATCH 3/6] pytevent: Define missing TeventFd_Type object

The type objects for Fd was declared but never defined,
resulting in segfaults when it was used.
Define it.

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tevent/pytevent.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/lib/tevent/pytevent.c b/lib/tevent/pytevent.c
index 4de0e3d..a495da5 100644
--- a/lib/tevent/pytevent.c
+++ b/lib/tevent/pytevent.c
@@ -415,6 +415,19 @@ static void py_fd_handler(struct tevent_context *ev,
 	Py_XDECREF(ret);
 }
 
+static void py_tevent_fp_dealloc(TeventFd_Object *self)
+{
+	talloc_free(self->fd);
+	PyObject_Del(self);
+}
+
+static PyTypeObject TeventFd_Type = {
+	.tp_name = "tevent.Fd",
+	.tp_basicsize = sizeof(TeventFd_Object),
+	.tp_dealloc = (destructor)py_tevent_fp_dealloc,
+	.tp_flags = Py_TPFLAGS_DEFAULT,
+};
+
 static PyObject *py_tevent_context_add_fd(TeventContext_Object *self, PyObject *args)
 {
 	int fd, flags;
-- 
2.1.0


From d2859d6030b6929caac710a03c0310041f1e8084 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Tue, 26 May 2015 13:25:12 +0200
Subject: [PATCH 4/6] pytalloc: Improve timer wrapper, and test it

Using Context.add_timer resulted in crashes due to missing type object
and bad reference