[libcamera-devel] [PATCH 3/3] base: thread: Fix recursive calls to dispatchMessages()

Jacopo Mondi jacopo at jmondi.org
Mon Jul 5 12:48:11 CEST 2021


Hi Laurent,

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 ?

> +	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 ?

Thanks
  j

>  }
>
>  /**
> --
> Regards,
>
> Laurent Pinchart
>


More information about the libcamera-devel mailing list