[libcamera-devel] [RFC 4/4] libcamera python bindings

Laurent Pinchart laurent.pinchart at ideasonboard.com
Sun Oct 4 21:46:30 CEST 2020


Hi Tomi,

On Sun, Oct 04, 2020 at 10:42:33PM +0300, Tomi Valkeinen wrote:
> On 04/10/2020 21:49, Laurent Pinchart wrote:
> > On Fri, Sep 18, 2020 at 06:20:19PM +0300, Tomi Valkeinen wrote:
> >> Main issues:
> >>
> >> - Memory management in general. Who owns the object, how to pass
> >>    ownership, etc.
> >>
> >> - Specifically, Request is currently broken. We can't, afaik, pass
> >>    ownership around. So currently Python never frees a Request, and if
> >>    the Request is not given to Camera::queueRequest, it will leak.
> >>
> >> - The forced threading causes some headache. Need to take care to use
> >>    gil_scoped_release when C++ context can invoke callbacks, and
> >>    gil_scoped_acquire at the invoke wrapper.
> > 
> > I've given this a thought, and I wonder if we could solve it by using an
> > event queue and an eventfd to move everything to the Python thread.
> > Signals would be connected to internal functions that would push a new
> > instance of a message object that binds the signal name and its
> > arguments. An eventfd would be used to wake up the python side, which
> > would call a process() function that would then dispatch the messages to
> > Python code.
> 
> Yeah, at least it would be an interesting comparison case. Curiously, 
> things seem to work very well. I guess CPython frees the lock quite 
> often without me specifically doing anything. To be honest, I don't know 
> why it works.
> 
> That said, I had had deadlock issues previously, and I recently got more 
> of them when I tried things with a python thread (doing background work 
> while running interactive python shell).
> 
> I think it may not be worth spending time with eventfd until it's clear 
> it will give some benefit, and at the moment it's not quite clear to me.
> 
> I can always move the execution from libcamera's thread to my thread in 
> one way or another. The main question is, will the GIL be freed often 
> enough to allow the callbacks to be called. Maybe it is freed often, as 
> I guess that's what python's own threads need also.

Blocking the callbacks will not only delay their delivery to the
application, but also potentially break the realtime requirements of
pipeline handlers. I think we should really not be doing so.

> But I need to understand this better until I find the answer to that.
> 
> >> - Callbacks. Difficult to attach context to the callbacks. I solved it
> >>    with BoundMethodFunction and using lambda captures
> >>
> >> - Need public Camera destructor. It is not clear to me why C++ allows it
> >>    to be private, but pybind11 doesn't.
> > 
> > Camera is meant to be managed as a shared_ptr<>, so if Python tries to
> > delete is explicitly, there's probably something bad happening
> > somewhere.
> > 
> > In file included from ../../src/py/pycamera/pymain.cpp:2:
> > In file included from /usr/include/c++/v1/thread:90:
> > In file included from /usr/include/c++/v1/functional:504:
> > /usr/include/c++/v1/memory:2363:12: error: calling a private destructor of class 'libcamera::Camera'
> >      delete __ptr;
> >             ^
> > /usr/include/c++/v1/memory:2618:7: note: in instantiation of member function 'std::__1::default_delete<libcamera::Camera>::operator()' requested here
> >        __ptr_.second()(__tmp);
> >        ^
> > /usr/include/c++/v1/memory:2572:19: note: in instantiation of member function 'std::__1::unique_ptr<libcamera::Camera, std::__1::default_delete<libcamera::Camera> >::reset' requested here
> >    ~unique_ptr() { reset(); }
> >                    ^
> > /usr/include/c++/v1/memory:3932:21: note: in instantiation of member function 'std::__1::unique_ptr<libcamera::Camera, std::__1::default_delete<libcamera::Camera> >::~unique_ptr' requested here
> >      unique_ptr<_Yp> __hold(__p);
> >                      ^
> > ../../subprojects/pybind11-2.3.0/include/pybind11/pybind11.h:1309:61: note: in instantiation of function template specialization 'std::__1::shared_ptr<libcamera::Camera>::shared_ptr<libcamera::Camera>' requested here
> >              new (std::addressof(v_h.holder<holder_type>())) holder_type(v_h.value_ptr<type>());
> >                                                              ^
> > ../../subprojects/pybind11-2.3.0/include/pybind11/pybind11.h:1346:9: note: in instantiation of function template specialization 'pybind11::class_<libcamera::Camera, std::__1::shared_ptr<libcamera::Camera> >::init_holder<libcamera::Camera>' requested here
> >          init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
> >          ^
> > ../../subprojects/pybind11-2.3.0/include/pybind11/pybind11.h:1080:32: note: in instantiation of member function 'pybind11::class_<libcamera::Camera, std::__1::shared_ptr<libcamera::Camera> >::init_instance' requested here
> >          record.init_instance = init_instance;
> >                                 ^
> > ../../src/py/pycamera/pymain.cpp:116:2: note: in instantiation of function template specialization 'pybind11::class_<libcamera::Camera, std::__1::shared_ptr<libcamera::Camera> >::class_<>' requested here
> >          py::class_<Camera, shared_ptr<Camera>>(m, "Camera")
> >          ^
> > ../../include/libcamera/camera.h:108:2: note: declared private here
> >          ~Camera();
> >          ^
> > 1 error generated.
> > 
> > The issue may come from the fact that pybin11 tries to create a
> > std::shared_ptr<> manually to hold the Camera pointer, instead of
> > copying the existing std::shared_ptr<>, but I'm not entirely sure.
> 
> Yep, no clear idea... "init_instance" there sounds like pybind11 is also 
> creating code to that allows creating Camera, which I guess then implies 
> also the need to see the destructor.
> 
> >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen at iki.fi>
> >> ---
> >>   meson.build                    |   1 +
> >>   meson_options.txt              |   2 +
> >>   py/meson.build                 |   1 +
> >>   py/pycamera/__init__.py        |  29 ++++++
> >>   py/pycamera/meson.build        |  35 +++++++
> >>   py/pycamera/pymain.cpp         | 169 +++++++++++++++++++++++++++++++
> >>   py/test/run-valgrind.sh        |   3 +
> >>   py/test/run.sh                 |   3 +
> >>   py/test/test.py                | 177 +++++++++++++++++++++++++++++++++
> >>   py/test/valgrind-pycamera.supp |  17 ++++
> >>   10 files changed, 437 insertions(+)
> >>   create mode 100644 py/meson.build
> >>   create mode 100644 py/pycamera/__init__.py
> >>   create mode 100644 py/pycamera/meson.build
> >>   create mode 100644 py/pycamera/pymain.cpp
> >>   create mode 100755 py/test/run-valgrind.sh
> >>   create mode 100755 py/test/run.sh
> >>   create mode 100755 py/test/test.py
> >>   create mode 100644 py/test/valgrind-pycamera.supp
> > 
> > [snip]
> > 
> >> diff --git a/py/test/valgrind-pycamera.supp b/py/test/valgrind-pycamera.supp
> >> new file mode 100644
> >> index 0000000..98c693f
> >> --- /dev/null
> >> +++ b/py/test/valgrind-pycamera.supp
> >> @@ -0,0 +1,17 @@
> >> +{
> >> +   <insert_a_suppression_name_here>
> >> +   Memcheck:Leak
> >> +   match-leak-kinds: definite
> >> +   fun:_Znwm
> > 
> > This is new(unsigned long). No wonder you don't have memory leaks
> > anymore :-)
> 
> That's just one 104 byte allocation at init time, with the callstack 
> below. I don't know what's it about, but look like one time alloc when 
> the pybind11 camera module is created.
> 
> >> +   fun:_ZN8pybind116moduleC1EPKcS2_
> >> +   fun:PyInit_pycamera
> >> +   fun:_PyImport_LoadDynamicModuleWithSpec
> >> +   obj:/usr/bin/python3.8
> >> +   obj:/usr/bin/python3.8
> >> +   fun:PyVectorcall_Call
> >> +   fun:_PyEval_EvalFrameDefault
> >> +   fun:_PyEval_EvalCodeWithName
> >> +   fun:_PyFunction_Vectorcall
> >> +   fun:_PyEval_EvalFrameDefault
> >> +   fun:_PyFunction_Vectorcall
> >> +}

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list