bt2: let components attach "user data" to ports
authorSimon Marchi <simon.marchi@efficios.com>
Fri, 31 May 2019 12:59:04 +0000 (08:59 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 5 Jun 2019 17:47:34 +0000 (13:47 -0400)
The C API allows components to attach "user data" to ports when creating
them:

    extern bt_self_component_status
    bt_self_component_source_add_output_port(
                    bt_self_component_source *self_component,
                    const char *name, void *user_data,
                    bt_self_component_port_output **self_component_port);

This is useful to identify the purpose of a given port.  This data can later be
fetched when an iterator is created for that port.

This patch makes the Python API offer a similar facility.  When adding a port,
the user can optionally pass an arbitrary Python object as user data:

    self_port = self._add_output_port('port name', user_data={'foo': 23})

which they can then access using the user_data property of
_UserComponentPort:

    print(self_port.user_data)  # {'foo': 23}

We confine the user data under the user_data property to avoid
clashes with future methods and properties that we might add to
_UserComponentPort or its subclasses.

A new "in" typemap is created for the "add port" functions to pass the
PyObject* representing the user data Python object to the creation
function.  Without the typemap, SWIG complains that the passed value (a
PyObject *) is not of the right type (it expects a void *):

    TypeError: in method 'self_component_source_add_output_port', argument 3 of type 'void *'

If the port is created successfully, it now owns a reference to this
Python object.  We must reflect that in the Python object's refcount,
this is done through an "argout" typemap.  In this typemap, we need to
check the return value of the function we called.

When fetching the user data of a port, we use an "out" typemap to
increment the refcount of the returned value.  This is because a Python
method that returns an object must return a new reference that is
transferred to the caller.

Change-Id: I0b83454a81e71bd7c2fe9449c7fc65c09f18fcf4
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/1359
Tested-by: jenkins
Reviewed-by: Philippe Proulx <eeppeliteloop@gmail.com>
bindings/python/bt2/bt2/component.py
bindings/python/bt2/bt2/native_bt_component.i
bindings/python/bt2/bt2/native_bt_port.i
bindings/python/bt2/bt2/port.py
tests/bindings/python/bt2/test_message_iterator.py
tests/bindings/python/bt2/test_port.py

index a0855a2e0bc99799caf29204f3f1debb7f8f6c80..e26acf612bc621b1630a5588a773000a7be07365 100644 (file)
@@ -702,10 +702,10 @@ class _UserSourceComponent(_UserComponent, _SourceComponent):
                                get_output_port_count,
                                bt2.port._UserComponentOutputPort)
 
-    def _add_output_port(self, name):
+    def _add_output_port(self, name, user_data=None):
         utils._check_str(name)
         fn = native_bt.self_component_source_add_output_port
-        comp_status, self_port_ptr = fn(self._ptr, name, None)
+        comp_status, self_port_ptr = fn(self._ptr, name, user_data)
         _handle_component_status(comp_status,
                                  'cannot add output port to source component object')
         assert self_port_ptr is not None
@@ -740,19 +740,19 @@ class _UserFilterComponent(_UserComponent, _FilterComponent):
                                get_input_port_count,
                                bt2.port._UserComponentInputPort)
 
-    def _add_output_port(self, name):
+    def _add_output_port(self, name, user_data=None):
         utils._check_str(name)
         fn = native_bt.self_component_filter_add_output_port
-        comp_status, self_port_ptr = fn(self._ptr, name, None)
+        comp_status, self_port_ptr = fn(self._ptr, name, user_data)
         _handle_component_status(comp_status,
                                  'cannot add output port to filter component object')
         assert self_port_ptr
         return bt2.port._UserComponentOutputPort._create_from_ptr(self_port_ptr)
 
-    def _add_input_port(self, name):
+    def _add_input_port(self, name, user_data=None):
         utils._check_str(name)
         fn = native_bt.self_component_filter_add_input_port
-        comp_status, self_port_ptr = fn(self._ptr, name, None)
+        comp_status, self_port_ptr = fn(self._ptr, name, user_data)
         _handle_component_status(comp_status,
                                  'cannot add input port to filter component object')
         assert self_port_ptr
@@ -775,10 +775,10 @@ class _UserSinkComponent(_UserComponent, _SinkComponent):
                                get_input_port_count,
                                bt2.port._UserComponentInputPort)
 
-    def _add_input_port(self, name):
+    def _add_input_port(self, name, user_data=None):
         utils._check_str(name)
         fn = native_bt.self_component_sink_add_input_port
-        comp_status, self_port_ptr = fn(self._ptr, name, None)
+        comp_status, self_port_ptr = fn(self._ptr, name, user_data)
         _handle_component_status(comp_status,
                                  'cannot add input port to sink component object')
         assert self_port_ptr
index 6612ad9c2d5e6c1de63c4aa3237d76f533921acc..f484263ecccad70bd6ff0411fa8a60bc3b237904 100644 (file)
        }
 }
 
+/* Typemaps used for user data attached to self component ports. */
+
+/*
+ * The user data Python object is kept as the user data of the port, we pass
+ * the PyObject pointer directly to the port creation function.
+ */
+%typemap(in) void *PY_SELF_PORT_USER_DATA {
+       $1 = $input;
+}
+
+/*
+ * The port, if created successfully, now owns a reference to the Python object,
+ * we reflect that here.
+ */
+%typemap(argout) void *PY_SELF_PORT_USER_DATA {
+       if (PyLong_AsLong($result) == BT_SELF_COMPONENT_STATUS_OK) {
+               Py_INCREF($1);
+       }
+}
+
 /* From component-const.h */
 
 extern const char *bt_component_get_name(const bt_component *component);
@@ -216,7 +236,7 @@ bt_self_component_source_borrow_output_port_by_index(
 extern bt_self_component_status
 bt_self_component_source_add_output_port(
                bt_self_component_source *self_component,
-               const char *name, void *user_data,
+               const char *name, void *PY_SELF_PORT_USER_DATA,
                bt_self_component_port_output **OUT);
 
 /* From self-component-filter.h */
@@ -241,7 +261,7 @@ bt_self_component_filter_borrow_output_port_by_index(
 extern bt_self_component_status
 bt_self_component_filter_add_output_port(
                bt_self_component_filter *self_component,
-               const char *name, void *data,
+               const char *name, void *PY_SELF_PORT_USER_DATA,
                bt_self_component_port_output **OUT);
 
 extern bt_self_component_port_input *
@@ -257,7 +277,7 @@ bt_self_component_filter_borrow_input_port_by_index(
 extern bt_self_component_status
 bt_self_component_filter_add_input_port(
                bt_self_component_filter *self_component,
-               const char *name, void *data,
+               const char *name, void *PY_SELF_PORT_USER_DATA,
                bt_self_component_port_input **OUT);
 
 /* From self-component-sink.h */
@@ -281,5 +301,5 @@ bt_self_component_sink_borrow_input_port_by_index(
 extern bt_self_component_status
 bt_self_component_sink_add_input_port(
                bt_self_component_sink *self_component,
-               const char *name, void *user_data,
+               const char *name, void *PY_SELF_PORT_USER_DATA,
                bt_self_component_port_input **OUT);
index 728543783bfadb8d8692c493c444f168d437e7c2..873c613627c35eda227e27a218d3a3d8de92763a 100644 (file)
  * THE SOFTWARE.
  */
 
+/*
+ * Typemap for the user data attached to (and owned by) a self component port.
+ * The pointer saved as the port's user data is directly the PyObject *.
+ *
+ * As per the CPython calling convention, we need to return a new reference to
+ * the returned object, which will be transferred to the caller.  The following
+ * typedef allows us to apply the typemap.
+ */
+%{
+typedef void *PY_SELF_PORT_USER_DATA;
+%}
+
+%typemap(out) PY_SELF_PORT_USER_DATA {
+       Py_INCREF($1);
+       $result = $1;
+}
+
 /* From port-const.h */
 
 typedef enum bt_port_type {
@@ -77,7 +94,7 @@ const bt_port *bt_self_component_port_as_port(
 extern bt_self_component *bt_self_component_port_borrow_component(
                bt_self_component_port *self_port);
 
-extern void *bt_self_component_port_get_data(
+extern PY_SELF_PORT_USER_DATA bt_self_component_port_get_data(
                const bt_self_component_port *self_port);
 
 /* From self-component-port-output.h */
index a84edf56d7a117c9ca0d835ce341c733c08d4de7..f7c236692e23d35951c00b85375ef58a887d9831 100644 (file)
@@ -103,6 +103,11 @@ class _UserComponentPort(_Port):
 
         return bt2.connection._Connection._create_from_ptr_and_get_ref(conn_ptr)
 
+    @property
+    def user_data(self):
+        ptr = self._as_self_port_ptr(self._ptr)
+        return native_bt.self_component_port_get_data(ptr)
+
 
 class _UserComponentInputPort(_UserComponentPort, _InputPort):
     _as_self_port_ptr = staticmethod(native_bt.self_component_port_input_as_self_component_port)
index 0ad56329da087136891185765fa6f18d5b83f8df..9c8277b7f6b1ef2e3e0054d9f51c0fffdd7aae0b 100644 (file)
@@ -40,13 +40,14 @@ class UserMessageIteratorTestCase(unittest.TestCase):
                        message_iterator_class=MyIter):
             def __init__(self, params):
                 nonlocal the_output_port_from_source
-                the_output_port_from_source = self._add_output_port('out')
+                the_output_port_from_source = self._add_output_port('out', 'user data')
 
         initialized = False
         graph = self._create_graph(MySource)
         graph.run()
         self.assertTrue(initialized)
         self.assertEqual(the_output_port_from_source.addr, the_output_port_from_iter.addr)
+        self.assertEqual(the_output_port_from_iter.user_data, 'user data')
 
     def test_finalize(self):
         class MyIter(bt2._UserMessageIterator):
index 2d203784624418925db5d55ff6d2f79ee0409304..4566c3a60d0a52e4dbd1b2110f5c25a21c00480c 100644 (file)
@@ -774,3 +774,69 @@ class PortTestCase(unittest.TestCase):
                 pass
 
         self._create_comp(MySink)
+
+    def test_source_self_port_user_data(self):
+        class MyIter(bt2._UserMessageIterator):
+            def __next__(self):
+                raise bt2.Stop
+
+        class MySource(bt2._UserFilterComponent,
+                       message_iterator_class=MyIter):
+            def __init__(comp_self, params):
+                nonlocal user_datas
+
+                p = comp_self._add_output_port('port1')
+                user_datas.append(p.user_data)
+                p = comp_self._add_output_port('port2', 2)
+                user_datas.append(p.user_data)
+
+        user_datas = []
+
+        comp = self._create_comp(MySource)
+        self.assertEqual(user_datas, [None, 2])
+
+    def test_filter_self_port_user_data(self):
+        class MyIter(bt2._UserMessageIterator):
+            def __next__(self):
+                raise bt2.Stop
+
+        class MyFilter(bt2._UserFilterComponent,
+                       message_iterator_class=MyIter):
+            def __init__(comp_self, params):
+                nonlocal user_datas
+
+                p = comp_self._add_output_port('port1')
+                user_datas.append(p.user_data)
+                p = comp_self._add_output_port('port2', 'user data string')
+                user_datas.append(p.user_data)
+
+                p = comp_self._add_input_port('port3')
+                user_datas.append(p.user_data)
+                p = comp_self._add_input_port('port4', user_data={'user data': 'dict'})
+                user_datas.append(p.user_data)
+
+        user_datas = []
+
+        comp = self._create_comp(MyFilter)
+        self.assertEqual(user_datas,
+                         [None, 'user data string', None, {'user data': 'dict'}])
+
+    def test_sink_self_port_user_data(self):
+        class MyIter(bt2._UserMessageIterator):
+            def __next__(self):
+                raise bt2.Stop
+
+        class MySink(bt2._UserFilterComponent,
+                     message_iterator_class=MyIter):
+            def __init__(comp_self, params):
+                nonlocal user_datas
+
+                p = comp_self._add_input_port('port1')
+                user_datas.append(p.user_data)
+                p = comp_self._add_input_port('port2', set())
+                user_datas.append(p.user_data)
+
+        user_datas = []
+
+        comp = self._create_comp(MySink)
+        self.assertEqual(user_datas, [None, set()])
This page took 0.029253 seconds and 4 git commands to generate.