[PATCH 12/12] libcamera: object: Add and use thread-bound assertion
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Tue Jan 23 00:47:17 CET 2024
On Mon, Jan 22, 2024 at 09:55:35PM +0100, Milan Zamazal wrote:
> Laurent Pinchart <laurent.pinchart at ideasonboard.com> writes:
>
> > Several functions in libcamera classes are marked as thread-bound,
> > restricting the contexts in which those functions can be called. There
> > is no infrastructure to enfore these restrictions, causing difficult to
> ^^^^^^
> enforce
>
> > debug race conditions when they are not met by callers.
> >
> > As a first step to solve this, add an assertThreadBound() protected
> > function to the Object class to test if the calling thread context is
> > valid, and use it in member functions of Object subclasses marked as
> > thread-bound. This replaces manual tests in a few locations.
> >
> > The thread-bound member functions of classes that do not inherit from
> > Object are not checked, and neither are the functions of classes marked
> > as thread-bound at the class level. These issue should be addressed in
> > the future.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> > ---
> > include/libcamera/base/object.h | 2 ++
> > src/libcamera/base/event_notifier.cpp | 6 +++++
> > src/libcamera/base/object.cpp | 32 ++++++++++++++++++++++++++-
> > src/libcamera/base/timer.cpp | 10 +++------
> > 4 files changed, 42 insertions(+), 8 deletions(-)
> >
> > diff --git a/include/libcamera/base/object.h b/include/libcamera/base/object.h
> > index 933336361155..cb7e0a132be2 100644
> > --- a/include/libcamera/base/object.h
> > +++ b/include/libcamera/base/object.h
> > @@ -49,6 +49,8 @@ public:
> > protected:
> > virtual void message(Message *msg);
> >
> > + bool assertThreadBound(const char *message);
> > +
> > private:
> > friend class SignalBase;
> > friend class Thread;
> > diff --git a/src/libcamera/base/event_notifier.cpp b/src/libcamera/base/event_notifier.cpp
> > index fd93c0878c6f..a519aec38efb 100644
> > --- a/src/libcamera/base/event_notifier.cpp
> > +++ b/src/libcamera/base/event_notifier.cpp
> > @@ -8,6 +8,7 @@
> > #include <libcamera/base/event_notifier.h>
> >
> > #include <libcamera/base/event_dispatcher.h>
> > +#include <libcamera/base/log.h>
> > #include <libcamera/base/message.h>
> > #include <libcamera/base/thread.h>
> >
> > @@ -20,6 +21,8 @@
> >
> > namespace libcamera {
> >
> > +LOG_DECLARE_CATEGORY(Event)
> > +
> > /**
> > * \class EventNotifier
> > * \brief Notify of activity on a file descriptor
> > @@ -104,6 +107,9 @@ EventNotifier::~EventNotifier()
> > */
> > void EventNotifier::setEnabled(bool enable)
> > {
> > + if (!assertThreadBound("EventNotifier can't be enabled from another thread"))
> > + return;
> > +
>
> Is it OK to continue with libcamera execution when the requested action is not
> fulfilled here? Can a wrong state of EventNotifier lead to serious trouble and
> damages worse than exiting the program?
>
> The same question for the other checks below.
In debug builds we certainly want to fail very loudly. In release
builds, however, I don't think exiting is a reasonable option. I expect
libcamera to be run as a service (underneath pipewire) in quite a lot of
use cases, and taking pipewire down would be Bad (TM). I think we should
instead propagate errors up the call chain all the way to the
application, but not in this patch series.
> > if (enabled_ == enable) return;
> >
> > diff --git a/src/libcamera/base/object.cpp b/src/libcamera/base/object.cpp
> > index 8af0337f5448..d98a9157773e 100644
> > --- a/src/libcamera/base/object.cpp
> > +++ b/src/libcamera/base/object.cpp
> > @@ -224,6 +224,35 @@ void Object::message(Message *msg)
> > }
> > }
> >
> > +/**
> > + * \fn Object::assertThreadBound()
> > + * \brief Check if the caller complies with thread-bound constraints
> > + * \param[in] message The message to be printed on error
> > + *
> > + * This function verifies the calling constraints required by the \threadbound
> > + * definition. It shall be called at the beginning of member functions of an
> > + * Object subclass that are explicitly marked as thread-bound in their
> > + * documentation.
> > + *
> > + * If the thread-bound constraints are not met, the function prints \a message
> > + * as an error message. For debug builds, it additionally causes an assertion
> > + * error.
> > + *
> > + * \todo Verify the thread-bound requirements for functions marked as
> > + * thread-bound at the class level.
> > + *
> > + * \return True if the call is thread-bound compliant, false otherwise
> > + */
> > +bool Object::assertThreadBound(const char *message)
> > +{
> > + if (Thread::current() == thread_)
> > + return true;
> > +
> > + LOG(Object, Error) << message;
> > + ASSERT(false);
> > + return false;
>
> Or maybe simply exiting here rather than leaving the decision on the callers?
>
> (There can be different answers for "in this patch for now" and "in future once
> more tested".)
>
> > +}
> > +
> > /**
> > * \fn R Object::invokeMethod()
> > * \brief Invoke a method asynchronously on an Object instance
> > @@ -275,7 +304,8 @@ void Object::message(Message *msg)
> > */
> > void Object::moveToThread(Thread *thread)
> > {
> > - ASSERT(Thread::current() == thread_);
> > + if (!assertThreadBound("Object can't be moved from another thread"))
> > + return;
> >
> > if (thread_ == thread)
> > return;
> > diff --git a/src/libcamera/base/timer.cpp b/src/libcamera/base/timer.cpp
> > index 74b060af32ff..24dbf1e892c3 100644
> > --- a/src/libcamera/base/timer.cpp
> > +++ b/src/libcamera/base/timer.cpp
> > @@ -85,10 +85,8 @@ void Timer::start(std::chrono::milliseconds duration)
> > */
> > void Timer::start(std::chrono::steady_clock::time_point deadline)
> > {
> > - if (Thread::current() != thread()) {
> > - LOG(Timer, Error) << "Timer " << this << " << can't be started from another thread";
> > + if (!assertThreadBound("Timer can't be started from another thread"))
> > return;
> > - }
> >
> > deadline_ = deadline;
> >
> > @@ -114,13 +112,11 @@ void Timer::start(std::chrono::steady_clock::time_point deadline)
> > */
> > void Timer::stop()
> > {
> > - if (!isRunning())
> > + if (!assertThreadBound("Timer can't be stopped from another thread"))
> > return;
> >
> > - if (Thread::current() != thread()) {
> > - LOG(Timer, Error) << "Timer " << this << " can't be stopped from another thread";
> > + if (!isRunning())
> > return;
> > - }
> >
> > unregisterTimer();
> > }
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list