[libcamera-devel] [PATCH 3/3] base: thread: Fix recursive calls to dispatchMessages()
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Sun Jul 11 16:27:47 CEST 2021
Hi Kieran,
On Fri, Jul 09, 2021 at 01:36:13PM +0100, Kieran Bingham wrote:
> On 05/07/2021 13:33, Jacopo Mondi wrote:
> > On Mon, Jul 05, 2021 at 03:12:45PM +0300, Laurent Pinchart wrote:
> >> 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
>
> to the .... ?
:-)
I'll drop the whole sentence, it's a leftover of a previous more complex
implementation, the current version doesn't keep track of the first
non-null entry.
> >>>>
> >>>> 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.
>
> "in all cases delivery of" sounds odd.
>
> Perhaps
>
> "It guarantees delivery of messages in the order they have been posted
> in all cases".
I'll use this wording.
> Or
>
> "In all cases, it guarantees 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().
>
> Sounds good, and Thread looks well asserted in most cases to guarantee
> rules are adhered to.
>
> >>>> + 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>
>
> With comments addressed if and as required:
>
> Reviewed-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
>
> >>>> }
> >>>>
> >>>> /**
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list