[libcamera-devel] [PATCH 3/3] base: thread: Fix recursive calls to dispatchMessages()
Jacopo Mondi
jacopo at jmondi.org
Mon Jul 5 14:33:03 CEST 2021
Hi Laurent,
On Mon, Jul 05, 2021 at 03:12:45PM +0300, Laurent Pinchart wrote:
> Hi Jacopo,
>
> On Mon, Jul 05, 2021 at 12:48:11PM +0200, Jacopo Mondi wrote:
> > On Fri, Jul 02, 2021 at 02:07:41AM +0300, Laurent Pinchart wrote:
> > > There are use cases for calling the dispatchMessages() function
> > > recursively, from within a message handler. This can be used, for
> > > instance, to force delivery of messages posted to a thread concurrently
> > > to stopping the thread. This currently causes access, in the outer
> > > dispatchMessages() call, to iterators that have been invalidated by
> > > erasing list elements in the recursive call, leading to undefined
> > > behaviour (most likely double-free or other crashes).
> > >
> > > Fix it by only erasing messages from the list at the end of the outer
> > > call, identified using a recursion counter. We need to keep track of the
> > > first non-null entry in the posted messages list to restrict the
> > > deletion to the
> > >
> > > Bug: https://bugs.libcamera.org/show_bug.cgi?id=26
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> > > ---
> > > src/libcamera/base/thread.cpp | 43 +++++++++++++++++++++++++++--------
> > > 1 file changed, 33 insertions(+), 10 deletions(-)
> > >
> > > diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp
> > > index 7f79115222e8..9eb488d46db8 100644
> > > --- a/src/libcamera/base/thread.cpp
> > > +++ b/src/libcamera/base/thread.cpp
> > > @@ -126,6 +126,11 @@ public:
> > > * \brief Protects the \ref list_
> > > */
> > > Mutex mutex_;
> > > + /**
> > > + * \brief The recursion level for recursive Thread::dispatchMessages()
> > > + * calls
> > > + */
> > > + unsigned int recursion_ = 0;
> > > };
> > >
> > > /**
> > > @@ -595,30 +600,34 @@ void Thread::removeMessages(Object *receiver)
> > > * Messages shall only be dispatched from the current thread, typically within
> > > * the thread from the run() function. Calling this function outside of the
> > > * thread results in undefined behaviour.
> > > + *
> > > + * This function is not thread-safe, but it may be called recursively in the
> > > + * same thread from an object's message handler. It guarantees in all cases
> > > + * delivery of messages in the order they have been posted.
> > > */
> > > void Thread::dispatchMessages(Message::Type type)
> > > {
> > > ASSERT(data_ == ThreadData::current());
> > >
> > > + ++data_->messages_.recursion_;
> > > +
> > > MutexLocker locker(data_->messages_.mutex_);
> > >
> > > std::list<std::unique_ptr<Message>> &messages = data_->messages_.list_;
> > >
> > > - for (auto iter = messages.begin(); iter != messages.end(); ) {
> > > - std::unique_ptr<Message> &msg = *iter;
> > > -
> > > - if (!msg) {
> > > - iter = data_->messages_.list_.erase(iter);
> > > + for (std::unique_ptr<Message> &msg : messages) {
> > > + if (!msg)
> > > continue;
> > > - }
> > >
> > > - if (type != Message::Type::None && msg->type() != type) {
> > > - ++iter;
> > > + if (type != Message::Type::None && msg->type() != type)
> > > continue;
> > > - }
> > >
> > > + /*
> > > + * Move the message, setting the entry in the list to null. It
> > > + * will cause recursive calls to ignore the entry, and the erase
> > > + * loop at the end of the function to delete it from the list.
> > > + */
> > > std::unique_ptr<Message> message = std::move(msg);
> > > - iter = data_->messages_.list_.erase(iter);
> > >
> > > Object *receiver = message->receiver_;
> > > ASSERT(data_ == receiver->thread()->data_);
> > > @@ -629,6 +638,20 @@ void Thread::dispatchMessages(Message::Type type)
> > > message.reset();
> > > locker.lock();
> > > }
> > > +
> > > + /*
> > > + * If the recursion level is 0, erase all null messages in the list. We
> > > + * can't do so during recursion, as it would invalidate the iterator of
> > > + * the outer calls.
> > > + */
> >
> > is it paranoid to think accesses to recursions_ should be serialized ?
>
> The function is documented as not being thread-safe. This is because it
> must be called from the thread created by the Thread class (see the
> assert at the beginning that enforces that). While it can be called
> recursively, there will never be two concurrent calls, so no race
> condition when accessing the recursion_ variable. This is unlike the
> messages list that needs to be protected by a lock because other threads
> can post messages to the list concurrently to
> Thread::dispatchMessages().
>
> > > + if (!--data_->messages_.recursion_) {
> > > + for (auto iter = messages.begin(); iter != messages.end(); ) {
> > > + if (!*iter)
> > > + iter = messages.erase(iter);
> > > + else
> > > + ++iter;
> > > + }
> > > + }
> >
> > Can null messages end up in the list ? can't you just clear() the
> > whole list ?
>
> The function takes a message type parameter to restrict it to
> dispatching messages of a given type. The list may thus not be empty, it
> may still contain messages of other types.
So it is the other way around actually, if the message has been
dispatched, we erase it. Sorry, I read the condition wrong, thanks for
explaining.
A little note, you could:
if (--data_->messages_.recursion_)
return;
And save one indentation level.
Apart from this:
Reviewed-by: Jacopo Mondi <jacopo at jmondi.org>
Thanks
j
>
> > > }
> > >
> > > /**
>
> --
> Regards,
>
> Laurent Pinchart
More information about the libcamera-devel
mailing list