[libcamera-devel,v2] libcamera: gst: Fix double-free when acquire_buffer fails
diff mbox series

Message ID 20210311093325.8933-1-m.cichy@pengutronix.de
State Accepted
Headers show
Series
  • [libcamera-devel,v2] libcamera: gst: Fix double-free when acquire_buffer fails
Related show

Commit Message

Marian Cichy March 11, 2021, 9:33 a.m. UTC
If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
unique_ptr to the request-object gets reset and hence, its destructor
is called. However, the wrap-object points to the same object and is
still alive at this moment. When the task_run-function is finished, the
destructor of the wrap-object is called, which in return calls the
destructor of the request-object again.

Instead of taking care of both, the request and the wrap-object, we can
move the request to the wrap which will then effectively take care of
the request object automatically.

Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
 1 file changed, 12 insertions(+), 16 deletions(-)

Comments

Umang Jain March 11, 2021, 11:35 a.m. UTC | #1
Hi Marian,

I noticed a few potential null/segfault issues with this approach

On 3/11/21 3:03 PM, Marian Cichy wrote:
> If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> unique_ptr to the request-object gets reset and hence, its destructor
> is called. However, the wrap-object points to the same object and is
> still alive at this moment. When the task_run-function is finished, the
> destructor of the wrap-object is called, which in return calls the
> destructor of the request-object again.
>
> Instead of taking care of both, the request and the wrap-object, we can
> move the request to the wrap which will then effectively take care of
> the request object automatically.
...take care of the request s/object/object-deletion/ maybe?

>
> Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
>   1 file changed, 12 insertions(+), 16 deletions(-)
>
> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> index 636c14df..e86c3d7f 100644
> --- a/src/gstreamer/gstlibcamerasrc.cpp
> +++ b/src/gstreamer/gstlibcamerasrc.cpp
> @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
>   #define GST_CAT_DEFAULT source_debug
>   
>   struct RequestWrap {
> -	RequestWrap(Request *request);
> +	RequestWrap(std::unique_ptr<Request> request);
>   	~RequestWrap();
>   
>   	void attachBuffer(GstBuffer *buffer);
>   	GstBuffer *detachBuffer(Stream *stream);
>   
>   	/* For ptr comparison only. */
I think you need to remove this comment too
> -	Request *request_;
> +	std::unique_ptr<Request> request_;
>   	std::map<Stream *, GstBuffer *> buffers_;
>   };
>   
> -RequestWrap::RequestWrap(Request *request)
> -	: request_(request)
> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> +	: request_(std::move(request))
>   {
>   }
>   
> @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
>   		if (item.second)
>   			gst_buffer_unref(item.second);
>   	}
> -
> -	delete request_;
>   }
>   
>   void RequestWrap::attachBuffer(GstBuffer *buffer)
> @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
>   	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
>   	requests_.pop();
>   
> -	g_return_if_fail(wrap->request_ == request);
> +	g_return_if_fail(wrap->request_.get() == request);
>   
>   	if ((request->status() == Request::RequestCancelled)) {
>   		GST_DEBUG_OBJECT(src_, "Request was cancelled");
> @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
>   	GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
>   	GstLibcameraSrcState *state = self->state;
>   
> -	std::unique_ptr<Request> request = state->cam_->createRequest();
> -	auto wrap = std::make_unique<RequestWrap>(request.get());
> +	auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
*1 for marker below
>   	for (GstPad *srcpad : state->srcpads_) {
>   		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
>   		GstBuffer *buffer;
> @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
>   						     &buffer, nullptr);
>   		if (ret != GST_FLOW_OK) {
>   			/*
> -			 * RequestWrap does not take ownership, and we won't be
> -			 * queueing this one due to lack of buffers.
> +			 * RequestWrap has ownership of the rquest, and we
s/rquest/request/
> +			 * won't be queueing this one due to lack of buffers.
>   			 */
> -			request.reset();
> +			wrap.release();
>   			break;
>   		}
>   
>   		wrap->attachBuffer(buffer);
>   	}
>   
> -	if (request) {
> +	if (wrap) {
I think this should be `if (wrap.get())` no? `wrap.release()` if hit, 
will set nullptr to the _managed_ object, so we need to check the 
managed object for null
>   		GLibLocker lock(GST_OBJECT(self));
>   		GST_TRACE_OBJECT(self, "Requesting buffers");
> -		state->cam_->queueRequest(request.get());
> +		state->cam_->queueRequest(wrap->request_.get());
from *1, createRequest() call can return nullptr, hence you have a

std::make_unique<RequestWrap>(nullptr);

which will set request_ as nullptr on the wrap object. It  is fine as 
long as wrap->request_ is not used unlike here, in this block.


>   		state->requests_.push(std::move(wrap));
>   
> -		/* The request will be deleted in the completion handler. */
> -		request.release();
> +		/* The RequestWrap will be deleted in the completion handler. */
>   	}
>   
>   	GstFlowReturn ret = GST_FLOW_OK;
Laurent Pinchart March 11, 2021, 11:59 a.m. UTC | #2
Hello,

On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> 
> Hi Marian,
> 
> I noticed a few potential null/segfault issues with this approach
> 
> On 3/11/21 3:03 PM, Marian Cichy wrote:
> > If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> > unique_ptr to the request-object gets reset and hence, its destructor
> > is called. However, the wrap-object points to the same object and is
> > still alive at this moment. When the task_run-function is finished, the
> > destructor of the wrap-object is called, which in return calls the
> > destructor of the request-object again.
> >
> > Instead of taking care of both, the request and the wrap-object, we can
> > move the request to the wrap which will then effectively take care of
> > the request object automatically.
>
> ...take care of the request s/object/object-deletion/ maybe?
> 
> >
> > Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> > Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >   src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
> >   1 file changed, 12 insertions(+), 16 deletions(-)
> >
> > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> > index 636c14df..e86c3d7f 100644
> > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> >   #define GST_CAT_DEFAULT source_debug
> >   
> >   struct RequestWrap {
> > -	RequestWrap(Request *request);
> > +	RequestWrap(std::unique_ptr<Request> request);
> >   	~RequestWrap();
> >   
> >   	void attachBuffer(GstBuffer *buffer);
> >   	GstBuffer *detachBuffer(Stream *stream);
> >   
> >   	/* For ptr comparison only. */
>
> I think you need to remove this comment too

Indeed :-)

> > -	Request *request_;
> > +	std::unique_ptr<Request> request_;
> >   	std::map<Stream *, GstBuffer *> buffers_;
> >   };
> >   
> > -RequestWrap::RequestWrap(Request *request)
> > -	: request_(request)
> > +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> > +	: request_(std::move(request))
> >   {
> >   }
> >   
> > @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> >   		if (item.second)
> >   			gst_buffer_unref(item.second);
> >   	}
> > -
> > -	delete request_;
> >   }
> >   
> >   void RequestWrap::attachBuffer(GstBuffer *buffer)
> > @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
> >   	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
> >   	requests_.pop();
> >   
> > -	g_return_if_fail(wrap->request_ == request);
> > +	g_return_if_fail(wrap->request_.get() == request);
> >   
> >   	if ((request->status() == Request::RequestCancelled)) {
> >   		GST_DEBUG_OBJECT(src_, "Request was cancelled");
> > @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> >   	GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> >   	GstLibcameraSrcState *state = self->state;
> >   
> > -	std::unique_ptr<Request> request = state->cam_->createRequest();
> > -	auto wrap = std::make_unique<RequestWrap>(request.get());
> > +	auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
>
> *1 for marker below
>
> >   	for (GstPad *srcpad : state->srcpads_) {
> >   		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
> >   		GstBuffer *buffer;
> > @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> >   						     &buffer, nullptr);
> >   		if (ret != GST_FLOW_OK) {
> >   			/*
> > -			 * RequestWrap does not take ownership, and we won't be
> > -			 * queueing this one due to lack of buffers.
> > +			 * RequestWrap has ownership of the rquest, and we
>
> s/rquest/request/
>
> > +			 * won't be queueing this one due to lack of buffers.
> >   			 */
> > -			request.reset();
> > +			wrap.release();
> >   			break;
> >   		}
> >   
> >   		wrap->attachBuffer(buffer);
> >   	}
> >   
> > -	if (request) {
> > +	if (wrap) {
>
> I think this should be `if (wrap.get())` no? `wrap.release()` if hit, 
> will set nullptr to the _managed_ object, so we need to check the 
> managed object for null

This is what is happening already:
https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool

> >   		GLibLocker lock(GST_OBJECT(self));
> >   		GST_TRACE_OBJECT(self, "Requesting buffers");
> > -		state->cam_->queueRequest(request.get());
> > +		state->cam_->queueRequest(wrap->request_.get());
>
> from *1, createRequest() call can return nullptr, hence you have a
> 
> std::make_unique<RequestWrap>(nullptr);
> 
> which will set request_ as nullptr on the wrap object. It  is fine as 
> long as wrap->request_ is not used unlike here, in this block.

That's indeed a problem. Maybe

	if (wrap && wrap->request_)

above ? Nicolas, any opinion ?

> >   		state->requests_.push(std::move(wrap));
> >   
> > -		/* The request will be deleted in the completion handler. */
> > -		request.release();
> > +		/* The RequestWrap will be deleted in the completion handler. */
> >   	}
> >   
> >   	GstFlowReturn ret = GST_FLOW_OK;
Umang Jain March 11, 2021, 12:17 p.m. UTC | #3
Hi Laurent,

On 3/11/21 5:29 PM, Laurent Pinchart wrote:
> Hello,
>
> On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
>> Hi Marian,
>>
>> I noticed a few potential null/segfault issues with this approach
>>
>> On 3/11/21 3:03 PM, Marian Cichy wrote:
>>> If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
>>> unique_ptr to the request-object gets reset and hence, its destructor
>>> is called. However, the wrap-object points to the same object and is
>>> still alive at this moment. When the task_run-function is finished, the
>>> destructor of the wrap-object is called, which in return calls the
>>> destructor of the request-object again.
>>>
>>> Instead of taking care of both, the request and the wrap-object, we can
>>> move the request to the wrap which will then effectively take care of
>>> the request object automatically.
>> ...take care of the request s/object/object-deletion/ maybe?
>>
>>> Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
>>> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>> ---
>>>    src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
>>>    1 file changed, 12 insertions(+), 16 deletions(-)
>>>
>>> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
>>> index 636c14df..e86c3d7f 100644
>>> --- a/src/gstreamer/gstlibcamerasrc.cpp
>>> +++ b/src/gstreamer/gstlibcamerasrc.cpp
>>> @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
>>>    #define GST_CAT_DEFAULT source_debug
>>>    
>>>    struct RequestWrap {
>>> -	RequestWrap(Request *request);
>>> +	RequestWrap(std::unique_ptr<Request> request);
>>>    	~RequestWrap();
>>>    
>>>    	void attachBuffer(GstBuffer *buffer);
>>>    	GstBuffer *detachBuffer(Stream *stream);
>>>    
>>>    	/* For ptr comparison only. */
>> I think you need to remove this comment too
> Indeed :-)
>
>>> -	Request *request_;
>>> +	std::unique_ptr<Request> request_;
>>>    	std::map<Stream *, GstBuffer *> buffers_;
>>>    };
>>>    
>>> -RequestWrap::RequestWrap(Request *request)
>>> -	: request_(request)
>>> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
>>> +	: request_(std::move(request))
>>>    {
>>>    }
>>>    
>>> @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
>>>    		if (item.second)
>>>    			gst_buffer_unref(item.second);
>>>    	}
>>> -
>>> -	delete request_;
>>>    }
>>>    
>>>    void RequestWrap::attachBuffer(GstBuffer *buffer)
>>> @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
>>>    	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
>>>    	requests_.pop();
>>>    
>>> -	g_return_if_fail(wrap->request_ == request);
>>> +	g_return_if_fail(wrap->request_.get() == request);
>>>    
>>>    	if ((request->status() == Request::RequestCancelled)) {
>>>    		GST_DEBUG_OBJECT(src_, "Request was cancelled");
>>> @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
>>>    	GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
>>>    	GstLibcameraSrcState *state = self->state;
>>>    
>>> -	std::unique_ptr<Request> request = state->cam_->createRequest();
>>> -	auto wrap = std::make_unique<RequestWrap>(request.get());
>>> +	auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
>> *1 for marker below
>>
>>>    	for (GstPad *srcpad : state->srcpads_) {
>>>    		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
>>>    		GstBuffer *buffer;
>>> @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
>>>    						     &buffer, nullptr);
>>>    		if (ret != GST_FLOW_OK) {
>>>    			/*
>>> -			 * RequestWrap does not take ownership, and we won't be
>>> -			 * queueing this one due to lack of buffers.
>>> +			 * RequestWrap has ownership of the rquest, and we
>> s/rquest/request/
>>
>>> +			 * won't be queueing this one due to lack of buffers.
>>>    			 */
>>> -			request.reset();
>>> +			wrap.release();
>>>    			break;
>>>    		}
>>>    
>>>    		wrap->attachBuffer(buffer);
>>>    	}
>>>    
>>> -	if (request) {
>>> +	if (wrap) {
>> I think this should be `if (wrap.get())` no? `wrap.release()` if hit,
>> will set nullptr to the _managed_ object, so we need to check the
>> managed object for null
> This is what is happening already:
> https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
>
>>>    		GLibLocker lock(GST_OBJECT(self));
>>>    		GST_TRACE_OBJECT(self, "Requesting buffers");
>>> -		state->cam_->queueRequest(request.get());
>>> +		state->cam_->queueRequest(wrap->request_.get());
>> from *1, createRequest() call can return nullptr, hence you have a
>>
>> std::make_unique<RequestWrap>(nullptr);
>>
>> which will set request_ as nullptr on the wrap object. It  is fine as
>> long as wrap->request_ is not used unlike here, in this block.
> That's indeed a problem. Maybe
>
> 	if (wrap && wrap->request_)
>
> above ? Nicolas, any opinion ?
OR null-check createRequest()'s returned object before wrapping it (i.e. 
ensure RequestWrap doesn't wrap a nullptr from the start), so we don't 
have to check everything for the wrap->request_ is valid or not.
>
>>>    		state->requests_.push(std::move(wrap));
>>>    
>>> -		/* The request will be deleted in the completion handler. */
>>> -		request.release();
>>> +		/* The RequestWrap will be deleted in the completion handler. */
>>>    	}
>>>    
>>>    	GstFlowReturn ret = GST_FLOW_OK;
Laurent Pinchart March 11, 2021, 12:22 p.m. UTC | #4
Hi Umang,

On Thu, Mar 11, 2021 at 05:47:59PM +0530, Umang Jain wrote:
> On 3/11/21 5:29 PM, Laurent Pinchart wrote:
> > On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> >> On 3/11/21 3:03 PM, Marian Cichy wrote:
> >>> If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> >>> unique_ptr to the request-object gets reset and hence, its destructor
> >>> is called. However, the wrap-object points to the same object and is
> >>> still alive at this moment. When the task_run-function is finished, the
> >>> destructor of the wrap-object is called, which in return calls the
> >>> destructor of the request-object again.
> >>>
> >>> Instead of taking care of both, the request and the wrap-object, we can
> >>> move the request to the wrap which will then effectively take care of
> >>> the request object automatically.
> >> ...take care of the request s/object/object-deletion/ maybe?
> >>
> >>> Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> >>> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> ---
> >>>    src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
> >>>    1 file changed, 12 insertions(+), 16 deletions(-)
> >>>
> >>> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> >>> index 636c14df..e86c3d7f 100644
> >>> --- a/src/gstreamer/gstlibcamerasrc.cpp
> >>> +++ b/src/gstreamer/gstlibcamerasrc.cpp
> >>> @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> >>>    #define GST_CAT_DEFAULT source_debug
> >>>    
> >>>    struct RequestWrap {
> >>> -	RequestWrap(Request *request);
> >>> +	RequestWrap(std::unique_ptr<Request> request);
> >>>    	~RequestWrap();
> >>>    
> >>>    	void attachBuffer(GstBuffer *buffer);
> >>>    	GstBuffer *detachBuffer(Stream *stream);
> >>>    
> >>>    	/* For ptr comparison only. */
> >>
> >> I think you need to remove this comment too
> >
> > Indeed :-)
> >
> >>> -	Request *request_;
> >>> +	std::unique_ptr<Request> request_;
> >>>    	std::map<Stream *, GstBuffer *> buffers_;
> >>>    };
> >>>    
> >>> -RequestWrap::RequestWrap(Request *request)
> >>> -	: request_(request)
> >>> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> >>> +	: request_(std::move(request))
> >>>    {
> >>>    }
> >>>    
> >>> @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> >>>    		if (item.second)
> >>>    			gst_buffer_unref(item.second);
> >>>    	}
> >>> -
> >>> -	delete request_;
> >>>    }
> >>>    
> >>>    void RequestWrap::attachBuffer(GstBuffer *buffer)
> >>> @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
> >>>    	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
> >>>    	requests_.pop();
> >>>    
> >>> -	g_return_if_fail(wrap->request_ == request);
> >>> +	g_return_if_fail(wrap->request_.get() == request);
> >>>    
> >>>    	if ((request->status() == Request::RequestCancelled)) {
> >>>    		GST_DEBUG_OBJECT(src_, "Request was cancelled");
> >>> @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> >>>    	GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> >>>    	GstLibcameraSrcState *state = self->state;
> >>>    
> >>> -	std::unique_ptr<Request> request = state->cam_->createRequest();
> >>> -	auto wrap = std::make_unique<RequestWrap>(request.get());
> >>> +	auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
> >
> >> *1 for marker below
> >>
> >>>    	for (GstPad *srcpad : state->srcpads_) {
> >>>    		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
> >>>    		GstBuffer *buffer;
> >>> @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> >>>    						     &buffer, nullptr);
> >>>    		if (ret != GST_FLOW_OK) {
> >>>    			/*
> >>> -			 * RequestWrap does not take ownership, and we won't be
> >>> -			 * queueing this one due to lack of buffers.
> >>> +			 * RequestWrap has ownership of the rquest, and we
> >>
> >> s/rquest/request/
> >>
> >>> +			 * won't be queueing this one due to lack of buffers.
> >>>    			 */
> >>> -			request.reset();
> >>> +			wrap.release();
> >>>    			break;
> >>>    		}
> >>>    
> >>>    		wrap->attachBuffer(buffer);
> >>>    	}
> >>>    
> >>> -	if (request) {
> >>> +	if (wrap) {
> >>
> >> I think this should be `if (wrap.get())` no? `wrap.release()` if hit,
> >> will set nullptr to the _managed_ object, so we need to check the
> >> managed object for null
> >
> > This is what is happening already:
> > https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
> >
> >>>    		GLibLocker lock(GST_OBJECT(self));
> >>>    		GST_TRACE_OBJECT(self, "Requesting buffers");
> >>> -		state->cam_->queueRequest(request.get());
> >>> +		state->cam_->queueRequest(wrap->request_.get());
> >>
> >> from *1, createRequest() call can return nullptr, hence you have a
> >>
> >> std::make_unique<RequestWrap>(nullptr);
> >>
> >> which will set request_ as nullptr on the wrap object. It  is fine as
> >> long as wrap->request_ is not used unlike here, in this block.
> >
> > That's indeed a problem. Maybe
> >
> > 	if (wrap && wrap->request_)
> >
> > above ? Nicolas, any opinion ?
>
> OR null-check createRequest()'s returned object before wrapping it (i.e. 
> ensure RequestWrap doesn't wrap a nullptr from the start), so we don't 
> have to check everything for the wrap->request_ is valid or not.

But, as far as I understand, code further down this function still have
to run in that case, we can't just do an early return using a null
check. That's why I've asked for Nicolas' feedback, he knows this code
much better than I do.

> >>>    		state->requests_.push(std::move(wrap));
> >>>    
> >>> -		/* The request will be deleted in the completion handler. */
> >>> -		request.release();
> >>> +		/* The RequestWrap will be deleted in the completion handler. */
> >>>    	}
> >>>    
> >>>    	GstFlowReturn ret = GST_FLOW_OK;
Nicolas Dufresne March 11, 2021, 8:35 p.m. UTC | #5
Le jeudi 11 mars 2021 à 13:59 +0200, Laurent Pinchart a écrit :
> Hello,
> 
> On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> > 
> > Hi Marian,
> > 
> > I noticed a few potential null/segfault issues with this approach
> > 
> > On 3/11/21 3:03 PM, Marian Cichy wrote:
> > > If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> > > unique_ptr to the request-object gets reset and hence, its destructor
> > > is called. However, the wrap-object points to the same object and is
> > > still alive at this moment. When the task_run-function is finished, the
> > > destructor of the wrap-object is called, which in return calls the
> > > destructor of the request-object again.
> > > 
> > > Instead of taking care of both, the request and the wrap-object, we can
> > > move the request to the wrap which will then effectively take care of
> > > the request object automatically.
> > 
> > ...take care of the request s/object/object-deletion/ maybe?
> > 
> > > 
> > > Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> > > Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > >   src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
> > >   1 file changed, 12 insertions(+), 16 deletions(-)
> > > 
> > > diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> > > b/src/gstreamer/gstlibcamerasrc.cpp
> > > index 636c14df..e86c3d7f 100644
> > > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > > @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> > >   #define GST_CAT_DEFAULT source_debug
> > >   
> > >   struct RequestWrap {
> > > -       RequestWrap(Request *request);
> > > +       RequestWrap(std::unique_ptr<Request> request);
> > >         ~RequestWrap();
> > >   
> > >         void attachBuffer(GstBuffer *buffer);
> > >         GstBuffer *detachBuffer(Stream *stream);
> > >   
> > >         /* For ptr comparison only. */
> > 
> > I think you need to remove this comment too
> 
> Indeed :-)
> 
> > > -       Request *request_;
> > > +       std::unique_ptr<Request> request_;
> > >         std::map<Stream *, GstBuffer *> buffers_;
> > >   };
> > >   
> > > -RequestWrap::RequestWrap(Request *request)
> > > -       : request_(request)
> > > +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> > > +       : request_(std::move(request))
> > >   {
> > >   }
> > >   
> > > @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> > >                 if (item.second)
> > >                         gst_buffer_unref(item.second);
> > >         }
> > > -
> > > -       delete request_;
> > >   }
> > >   
> > >   void RequestWrap::attachBuffer(GstBuffer *buffer)
> > > @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request
> > > *request)
> > >         std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
> > >         requests_.pop();
> > >   
> > > -       g_return_if_fail(wrap->request_ == request);
> > > +       g_return_if_fail(wrap->request_.get() == request);
> > >   
> > >         if ((request->status() == Request::RequestCancelled)) {
> > >                 GST_DEBUG_OBJECT(src_, "Request was cancelled");
> > > @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> > >         GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> > >         GstLibcameraSrcState *state = self->state;
> > >   
> > > -       std::unique_ptr<Request> request = state->cam_->createRequest();
> > > -       auto wrap = std::make_unique<RequestWrap>(request.get());
> > > +       auto wrap =

Please keep seperate, and add missing nullptr check in between.

> > > std::make_unique<RequestWrap>(state->cam_->createRequest());
> > 
> > *1 for marker below
> > 
> > >         for (GstPad *srcpad : state->srcpads_) {
> > >                 GstLibcameraPool *pool =
> > > gst_libcamera_pad_get_pool(srcpad);
> > >                 GstBuffer *buffer;
> > > @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> > >                                                      &buffer, nullptr);
> > >                 if (ret != GST_FLOW_OK) {
> > >                         /*
> > > -                        * RequestWrap does not take ownership, and we
> > > won't be
> > > -                        * queueing this one due to lack of buffers.
> > > +                        * RequestWrap has ownership of the rquest, and we
> > 
> > s/rquest/request/
> > 
> > > +                        * won't be queueing this one due to lack of
> > > buffers.
> > >                          */
> > > -                       request.reset();
> > > +                       wrap.release();
> > >                         break;
> > >                 }
> > >   
> > >                 wrap->attachBuffer(buffer);
> > >         }
> > >   
> > > -       if (request) {
> > > +       if (wrap) {
> > 
> > I think this should be `if (wrap.get())` no? `wrap.release()` if hit, 
> > will set nullptr to the _managed_ object, so we need to check the 
> > managed object for null
> 
> This is what is happening already:
> https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
> 
> > >                 GLibLocker lock(GST_OBJECT(self));
> > >                 GST_TRACE_OBJECT(self, "Requesting buffers");
> > > -               state->cam_->queueRequest(request.get());
> > > +               state->cam_->queueRequest(wrap->request_.get());
> > 
> > from *1, createRequest() call can return nullptr, hence you have a
> > 
> > std::make_unique<RequestWrap>(nullptr);
> > 
> > which will set request_ as nullptr on the wrap object. It  is fine as 
> > long as wrap->request_ is not used unlike here, in this block.
> 
> That's indeed a problem. Maybe
> 
>         if (wrap && wrap->request_)
> 
> above ? Nicolas, any opinion ?

This way we don't have to be defensive in so many places.

> 
> > >                 state->requests_.push(std::move(wrap));
> > >   
> > > -               /* The request will be deleted in the completion handler.
> > > */
> > > -               request.release();
> > > +               /* The RequestWrap will be deleted in the completion
> > > handler. */
> > >         }
> > >   
> > >         GstFlowReturn ret = GST_FLOW_OK;
>
Nicolas Dufresne March 11, 2021, 8:36 p.m. UTC | #6
Le jeudi 11 mars 2021 à 17:47 +0530, Umang Jain a écrit :
> Hi Laurent,
> 
> On 3/11/21 5:29 PM, Laurent Pinchart wrote:
> > Hello,
> > 
> > On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> > > Hi Marian,
> > > 
> > > I noticed a few potential null/segfault issues with this approach
> > > 
> > > On 3/11/21 3:03 PM, Marian Cichy wrote:
> > > > If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> > > > unique_ptr to the request-object gets reset and hence, its destructor
> > > > is called. However, the wrap-object points to the same object and is
> > > > still alive at this moment. When the task_run-function is finished, the
> > > > destructor of the wrap-object is called, which in return calls the
> > > > destructor of the request-object again.
> > > > 
> > > > Instead of taking care of both, the request and the wrap-object, we can
> > > > move the request to the wrap which will then effectively take care of
> > > > the request object automatically.
> > > ...take care of the request s/object/object-deletion/ maybe?
> > > 
> > > > Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> > > > Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > ---
> > > >    src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
> > > >    1 file changed, 12 insertions(+), 16 deletions(-)
> > > > 
> > > > diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> > > > b/src/gstreamer/gstlibcamerasrc.cpp
> > > > index 636c14df..e86c3d7f 100644
> > > > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > > > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > > > @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> > > >    #define GST_CAT_DEFAULT source_debug
> > > >    
> > > >    struct RequestWrap {
> > > > -       RequestWrap(Request *request);
> > > > +       RequestWrap(std::unique_ptr<Request> request);
> > > >         ~RequestWrap();
> > > >    
> > > >         void attachBuffer(GstBuffer *buffer);
> > > >         GstBuffer *detachBuffer(Stream *stream);
> > > >    
> > > >         /* For ptr comparison only. */
> > > I think you need to remove this comment too
> > Indeed :-)
> > 
> > > > -       Request *request_;
> > > > +       std::unique_ptr<Request> request_;
> > > >         std::map<Stream *, GstBuffer *> buffers_;
> > > >    };
> > > >    
> > > > -RequestWrap::RequestWrap(Request *request)
> > > > -       : request_(request)
> > > > +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> > > > +       : request_(std::move(request))
> > > >    {
> > > >    }
> > > >    
> > > > @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> > > >                 if (item.second)
> > > >                         gst_buffer_unref(item.second);
> > > >         }
> > > > -
> > > > -       delete request_;
> > > >    }
> > > >    
> > > >    void RequestWrap::attachBuffer(GstBuffer *buffer)
> > > > @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request
> > > > *request)
> > > >         std::unique_ptr<RequestWrap> wrap =
> > > > std::move(requests_.front());
> > > >         requests_.pop();
> > > >    
> > > > -       g_return_if_fail(wrap->request_ == request);
> > > > +       g_return_if_fail(wrap->request_.get() == request);
> > > >    
> > > >         if ((request->status() == Request::RequestCancelled)) {
> > > >                 GST_DEBUG_OBJECT(src_, "Request was cancelled");
> > > > @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> > > >         GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> > > >         GstLibcameraSrcState *state = self->state;
> > > >    
> > > > -       std::unique_ptr<Request> request = state->cam_->createRequest();
> > > > -       auto wrap = std::make_unique<RequestWrap>(request.get());
> > > > +       auto wrap = std::make_unique<RequestWrap>(state->cam_-
> > > > >createRequest());
> > > *1 for marker below
> > > 
> > > >         for (GstPad *srcpad : state->srcpads_) {
> > > >                 GstLibcameraPool *pool =
> > > > gst_libcamera_pad_get_pool(srcpad);
> > > >                 GstBuffer *buffer;
> > > > @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> > > >                                                      &buffer, nullptr);
> > > >                 if (ret != GST_FLOW_OK) {
> > > >                         /*
> > > > -                        * RequestWrap does not take ownership, and we
> > > > won't be
> > > > -                        * queueing this one due to lack of buffers.
> > > > +                        * RequestWrap has ownership of the rquest, and
> > > > we
> > > s/rquest/request/
> > > 
> > > > +                        * won't be queueing this one due to lack of
> > > > buffers.
> > > >                          */
> > > > -                       request.reset();
> > > > +                       wrap.release();
> > > >                         break;
> > > >                 }
> > > >    
> > > >                 wrap->attachBuffer(buffer);
> > > >         }
> > > >    
> > > > -       if (request) {
> > > > +       if (wrap) {
> > > I think this should be `if (wrap.get())` no? `wrap.release()` if hit,
> > > will set nullptr to the _managed_ object, so we need to check the
> > > managed object for null
> > This is what is happening already:
> > https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
> > 
> > > >                 GLibLocker lock(GST_OBJECT(self));
> > > >                 GST_TRACE_OBJECT(self, "Requesting buffers");
> > > > -               state->cam_->queueRequest(request.get());
> > > > +               state->cam_->queueRequest(wrap->request_.get());
> > > from *1, createRequest() call can return nullptr, hence you have a
> > > 
> > > std::make_unique<RequestWrap>(nullptr);
> > > 
> > > which will set request_ as nullptr on the wrap object. It  is fine as
> > > long as wrap->request_ is not used unlike here, in this block.
> > That's indeed a problem. Maybe
> > 
> >         if (wrap && wrap->request_)
> > 
> > above ? Nicolas, any opinion ?
> OR null-check createRequest()'s returned object before wrapping it (i.e. 
> ensure RequestWrap doesn't wrap a nullptr from the start), so we don't 
> have to check everything for the wrap->request_ is valid or not.

Agreed! I've just replied the same in previous email.

> > 
> > > >                 state->requests_.push(std::move(wrap));
> > > >    
> > > > -               /* The request will be deleted in the completion
> > > > handler. */
> > > > -               request.release();
> > > > +               /* The RequestWrap will be deleted in the completion
> > > > handler. */
> > > >         }
> > > >    
> > > >         GstFlowReturn ret = GST_FLOW_OK;
> 
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
Nicolas Dufresne March 11, 2021, 9:02 p.m. UTC | #7
Le jeudi 11 mars 2021 à 14:22 +0200, Laurent Pinchart a écrit :
> Hi Umang,
> 
> On Thu, Mar 11, 2021 at 05:47:59PM +0530, Umang Jain wrote:
> > On 3/11/21 5:29 PM, Laurent Pinchart wrote:
> > > On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> > > > On 3/11/21 3:03 PM, Marian Cichy wrote:
> > > > > If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> > > > > unique_ptr to the request-object gets reset and hence, its destructor
> > > > > is called. However, the wrap-object points to the same object and is
> > > > > still alive at this moment. When the task_run-function is finished,
> > > > > the
> > > > > destructor of the wrap-object is called, which in return calls the
> > > > > destructor of the request-object again.
> > > > > 
> > > > > Instead of taking care of both, the request and the wrap-object, we
> > > > > can
> > > > > move the request to the wrap which will then effectively take care of
> > > > > the request object automatically.
> > > > ...take care of the request s/object/object-deletion/ maybe?
> > > > 
> > > > > Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> > > > > Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > ---
> > > > >    src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
> > > > >    1 file changed, 12 insertions(+), 16 deletions(-)
> > > > > 
> > > > > diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> > > > > b/src/gstreamer/gstlibcamerasrc.cpp
> > > > > index 636c14df..e86c3d7f 100644
> > > > > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > > > > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > > > > @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> > > > >    #define GST_CAT_DEFAULT source_debug
> > > > >    
> > > > >    struct RequestWrap {
> > > > > -       RequestWrap(Request *request);
> > > > > +       RequestWrap(std::unique_ptr<Request> request);
> > > > >         ~RequestWrap();
> > > > >    
> > > > >         void attachBuffer(GstBuffer *buffer);
> > > > >         GstBuffer *detachBuffer(Stream *stream);
> > > > >    
> > > > >         /* For ptr comparison only. */
> > > > 
> > > > I think you need to remove this comment too
> > > 
> > > Indeed :-)
> > > 
> > > > > -       Request *request_;
> > > > > +       std::unique_ptr<Request> request_;
> > > > >         std::map<Stream *, GstBuffer *> buffers_;
> > > > >    };
> > > > >    
> > > > > -RequestWrap::RequestWrap(Request *request)
> > > > > -       : request_(request)
> > > > > +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> > > > > +       : request_(std::move(request))
> > > > >    {
> > > > >    }
> > > > >    
> > > > > @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> > > > >                 if (item.second)
> > > > >                         gst_buffer_unref(item.second);
> > > > >         }
> > > > > -
> > > > > -       delete request_;
> > > > >    }
> > > > >    
> > > > >    void RequestWrap::attachBuffer(GstBuffer *buffer)
> > > > > @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request
> > > > > *request)
> > > > >         std::unique_ptr<RequestWrap> wrap =
> > > > > std::move(requests_.front());
> > > > >         requests_.pop();
> > > > >    
> > > > > -       g_return_if_fail(wrap->request_ == request);
> > > > > +       g_return_if_fail(wrap->request_.get() == request);
> > > > >    
> > > > >         if ((request->status() == Request::RequestCancelled)) {
> > > > >                 GST_DEBUG_OBJECT(src_, "Request was cancelled");
> > > > > @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> > > > >         GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> > > > >         GstLibcameraSrcState *state = self->state;
> > > > >    
> > > > > -       std::unique_ptr<Request> request = state->cam_-
> > > > > >createRequest();
> > > > > -       auto wrap = std::make_unique<RequestWrap>(request.get());
> > > > > +       auto wrap = std::make_unique<RequestWrap>(state->cam_-
> > > > > >createRequest());
> > > 
> > > > *1 for marker below
> > > > 
> > > > >         for (GstPad *srcpad : state->srcpads_) {
> > > > >                 GstLibcameraPool *pool =
> > > > > gst_libcamera_pad_get_pool(srcpad);
> > > > >                 GstBuffer *buffer;
> > > > > @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> > > > >                                                      &buffer,
> > > > > nullptr);
> > > > >                 if (ret != GST_FLOW_OK) {
> > > > >                         /*
> > > > > -                        * RequestWrap does not take ownership, and we
> > > > > won't be
> > > > > -                        * queueing this one due to lack of buffers.
> > > > > +                        * RequestWrap has ownership of the rquest,
> > > > > and we
> > > > 
> > > > s/rquest/request/
> > > > 
> > > > > +                        * won't be queueing this one due to lack of
> > > > > buffers.
> > > > >                          */
> > > > > -                       request.reset();
> > > > > +                       wrap.release();
> > > > >                         break;
> > > > >                 }
> > > > >    
> > > > >                 wrap->attachBuffer(buffer);
> > > > >         }
> > > > >    
> > > > > -       if (request) {
> > > > > +       if (wrap) {
> > > > 
> > > > I think this should be `if (wrap.get())` no? `wrap.release()` if hit,
> > > > will set nullptr to the _managed_ object, so we need to check the
> > > > managed object for null
> > > 
> > > This is what is happening already:
> > > https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
> > > 
> > > > >                 GLibLocker lock(GST_OBJECT(self));
> > > > >                 GST_TRACE_OBJECT(self, "Requesting buffers");
> > > > > -               state->cam_->queueRequest(request.get());
> > > > > +               state->cam_->queueRequest(wrap->request_.get());
> > > > 
> > > > from *1, createRequest() call can return nullptr, hence you have a
> > > > 
> > > > std::make_unique<RequestWrap>(nullptr);
> > > > 
> > > > which will set request_ as nullptr on the wrap object. It  is fine as
> > > > long as wrap->request_ is not used unlike here, in this block.
> > > 
> > > That's indeed a problem. Maybe
> > > 
> > >         if (wrap && wrap->request_)
> > > 
> > > above ? Nicolas, any opinion ?
> > 
> > OR null-check createRequest()'s returned object before wrapping it (i.e. 
> > ensure RequestWrap doesn't wrap a nullptr from the start), so we don't 
> > have to check everything for the wrap->request_ is valid or not.
> 
> But, as far as I understand, code further down this function still have
> to run in that case, we can't just do an early return using a null
> check. That's why I've asked for Nicolas' feedback, he knows this code
> much better than I do.

Hmm, right. Something like this should do:

if (!request) {
	GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,...;
        gst_task_stop(task);
	return;
}

> 
> > > > >                 state->requests_.push(std::move(wrap));
> > > > >    
> > > > > -               /* The request will be deleted in the completion
> > > > > handler. */
> > > > > -               request.release();
> > > > > +               /* The RequestWrap will be deleted in the completion
> > > > > handler. */
> > > > >         }
> > > > >    
> > > > >         GstFlowReturn ret = GST_FLOW_OK;
>
Laurent Pinchart March 11, 2021, 11:14 p.m. UTC | #8
Hi Nicolas,

On Thu, Mar 11, 2021 at 04:02:20PM -0500, Nicolas Dufresne wrote:
> Le jeudi 11 mars 2021 à 14:22 +0200, Laurent Pinchart a écrit :
> > On Thu, Mar 11, 2021 at 05:47:59PM +0530, Umang Jain wrote:
> > > On 3/11/21 5:29 PM, Laurent Pinchart wrote:
> > > > On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> > > > > On 3/11/21 3:03 PM, Marian Cichy wrote:
> > > > > > If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> > > > > > unique_ptr to the request-object gets reset and hence, its destructor
> > > > > > is called. However, the wrap-object points to the same object and is
> > > > > > still alive at this moment. When the task_run-function is finished, the
> > > > > > destructor of the wrap-object is called, which in return calls the
> > > > > > destructor of the request-object again.
> > > > > > 
> > > > > > Instead of taking care of both, the request and the wrap-object, we can
> > > > > > move the request to the wrap which will then effectively take care of
> > > > > > the request object automatically.
> > > > >
> > > > > ...take care of the request s/object/object-deletion/ maybe?
> > > > > 
> > > > > > Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> > > > > > Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > ---
> > > > > >    src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
> > > > > >    1 file changed, 12 insertions(+), 16 deletions(-)
> > > > > > 
> > > > > > diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > b/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > index 636c14df..e86c3d7f 100644
> > > > > > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> > > > > >    #define GST_CAT_DEFAULT source_debug
> > > > > >    
> > > > > >    struct RequestWrap {
> > > > > > -       RequestWrap(Request *request);
> > > > > > +       RequestWrap(std::unique_ptr<Request> request);
> > > > > >         ~RequestWrap();
> > > > > >    
> > > > > >         void attachBuffer(GstBuffer *buffer);
> > > > > >         GstBuffer *detachBuffer(Stream *stream);
> > > > > >    
> > > > > >         /* For ptr comparison only. */
> > > > > 
> > > > > I think you need to remove this comment too
> > > > 
> > > > Indeed :-)
> > > > 
> > > > > > -       Request *request_;
> > > > > > +       std::unique_ptr<Request> request_;
> > > > > >         std::map<Stream *, GstBuffer *> buffers_;
> > > > > >    };
> > > > > >    
> > > > > > -RequestWrap::RequestWrap(Request *request)
> > > > > > -       : request_(request)
> > > > > > +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> > > > > > +       : request_(std::move(request))
> > > > > >    {
> > > > > >    }
> > > > > >    
> > > > > > @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> > > > > >                 if (item.second)
> > > > > >                         gst_buffer_unref(item.second);
> > > > > >         }
> > > > > > -
> > > > > > -       delete request_;
> > > > > >    }
> > > > > >    
> > > > > >    void RequestWrap::attachBuffer(GstBuffer *buffer)
> > > > > > @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
> > > > > >         std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
> > > > > >         requests_.pop();
> > > > > >    
> > > > > > -       g_return_if_fail(wrap->request_ == request);
> > > > > > +       g_return_if_fail(wrap->request_.get() == request);
> > > > > >    
> > > > > >         if ((request->status() == Request::RequestCancelled)) {
> > > > > >                 GST_DEBUG_OBJECT(src_, "Request was cancelled");
> > > > > > @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> > > > > >         GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> > > > > >         GstLibcameraSrcState *state = self->state;
> > > > > >    
> > > > > > -       std::unique_ptr<Request> request = state->cam_->createRequest();
> > > > > > -       auto wrap = std::make_unique<RequestWrap>(request.get());
> > > > > > +       auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
> > > > 
> > > > > *1 for marker below
> > > > > 
> > > > > >         for (GstPad *srcpad : state->srcpads_) {
> > > > > >                 GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
> > > > > >                 GstBuffer *buffer;
> > > > > > @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> > > > > >                                                      &buffer, nullptr);
> > > > > >                 if (ret != GST_FLOW_OK) {
> > > > > >                         /*
> > > > > > -                        * RequestWrap does not take ownership, and we won't be
> > > > > > -                        * queueing this one due to lack of buffers.
> > > > > > +                        * RequestWrap has ownership of the rquest, and we
> > > > > 
> > > > > s/rquest/request/
> > > > > 
> > > > > > +                        * won't be queueing this one due to lack of buffers.
> > > > > >                          */
> > > > > > -                       request.reset();
> > > > > > +                       wrap.release();
> > > > > >                         break;
> > > > > >                 }
> > > > > >    
> > > > > >                 wrap->attachBuffer(buffer);
> > > > > >         }
> > > > > >    
> > > > > > -       if (request) {
> > > > > > +       if (wrap) {
> > > > > 
> > > > > I think this should be `if (wrap.get())` no? `wrap.release()` if hit,
> > > > > will set nullptr to the _managed_ object, so we need to check the
> > > > > managed object for null
> > > > 
> > > > This is what is happening already:
> > > > https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
> > > > 
> > > > > >                 GLibLocker lock(GST_OBJECT(self));
> > > > > >                 GST_TRACE_OBJECT(self, "Requesting buffers");
> > > > > > -               state->cam_->queueRequest(request.get());
> > > > > > +               state->cam_->queueRequest(wrap->request_.get());
> > > > > 
> > > > > from *1, createRequest() call can return nullptr, hence you have a
> > > > > 
> > > > > std::make_unique<RequestWrap>(nullptr);
> > > > > 
> > > > > which will set request_ as nullptr on the wrap object. It  is fine as
> > > > > long as wrap->request_ is not used unlike here, in this block.
> > > > 
> > > > That's indeed a problem. Maybe
> > > > 
> > > >         if (wrap && wrap->request_)
> > > > 
> > > > above ? Nicolas, any opinion ?
> > > 
> > > OR null-check createRequest()'s returned object before wrapping it (i.e. 
> > > ensure RequestWrap doesn't wrap a nullptr from the start), so we don't 
> > > have to check everything for the wrap->request_ is valid or not.
> > 
> > But, as far as I understand, code further down this function still have
> > to run in that case, we can't just do an early return using a null
> > check. That's why I've asked for Nicolas' feedback, he knows this code
> > much better than I do.
> 
> Hmm, right. Something like this should do:
> 
> if (!request) {
> 	GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,...;
>         gst_task_stop(task);
> 	return;
> }

Something along those lines ?

commit be081f85bbab0c8b3b5f08d73c5bda504be7f2e6
Author: Marian Cichy <m.cichy@pengutronix.de>
Date:   Thu Mar 11 10:33:25 2021 +0100

    libcamera: gst: Fix double-free when acquire_buffer fails
    
    If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
    unique_ptr to the request-object gets reset and hence, its destructor
    is called. However, the wrap-object points to the same object and is
    still alive at this moment. When the task_run-function is finished, the
    destructor of the wrap-object is called, which in return calls the
    destructor of the request-object again.
    
    Instead of taking care of both, the request and the wrap-object, we can
    move the request to the wrap which will then effectively take care of
    the request object automatically.
    
    Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
    Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
    Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 636c14dff64e..7b9671200f87 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -52,19 +52,18 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
 #define GST_CAT_DEFAULT source_debug
 
 struct RequestWrap {
-	RequestWrap(Request *request);
+	RequestWrap(std::unique_ptr<Request> request);
 	~RequestWrap();
 
 	void attachBuffer(GstBuffer *buffer);
 	GstBuffer *detachBuffer(Stream *stream);
 
-	/* For ptr comparison only. */
-	Request *request_;
+	std::unique_ptr<Request> request_;
 	std::map<Stream *, GstBuffer *> buffers_;
 };
 
-RequestWrap::RequestWrap(Request *request)
-	: request_(request)
+RequestWrap::RequestWrap(std::unique_ptr<Request> request)
+	: request_(std::move(request))
 {
 }
 
@@ -74,8 +73,6 @@ RequestWrap::~RequestWrap()
 		if (item.second)
 			gst_buffer_unref(item.second);
 	}
-
-	delete request_;
 }
 
 void RequestWrap::attachBuffer(GstBuffer *buffer)
@@ -164,7 +161,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
 	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
 	requests_.pop();
 
-	g_return_if_fail(wrap->request_ == request);
+	g_return_if_fail(wrap->request_.get() == request);
 
 	if ((request->status() == Request::RequestCancelled)) {
 		GST_DEBUG_OBJECT(src_, "Request was cancelled");
@@ -269,7 +266,18 @@ gst_libcamera_src_task_run(gpointer user_data)
 	GstLibcameraSrcState *state = self->state;
 
 	std::unique_ptr<Request> request = state->cam_->createRequest();
-	auto wrap = std::make_unique<RequestWrap>(request.get());
+	if (!request) {
+		GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
+				  ("Failed to allocate request for camera '%s'.",
+				   state->cam_->id().c_str()),
+				  ("libcamera::Camera::createRequest() failed"));
+		gst_task_stop(self->task);
+		return;
+	}
+
+	std::unique_ptr<RequestWrap> wrap =
+		std::make_unique<RequestWrap>(std::move(request));
+
 	for (GstPad *srcpad : state->srcpads_) {
 		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
 		GstBuffer *buffer;
@@ -279,24 +287,23 @@ gst_libcamera_src_task_run(gpointer user_data)
 						     &buffer, nullptr);
 		if (ret != GST_FLOW_OK) {
 			/*
-			 * RequestWrap does not take ownership, and we won't be
-			 * queueing this one due to lack of buffers.
+			 * RequestWrap has ownership of the rquest, and we
+			 * won't be queueing this one due to lack of buffers.
 			 */
-			request.reset();
+			wrap.release();
 			break;
 		}
 
 		wrap->attachBuffer(buffer);
 	}
 
-	if (request) {
+	if (wrap) {
 		GLibLocker lock(GST_OBJECT(self));
 		GST_TRACE_OBJECT(self, "Requesting buffers");
-		state->cam_->queueRequest(request.get());
+		state->cam_->queueRequest(wrap->request_.get());
 		state->requests_.push(std::move(wrap));
 
-		/* The request will be deleted in the completion handler. */
-		request.release();
+		/* The RequestWrap will be deleted in the completion handler. */
 	}
 
 	GstFlowReturn ret = GST_FLOW_OK;

Marian, would you be able to test this ?

Should the same be done in case wrap becomes null, due to a call to
gst_buffer_pool_acquire_buffer() failing, insted of continuing to
processing the flow ? The lack of symmetry between the two codes paths
bothers me, either because there's an issue there, or because I don't
understand why they need to be different :-) This can be addressed in a
subsequent patch, as it's not an issue introduced by this patch.

> > > > > >                 state->requests_.push(std::move(wrap));
> > > > > >    
> > > > > > -               /* The request will be deleted in the completion handler. */
> > > > > > -               request.release();
> > > > > > +               /* The RequestWrap will be deleted in the completion handler. */
> > > > > >         }
> > > > > >    
> > > > > >         GstFlowReturn ret = GST_FLOW_OK;
Nicolas Dufresne March 12, 2021, 3:19 p.m. UTC | #9
Le vendredi 12 mars 2021 à 01:14 +0200, Laurent Pinchart a écrit :
> Hi Nicolas,
> 
> On Thu, Mar 11, 2021 at 04:02:20PM -0500, Nicolas Dufresne wrote:
> > Le jeudi 11 mars 2021 à 14:22 +0200, Laurent Pinchart a écrit :
> > > On Thu, Mar 11, 2021 at 05:47:59PM +0530, Umang Jain wrote:
> > > > On 3/11/21 5:29 PM, Laurent Pinchart wrote:
> > > > > On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> > > > > > On 3/11/21 3:03 PM, Marian Cichy wrote:
> > > > > > > If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails,
> > > > > > > the
> > > > > > > unique_ptr to the request-object gets reset and hence, its
> > > > > > > destructor
> > > > > > > is called. However, the wrap-object points to the same object and
> > > > > > > is
> > > > > > > still alive at this moment. When the task_run-function is
> > > > > > > finished, the
> > > > > > > destructor of the wrap-object is called, which in return calls the
> > > > > > > destructor of the request-object again.
> > > > > > > 
> > > > > > > Instead of taking care of both, the request and the wrap-object,
> > > > > > > we can
> > > > > > > move the request to the wrap which will then effectively take care
> > > > > > > of
> > > > > > > the request object automatically.
> > > > > > 
> > > > > > ...take care of the request s/object/object-deletion/ maybe?
> > > > > > 
> > > > > > > Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> > > > > > > Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > > ---
> > > > > > >    src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++------------
> > > > > > > ----
> > > > > > >    1 file changed, 12 insertions(+), 16 deletions(-)
> > > > > > > 
> > > > > > > diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > > b/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > > index 636c14df..e86c3d7f 100644
> > > > > > > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > > > > > > @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> > > > > > >    #define GST_CAT_DEFAULT source_debug
> > > > > > >    
> > > > > > >    struct RequestWrap {
> > > > > > > -       RequestWrap(Request *request);
> > > > > > > +       RequestWrap(std::unique_ptr<Request> request);
> > > > > > >         ~RequestWrap();
> > > > > > >    
> > > > > > >         void attachBuffer(GstBuffer *buffer);
> > > > > > >         GstBuffer *detachBuffer(Stream *stream);
> > > > > > >    
> > > > > > >         /* For ptr comparison only. */
> > > > > > 
> > > > > > I think you need to remove this comment too
> > > > > 
> > > > > Indeed :-)
> > > > > 
> > > > > > > -       Request *request_;
> > > > > > > +       std::unique_ptr<Request> request_;
> > > > > > >         std::map<Stream *, GstBuffer *> buffers_;
> > > > > > >    };
> > > > > > >    
> > > > > > > -RequestWrap::RequestWrap(Request *request)
> > > > > > > -       : request_(request)
> > > > > > > +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> > > > > > > +       : request_(std::move(request))
> > > > > > >    {
> > > > > > >    }
> > > > > > >    
> > > > > > > @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> > > > > > >                 if (item.second)
> > > > > > >                         gst_buffer_unref(item.second);
> > > > > > >         }
> > > > > > > -
> > > > > > > -       delete request_;
> > > > > > >    }
> > > > > > >    
> > > > > > >    void RequestWrap::attachBuffer(GstBuffer *buffer)
> > > > > > > @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request
> > > > > > > *request)
> > > > > > >         std::unique_ptr<RequestWrap> wrap =
> > > > > > > std::move(requests_.front());
> > > > > > >         requests_.pop();
> > > > > > >    
> > > > > > > -       g_return_if_fail(wrap->request_ == request);
> > > > > > > +       g_return_if_fail(wrap->request_.get() == request);
> > > > > > >    
> > > > > > >         if ((request->status() == Request::RequestCancelled)) {
> > > > > > >                 GST_DEBUG_OBJECT(src_, "Request was cancelled");
> > > > > > > @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> > > > > > >         GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> > > > > > >         GstLibcameraSrcState *state = self->state;
> > > > > > >    
> > > > > > > -       std::unique_ptr<Request> request = state->cam_-
> > > > > > > >createRequest();
> > > > > > > -       auto wrap = std::make_unique<RequestWrap>(request.get());
> > > > > > > +       auto wrap = std::make_unique<RequestWrap>(state->cam_-
> > > > > > > >createRequest());
> > > > > 
> > > > > > *1 for marker below
> > > > > > 
> > > > > > >         for (GstPad *srcpad : state->srcpads_) {
> > > > > > >                 GstLibcameraPool *pool =
> > > > > > > gst_libcamera_pad_get_pool(srcpad);
> > > > > > >                 GstBuffer *buffer;
> > > > > > > @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer
> > > > > > > user_data)
> > > > > > >                                                      &buffer,
> > > > > > > nullptr);
> > > > > > >                 if (ret != GST_FLOW_OK) {
> > > > > > >                         /*
> > > > > > > -                        * RequestWrap does not take ownership,
> > > > > > > and we won't be
> > > > > > > -                        * queueing this one due to lack of
> > > > > > > buffers.
> > > > > > > +                        * RequestWrap has ownership of the
> > > > > > > rquest, and we
> > > > > > 
> > > > > > s/rquest/request/
> > > > > > 
> > > > > > > +                        * won't be queueing this one due to lack
> > > > > > > of buffers.
> > > > > > >                          */
> > > > > > > -                       request.reset();
> > > > > > > +                       wrap.release();
> > > > > > >                         break;
> > > > > > >                 }
> > > > > > >    
> > > > > > >                 wrap->attachBuffer(buffer);
> > > > > > >         }
> > > > > > >    
> > > > > > > -       if (request) {
> > > > > > > +       if (wrap) {
> > > > > > 
> > > > > > I think this should be `if (wrap.get())` no? `wrap.release()` if
> > > > > > hit,
> > > > > > will set nullptr to the _managed_ object, so we need to check the
> > > > > > managed object for null
> > > > > 
> > > > > This is what is happening already:
> > > > > https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
> > > > > 
> > > > > > >                 GLibLocker lock(GST_OBJECT(self));
> > > > > > >                 GST_TRACE_OBJECT(self, "Requesting buffers");
> > > > > > > -               state->cam_->queueRequest(request.get());
> > > > > > > +               state->cam_->queueRequest(wrap->request_.get());
> > > > > > 
> > > > > > from *1, createRequest() call can return nullptr, hence you have a
> > > > > > 
> > > > > > std::make_unique<RequestWrap>(nullptr);
> > > > > > 
> > > > > > which will set request_ as nullptr on the wrap object. It  is fine
> > > > > > as
> > > > > > long as wrap->request_ is not used unlike here, in this block.
> > > > > 
> > > > > That's indeed a problem. Maybe
> > > > > 
> > > > >         if (wrap && wrap->request_)
> > > > > 
> > > > > above ? Nicolas, any opinion ?
> > > > 
> > > > OR null-check createRequest()'s returned object before wrapping it (i.e.
> > > > ensure RequestWrap doesn't wrap a nullptr from the start), so we don't 
> > > > have to check everything for the wrap->request_ is valid or not.
> > > 
> > > But, as far as I understand, code further down this function still have
> > > to run in that case, we can't just do an early return using a null
> > > check. That's why I've asked for Nicolas' feedback, he knows this code
> > > much better than I do.
> > 
> > Hmm, right. Something like this should do:
> > 
> > if (!request) {
> >         GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,...;
> >         gst_task_stop(task);
> >         return;
> > }
> 
> Something along those lines ?
> 
> commit be081f85bbab0c8b3b5f08d73c5bda504be7f2e6
> Author: Marian Cichy <m.cichy@pengutronix.de>
> Date:   Thu Mar 11 10:33:25 2021 +0100
> 
>     libcamera: gst: Fix double-free when acquire_buffer fails
>     
>     If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
>     unique_ptr to the request-object gets reset and hence, its destructor
>     is called. However, the wrap-object points to the same object and is
>     still alive at this moment. When the task_run-function is finished, the
>     destructor of the wrap-object is called, which in return calls the
>     destructor of the request-object again.
>     
>     Instead of taking care of both, the request and the wrap-object, we can
>     move the request to the wrap which will then effectively take care of
>     the request object automatically.
>     
>     Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
>     Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>     Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> b/src/gstreamer/gstlibcamerasrc.cpp
> index 636c14dff64e..7b9671200f87 100644
> --- a/src/gstreamer/gstlibcamerasrc.cpp
> +++ b/src/gstreamer/gstlibcamerasrc.cpp
> @@ -52,19 +52,18 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
>  #define GST_CAT_DEFAULT source_debug
>  
>  struct RequestWrap {
> -       RequestWrap(Request *request);
> +       RequestWrap(std::unique_ptr<Request> request);
>         ~RequestWrap();
>  
>         void attachBuffer(GstBuffer *buffer);
>         GstBuffer *detachBuffer(Stream *stream);
>  
> -       /* For ptr comparison only. */
> -       Request *request_;
> +       std::unique_ptr<Request> request_;
>         std::map<Stream *, GstBuffer *> buffers_;
>  };
>  
> -RequestWrap::RequestWrap(Request *request)
> -       : request_(request)
> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> +       : request_(std::move(request))
>  {
>  }
>  
> @@ -74,8 +73,6 @@ RequestWrap::~RequestWrap()
>                 if (item.second)
>                         gst_buffer_unref(item.second);
>         }
> -
> -       delete request_;
>  }
>  
>  void RequestWrap::attachBuffer(GstBuffer *buffer)
> @@ -164,7 +161,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
>         std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
>         requests_.pop();
>  
> -       g_return_if_fail(wrap->request_ == request);
> +       g_return_if_fail(wrap->request_.get() == request);
>  
>         if ((request->status() == Request::RequestCancelled)) {
>                 GST_DEBUG_OBJECT(src_, "Request was cancelled");
> @@ -269,7 +266,18 @@ gst_libcamera_src_task_run(gpointer user_data)
>         GstLibcameraSrcState *state = self->state;
>  
>         std::unique_ptr<Request> request = state->cam_->createRequest();
> -       auto wrap = std::make_unique<RequestWrap>(request.get());
> +       if (!request) {
> +               GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
> +                                 ("Failed to allocate request for camera
> '%s'.",
> +                                  state->cam_->id().c_str()),
> +                                 ("libcamera::Camera::createRequest()
> failed"));
> +               gst_task_stop(self->task);
> +               return;
> +       }
> +
> +       std::unique_ptr<RequestWrap> wrap =
> +               std::make_unique<RequestWrap>(std::move(request));
> +
>         for (GstPad *srcpad : state->srcpads_) {
>                 GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
>                 GstBuffer *buffer;
> @@ -279,24 +287,23 @@ gst_libcamera_src_task_run(gpointer user_data)
>                                                      &buffer, nullptr);
>                 if (ret != GST_FLOW_OK) {
>                         /*
> -                        * RequestWrap does not take ownership, and we won't
> be
> -                        * queueing this one due to lack of buffers.
> +                        * RequestWrap has ownership of the rquest, and we
                                                              request
> +                        * won't be queueing this one due to lack of buffers.
>                          */
> -                       request.reset();
> +                       wrap.release();
>                         break;
>                 }
>  
>                 wrap->attachBuffer(buffer);
>         }
>  
> -       if (request) {
> +       if (wrap) {
>                 GLibLocker lock(GST_OBJECT(self));
>                 GST_TRACE_OBJECT(self, "Requesting buffers");
> -               state->cam_->queueRequest(request.get());
> +               state->cam_->queueRequest(wrap->request_.get());
>                 state->requests_.push(std::move(wrap));
>  
> -               /* The request will be deleted in the completion handler. */
> -               request.release();
> +               /* The RequestWrap will be deleted in the completion handler.
> */
>         }
>  
>         GstFlowReturn ret = GST_FLOW_OK;

Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>

> 
> Marian, would you be able to test this ?
> 
> Should the same be done in case wrap becomes null, due to a call to
> gst_buffer_pool_acquire_buffer() failing, insted of continuing to
> processing the flow ? The lack of symmetry between the two codes paths
> bothers me, either because there's an issue there, or because I don't
> understand why they need to be different :-) This can be addressed in a
> subsequent patch, as it's not an issue introduced by this patch.

I think this code is a bit incomplete, specially since it supports multiple
pads, while you cannot yet request a second pad. In general, my idea was that
we'd try and ensure we have at least one pending request per stream/pad before
pausing the task. Pausing the task, which just mean our task function will stop
being called repeatedly until gst_libcamera_resume_task() is called.

For the case we run-out of buffer in our pool (which is not as dramatic as
failing to create a request), we should only stop / fail if there is no pending
request already queued for that pad. I'll take care of this one, it also need a
locking fix, and reversing the pending condition check lower (which is upside
down).

> 
> > > > > > >                 state->requests_.push(std::move(wrap));
> > > > > > >    
> > > > > > > -               /* The request will be deleted in the completion
> > > > > > > handler. */
> > > > > > > -               request.release();
> > > > > > > +               /* The RequestWrap will be deleted in the
> > > > > > > completion handler. */
> > > > > > >         }
> > > > > > >    
> > > > > > >         GstFlowReturn ret = GST_FLOW_OK;
>
Marian Cichy March 12, 2021, 5:59 p.m. UTC | #10
On 3/12/21 12:14 AM, Laurent Pinchart wrote:
> Hi Nicolas,
>
> On Thu, Mar 11, 2021 at 04:02:20PM -0500, Nicolas Dufresne wrote:
>> Le jeudi 11 mars 2021 à 14:22 +0200, Laurent Pinchart a écrit :
>>> On Thu, Mar 11, 2021 at 05:47:59PM +0530, Umang Jain wrote:
>>>> On 3/11/21 5:29 PM, Laurent Pinchart wrote:
>>>>> On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
>>>>>> On 3/11/21 3:03 PM, Marian Cichy wrote:
>>>>>>> If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
>>>>>>> unique_ptr to the request-object gets reset and hence, its destructor
>>>>>>> is called. However, the wrap-object points to the same object and is
>>>>>>> still alive at this moment. When the task_run-function is finished, the
>>>>>>> destructor of the wrap-object is called, which in return calls the
>>>>>>> destructor of the request-object again.
>>>>>>>
>>>>>>> Instead of taking care of both, the request and the wrap-object, we can
>>>>>>> move the request to the wrap which will then effectively take care of
>>>>>>> the request object automatically.
>>>>>> ...take care of the request s/object/object-deletion/ maybe?
>>>>>>
>>>>>>> Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
>>>>>>> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>> ---
>>>>>>>     src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
>>>>>>>     1 file changed, 12 insertions(+), 16 deletions(-)
>>>>>>>
>>>>>>> diff --git a/src/gstreamer/gstlibcamerasrc.cpp
>>>>>>> b/src/gstreamer/gstlibcamerasrc.cpp
>>>>>>> index 636c14df..e86c3d7f 100644
>>>>>>> --- a/src/gstreamer/gstlibcamerasrc.cpp
>>>>>>> +++ b/src/gstreamer/gstlibcamerasrc.cpp
>>>>>>> @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
>>>>>>>     #define GST_CAT_DEFAULT source_debug
>>>>>>>     
>>>>>>>     struct RequestWrap {
>>>>>>> -       RequestWrap(Request *request);
>>>>>>> +       RequestWrap(std::unique_ptr<Request> request);
>>>>>>>          ~RequestWrap();
>>>>>>>     
>>>>>>>          void attachBuffer(GstBuffer *buffer);
>>>>>>>          GstBuffer *detachBuffer(Stream *stream);
>>>>>>>     
>>>>>>>          /* For ptr comparison only. */
>>>>>> I think you need to remove this comment too
>>>>> Indeed :-)
>>>>>
>>>>>>> -       Request *request_;
>>>>>>> +       std::unique_ptr<Request> request_;
>>>>>>>          std::map<Stream *, GstBuffer *> buffers_;
>>>>>>>     };
>>>>>>>     
>>>>>>> -RequestWrap::RequestWrap(Request *request)
>>>>>>> -       : request_(request)
>>>>>>> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
>>>>>>> +       : request_(std::move(request))
>>>>>>>     {
>>>>>>>     }
>>>>>>>     
>>>>>>> @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
>>>>>>>                  if (item.second)
>>>>>>>                          gst_buffer_unref(item.second);
>>>>>>>          }
>>>>>>> -
>>>>>>> -       delete request_;
>>>>>>>     }
>>>>>>>     
>>>>>>>     void RequestWrap::attachBuffer(GstBuffer *buffer)
>>>>>>> @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
>>>>>>>          std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
>>>>>>>          requests_.pop();
>>>>>>>     
>>>>>>> -       g_return_if_fail(wrap->request_ == request);
>>>>>>> +       g_return_if_fail(wrap->request_.get() == request);
>>>>>>>     
>>>>>>>          if ((request->status() == Request::RequestCancelled)) {
>>>>>>>                  GST_DEBUG_OBJECT(src_, "Request was cancelled");
>>>>>>> @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
>>>>>>>          GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
>>>>>>>          GstLibcameraSrcState *state = self->state;
>>>>>>>     
>>>>>>> -       std::unique_ptr<Request> request = state->cam_->createRequest();
>>>>>>> -       auto wrap = std::make_unique<RequestWrap>(request.get());
>>>>>>> +       auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
>>>>>> *1 for marker below
>>>>>>
>>>>>>>          for (GstPad *srcpad : state->srcpads_) {
>>>>>>>                  GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
>>>>>>>                  GstBuffer *buffer;
>>>>>>> @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
>>>>>>>                                                       &buffer, nullptr);
>>>>>>>                  if (ret != GST_FLOW_OK) {
>>>>>>>                          /*
>>>>>>> -                        * RequestWrap does not take ownership, and we won't be
>>>>>>> -                        * queueing this one due to lack of buffers.
>>>>>>> +                        * RequestWrap has ownership of the rquest, and we
>>>>>> s/rquest/request/
>>>>>>
>>>>>>> +                        * won't be queueing this one due to lack of buffers.
>>>>>>>                           */
>>>>>>> -                       request.reset();
>>>>>>> +                       wrap.release();
>>>>>>>                          break;
>>>>>>>                  }
>>>>>>>     
>>>>>>>                  wrap->attachBuffer(buffer);
>>>>>>>          }
>>>>>>>     
>>>>>>> -       if (request) {
>>>>>>> +       if (wrap) {
>>>>>> I think this should be `if (wrap.get())` no? `wrap.release()` if hit,
>>>>>> will set nullptr to the _managed_ object, so we need to check the
>>>>>> managed object for null
>>>>> This is what is happening already:
>>>>> https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
>>>>>
>>>>>>>                  GLibLocker lock(GST_OBJECT(self));
>>>>>>>                  GST_TRACE_OBJECT(self, "Requesting buffers");
>>>>>>> -               state->cam_->queueRequest(request.get());
>>>>>>> +               state->cam_->queueRequest(wrap->request_.get());
>>>>>> from *1, createRequest() call can return nullptr, hence you have a
>>>>>>
>>>>>> std::make_unique<RequestWrap>(nullptr);
>>>>>>
>>>>>> which will set request_ as nullptr on the wrap object. It  is fine as
>>>>>> long as wrap->request_ is not used unlike here, in this block.
>>>>> That's indeed a problem. Maybe
>>>>>
>>>>>          if (wrap && wrap->request_)
>>>>>
>>>>> above ? Nicolas, any opinion ?
>>>> OR null-check createRequest()'s returned object before wrapping it (i.e.
>>>> ensure RequestWrap doesn't wrap a nullptr from the start), so we don't
>>>> have to check everything for the wrap->request_ is valid or not.
>>> But, as far as I understand, code further down this function still have
>>> to run in that case, we can't just do an early return using a null
>>> check. That's why I've asked for Nicolas' feedback, he knows this code
>>> much better than I do.
>> Hmm, right. Something like this should do:
>>
>> if (!request) {
>> 	GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,...;
>>          gst_task_stop(task);
>> 	return;
>> }
> Something along those lines ?
>
> commit be081f85bbab0c8b3b5f08d73c5bda504be7f2e6
> Author: Marian Cichy <m.cichy@pengutronix.de>
> Date:   Thu Mar 11 10:33:25 2021 +0100
>
>      libcamera: gst: Fix double-free when acquire_buffer fails
>      
>      If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
>      unique_ptr to the request-object gets reset and hence, its destructor
>      is called. However, the wrap-object points to the same object and is
>      still alive at this moment. When the task_run-function is finished, the
>      destructor of the wrap-object is called, which in return calls the
>      destructor of the request-object again.
>      
>      Instead of taking care of both, the request and the wrap-object, we can
>      move the request to the wrap which will then effectively take care of
>      the request object automatically.
>      
>      Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
>      Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>      Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>
> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> index 636c14dff64e..7b9671200f87 100644
> --- a/src/gstreamer/gstlibcamerasrc.cpp
> +++ b/src/gstreamer/gstlibcamerasrc.cpp
> @@ -52,19 +52,18 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
>   #define GST_CAT_DEFAULT source_debug
>   
>   struct RequestWrap {
> -	RequestWrap(Request *request);
> +	RequestWrap(std::unique_ptr<Request> request);
>   	~RequestWrap();
>   
>   	void attachBuffer(GstBuffer *buffer);
>   	GstBuffer *detachBuffer(Stream *stream);
>   
> -	/* For ptr comparison only. */
> -	Request *request_;
> +	std::unique_ptr<Request> request_;
>   	std::map<Stream *, GstBuffer *> buffers_;
>   };
>   
> -RequestWrap::RequestWrap(Request *request)
> -	: request_(request)
> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> +	: request_(std::move(request))
>   {
>   }
>   
> @@ -74,8 +73,6 @@ RequestWrap::~RequestWrap()
>   		if (item.second)
>   			gst_buffer_unref(item.second);
>   	}
> -
> -	delete request_;
>   }
>   
>   void RequestWrap::attachBuffer(GstBuffer *buffer)
> @@ -164,7 +161,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
>   	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
>   	requests_.pop();
>   
> -	g_return_if_fail(wrap->request_ == request);
> +	g_return_if_fail(wrap->request_.get() == request);
>   
>   	if ((request->status() == Request::RequestCancelled)) {
>   		GST_DEBUG_OBJECT(src_, "Request was cancelled");
> @@ -269,7 +266,18 @@ gst_libcamera_src_task_run(gpointer user_data)
>   	GstLibcameraSrcState *state = self->state;
>   
>   	std::unique_ptr<Request> request = state->cam_->createRequest();
> -	auto wrap = std::make_unique<RequestWrap>(request.get());
> +	if (!request) {
> +		GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
> +				  ("Failed to allocate request for camera '%s'.",
> +				   state->cam_->id().c_str()),
> +				  ("libcamera::Camera::createRequest() failed"));
> +		gst_task_stop(self->task);
> +		return;
> +	}
> +
> +	std::unique_ptr<RequestWrap> wrap =
> +		std::make_unique<RequestWrap>(std::move(request));
> +
>   	for (GstPad *srcpad : state->srcpads_) {
>   		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
>   		GstBuffer *buffer;
> @@ -279,24 +287,23 @@ gst_libcamera_src_task_run(gpointer user_data)
>   						     &buffer, nullptr);
>   		if (ret != GST_FLOW_OK) {
>   			/*
> -			 * RequestWrap does not take ownership, and we won't be
> -			 * queueing this one due to lack of buffers.
> +			 * RequestWrap has ownership of the rquest, and we
> +			 * won't be queueing this one due to lack of buffers.
>   			 */
> -			request.reset();
> +			wrap.release();
>   			break;
>   		}
>   
>   		wrap->attachBuffer(buffer);
>   	}
>   
> -	if (request) {
> +	if (wrap) {
>   		GLibLocker lock(GST_OBJECT(self));
>   		GST_TRACE_OBJECT(self, "Requesting buffers");
> -		state->cam_->queueRequest(request.get());
> +		state->cam_->queueRequest(wrap->request_.get());
>   		state->requests_.push(std::move(wrap));
>   
> -		/* The request will be deleted in the completion handler. */
> -		request.release();
> +		/* The RequestWrap will be deleted in the completion handler. */
>   	}
>   
>   	GstFlowReturn ret = GST_FLOW_OK;
>
> Marian, would you be able to test this ?
>
> Should the same be done in case wrap becomes null, due to a call to
> gst_buffer_pool_acquire_buffer() failing, insted of continuing to
> processing the flow ? The lack of symmetry between the two codes paths
> bothers me, either because there's an issue there, or because I don't
> understand why they need to be different :-) This can be addressed in a
> subsequent patch, as it's not an issue introduced by this patch.


Hi Laurent,

I tested this, using:


@@ -267,6 +265,7 @@ gst_libcamera_src_task_run(gpointer user_data)
  {
         GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
         GstLibcameraSrcState *state = self->state;
+       static int cnt = 0;

         std::unique_ptr<Request> request = state->cam_->createRequest();
         auto wrap = std::make_unique<RequestWrap>(request.get());
@@ -275,7 +274,10 @@ gst_libcamera_src_task_run(gpointer user_data)
                 GstBuffer *buffer;
                 GstFlowReturn ret;

-               ret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),
+               if (++cnt == 10)
+                       ret = GST_FLOW_ERROR;
+               else
+                       ret = 
gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),


as expected, the code skips the failing buffer, than continues it works, 
no problems whatsoever. So it's fine.

I see there is still ongoing discussion with you and Nicolas, so I'd say 
I wait before sending V3 until it's clear?

Marian


>>>>>>>                  state->requests_.push(std::move(wrap));
>>>>>>>     
>>>>>>> -               /* The request will be deleted in the completion handler. */
>>>>>>> -               request.release();
>>>>>>> +               /* The RequestWrap will be deleted in the completion handler. */
>>>>>>>          }
>>>>>>>     
>>>>>>>          GstFlowReturn ret = GST_FLOW_OK;
Laurent Pinchart March 12, 2021, 8:56 p.m. UTC | #11
Hi Marian,

On Fri, Mar 12, 2021 at 06:59:11PM +0100, Marian Cichy wrote:
> On 3/12/21 12:14 AM, Laurent Pinchart wrote:
> > On Thu, Mar 11, 2021 at 04:02:20PM -0500, Nicolas Dufresne wrote:
> >> Le jeudi 11 mars 2021 à 14:22 +0200, Laurent Pinchart a écrit :
> >>> On Thu, Mar 11, 2021 at 05:47:59PM +0530, Umang Jain wrote:
> >>>> On 3/11/21 5:29 PM, Laurent Pinchart wrote:
> >>>>> On Thu, Mar 11, 2021 at 05:05:39PM +0530, Umang Jain wrote:
> >>>>>> On 3/11/21 3:03 PM, Marian Cichy wrote:
> >>>>>>> If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> >>>>>>> unique_ptr to the request-object gets reset and hence, its destructor
> >>>>>>> is called. However, the wrap-object points to the same object and is
> >>>>>>> still alive at this moment. When the task_run-function is finished, the
> >>>>>>> destructor of the wrap-object is called, which in return calls the
> >>>>>>> destructor of the request-object again.
> >>>>>>>
> >>>>>>> Instead of taking care of both, the request and the wrap-object, we can
> >>>>>>> move the request to the wrap which will then effectively take care of
> >>>>>>> the request object automatically.
> >>>>>> ...take care of the request s/object/object-deletion/ maybe?
> >>>>>>
> >>>>>>> Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> >>>>>>> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>> ---
> >>>>>>>     src/gstreamer/gstlibcamerasrc.cpp | 28 ++++++++++++----------------
> >>>>>>>     1 file changed, 12 insertions(+), 16 deletions(-)
> >>>>>>>
> >>>>>>> diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> >>>>>>> b/src/gstreamer/gstlibcamerasrc.cpp
> >>>>>>> index 636c14df..e86c3d7f 100644
> >>>>>>> --- a/src/gstreamer/gstlibcamerasrc.cpp
> >>>>>>> +++ b/src/gstreamer/gstlibcamerasrc.cpp
> >>>>>>> @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> >>>>>>>     #define GST_CAT_DEFAULT source_debug
> >>>>>>>     
> >>>>>>>     struct RequestWrap {
> >>>>>>> -       RequestWrap(Request *request);
> >>>>>>> +       RequestWrap(std::unique_ptr<Request> request);
> >>>>>>>          ~RequestWrap();
> >>>>>>>     
> >>>>>>>          void attachBuffer(GstBuffer *buffer);
> >>>>>>>          GstBuffer *detachBuffer(Stream *stream);
> >>>>>>>     
> >>>>>>>          /* For ptr comparison only. */
> >>>>>> I think you need to remove this comment too
> >>>>> Indeed :-)
> >>>>>
> >>>>>>> -       Request *request_;
> >>>>>>> +       std::unique_ptr<Request> request_;
> >>>>>>>          std::map<Stream *, GstBuffer *> buffers_;
> >>>>>>>     };
> >>>>>>>     
> >>>>>>> -RequestWrap::RequestWrap(Request *request)
> >>>>>>> -       : request_(request)
> >>>>>>> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> >>>>>>> +       : request_(std::move(request))
> >>>>>>>     {
> >>>>>>>     }
> >>>>>>>     
> >>>>>>> @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
> >>>>>>>                  if (item.second)
> >>>>>>>                          gst_buffer_unref(item.second);
> >>>>>>>          }
> >>>>>>> -
> >>>>>>> -       delete request_;
> >>>>>>>     }
> >>>>>>>     
> >>>>>>>     void RequestWrap::attachBuffer(GstBuffer *buffer)
> >>>>>>> @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
> >>>>>>>          std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
> >>>>>>>          requests_.pop();
> >>>>>>>     
> >>>>>>> -       g_return_if_fail(wrap->request_ == request);
> >>>>>>> +       g_return_if_fail(wrap->request_.get() == request);
> >>>>>>>     
> >>>>>>>          if ((request->status() == Request::RequestCancelled)) {
> >>>>>>>                  GST_DEBUG_OBJECT(src_, "Request was cancelled");
> >>>>>>> @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
> >>>>>>>          GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
> >>>>>>>          GstLibcameraSrcState *state = self->state;
> >>>>>>>     
> >>>>>>> -       std::unique_ptr<Request> request = state->cam_->createRequest();
> >>>>>>> -       auto wrap = std::make_unique<RequestWrap>(request.get());
> >>>>>>> +       auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
> >>>>>> *1 for marker below
> >>>>>>
> >>>>>>>          for (GstPad *srcpad : state->srcpads_) {
> >>>>>>>                  GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
> >>>>>>>                  GstBuffer *buffer;
> >>>>>>> @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> >>>>>>>                                                       &buffer, nullptr);
> >>>>>>>                  if (ret != GST_FLOW_OK) {
> >>>>>>>                          /*
> >>>>>>> -                        * RequestWrap does not take ownership, and we won't be
> >>>>>>> -                        * queueing this one due to lack of buffers.
> >>>>>>> +                        * RequestWrap has ownership of the rquest, and we
> >>>>>> s/rquest/request/
> >>>>>>
> >>>>>>> +                        * won't be queueing this one due to lack of buffers.
> >>>>>>>                           */
> >>>>>>> -                       request.reset();
> >>>>>>> +                       wrap.release();
> >>>>>>>                          break;
> >>>>>>>                  }
> >>>>>>>     
> >>>>>>>                  wrap->attachBuffer(buffer);
> >>>>>>>          }
> >>>>>>>     
> >>>>>>> -       if (request) {
> >>>>>>> +       if (wrap) {
> >>>>>> I think this should be `if (wrap.get())` no? `wrap.release()` if hit,
> >>>>>> will set nullptr to the _managed_ object, so we need to check the
> >>>>>> managed object for null
> >>>>> This is what is happening already:
> >>>>> https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
> >>>>>
> >>>>>>>                  GLibLocker lock(GST_OBJECT(self));
> >>>>>>>                  GST_TRACE_OBJECT(self, "Requesting buffers");
> >>>>>>> -               state->cam_->queueRequest(request.get());
> >>>>>>> +               state->cam_->queueRequest(wrap->request_.get());
> >>>>>> from *1, createRequest() call can return nullptr, hence you have a
> >>>>>>
> >>>>>> std::make_unique<RequestWrap>(nullptr);
> >>>>>>
> >>>>>> which will set request_ as nullptr on the wrap object. It  is fine as
> >>>>>> long as wrap->request_ is not used unlike here, in this block.
> >>>>> That's indeed a problem. Maybe
> >>>>>
> >>>>>          if (wrap && wrap->request_)
> >>>>>
> >>>>> above ? Nicolas, any opinion ?
> >>>> OR null-check createRequest()'s returned object before wrapping it (i.e.
> >>>> ensure RequestWrap doesn't wrap a nullptr from the start), so we don't
> >>>> have to check everything for the wrap->request_ is valid or not.
> >>> But, as far as I understand, code further down this function still have
> >>> to run in that case, we can't just do an early return using a null
> >>> check. That's why I've asked for Nicolas' feedback, he knows this code
> >>> much better than I do.
> >> Hmm, right. Something like this should do:
> >>
> >> if (!request) {
> >> 	GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,...;
> >>          gst_task_stop(task);
> >> 	return;
> >> }
> > Something along those lines ?
> >
> > commit be081f85bbab0c8b3b5f08d73c5bda504be7f2e6
> > Author: Marian Cichy <m.cichy@pengutronix.de>
> > Date:   Thu Mar 11 10:33:25 2021 +0100
> >
> >      libcamera: gst: Fix double-free when acquire_buffer fails
> >      
> >      If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
> >      unique_ptr to the request-object gets reset and hence, its destructor
> >      is called. However, the wrap-object points to the same object and is
> >      still alive at this moment. When the task_run-function is finished, the
> >      destructor of the wrap-object is called, which in return calls the
> >      destructor of the request-object again.
> >      
> >      Instead of taking care of both, the request and the wrap-object, we can
> >      move the request to the wrap which will then effectively take care of
> >      the request object automatically.
> >      
> >      Signed-off-by: Marian Cichy <m.cichy@pengutronix.de>
> >      Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >      Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >
> > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> > index 636c14dff64e..7b9671200f87 100644
> > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > @@ -52,19 +52,18 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
> >   #define GST_CAT_DEFAULT source_debug
> >   
> >   struct RequestWrap {
> > -	RequestWrap(Request *request);
> > +	RequestWrap(std::unique_ptr<Request> request);
> >   	~RequestWrap();
> >   
> >   	void attachBuffer(GstBuffer *buffer);
> >   	GstBuffer *detachBuffer(Stream *stream);
> >   
> > -	/* For ptr comparison only. */
> > -	Request *request_;
> > +	std::unique_ptr<Request> request_;
> >   	std::map<Stream *, GstBuffer *> buffers_;
> >   };
> >   
> > -RequestWrap::RequestWrap(Request *request)
> > -	: request_(request)
> > +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
> > +	: request_(std::move(request))
> >   {
> >   }
> >   
> > @@ -74,8 +73,6 @@ RequestWrap::~RequestWrap()
> >   		if (item.second)
> >   			gst_buffer_unref(item.second);
> >   	}
> > -
> > -	delete request_;
> >   }
> >   
> >   void RequestWrap::attachBuffer(GstBuffer *buffer)
> > @@ -164,7 +161,7 @@ GstLibcameraSrcState::requestCompleted(Request *request)
> >   	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
> >   	requests_.pop();
> >   
> > -	g_return_if_fail(wrap->request_ == request);
> > +	g_return_if_fail(wrap->request_.get() == request);
> >   
> >   	if ((request->status() == Request::RequestCancelled)) {
> >   		GST_DEBUG_OBJECT(src_, "Request was cancelled");
> > @@ -269,7 +266,18 @@ gst_libcamera_src_task_run(gpointer user_data)
> >   	GstLibcameraSrcState *state = self->state;
> >   
> >   	std::unique_ptr<Request> request = state->cam_->createRequest();
> > -	auto wrap = std::make_unique<RequestWrap>(request.get());
> > +	if (!request) {
> > +		GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
> > +				  ("Failed to allocate request for camera '%s'.",
> > +				   state->cam_->id().c_str()),
> > +				  ("libcamera::Camera::createRequest() failed"));
> > +		gst_task_stop(self->task);
> > +		return;
> > +	}
> > +
> > +	std::unique_ptr<RequestWrap> wrap =
> > +		std::make_unique<RequestWrap>(std::move(request));
> > +
> >   	for (GstPad *srcpad : state->srcpads_) {
> >   		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
> >   		GstBuffer *buffer;
> > @@ -279,24 +287,23 @@ gst_libcamera_src_task_run(gpointer user_data)
> >   						     &buffer, nullptr);
> >   		if (ret != GST_FLOW_OK) {
> >   			/*
> > -			 * RequestWrap does not take ownership, and we won't be
> > -			 * queueing this one due to lack of buffers.
> > +			 * RequestWrap has ownership of the rquest, and we
> > +			 * won't be queueing this one due to lack of buffers.
> >   			 */
> > -			request.reset();
> > +			wrap.release();
> >   			break;
> >   		}
> >   
> >   		wrap->attachBuffer(buffer);
> >   	}
> >   
> > -	if (request) {
> > +	if (wrap) {
> >   		GLibLocker lock(GST_OBJECT(self));
> >   		GST_TRACE_OBJECT(self, "Requesting buffers");
> > -		state->cam_->queueRequest(request.get());
> > +		state->cam_->queueRequest(wrap->request_.get());
> >   		state->requests_.push(std::move(wrap));
> >   
> > -		/* The request will be deleted in the completion handler. */
> > -		request.release();
> > +		/* The RequestWrap will be deleted in the completion handler. */
> >   	}
> >   
> >   	GstFlowReturn ret = GST_FLOW_OK;
> >
> > Marian, would you be able to test this ?
> >
> > Should the same be done in case wrap becomes null, due to a call to
> > gst_buffer_pool_acquire_buffer() failing, insted of continuing to
> > processing the flow ? The lack of symmetry between the two codes paths
> > bothers me, either because there's an issue there, or because I don't
> > understand why they need to be different :-) This can be addressed in a
> > subsequent patch, as it's not an issue introduced by this patch.
> 
> 
> Hi Laurent,
> 
> I tested this, using:
> 
> 
> @@ -267,6 +265,7 @@ gst_libcamera_src_task_run(gpointer user_data)
>   {
>          GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
>          GstLibcameraSrcState *state = self->state;
> +       static int cnt = 0;
> 
>          std::unique_ptr<Request> request = state->cam_->createRequest();
>          auto wrap = std::make_unique<RequestWrap>(request.get());
> @@ -275,7 +274,10 @@ gst_libcamera_src_task_run(gpointer user_data)
>                  GstBuffer *buffer;
>                  GstFlowReturn ret;
> 
> -               ret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),
> +               if (++cnt == 10)
> +                       ret = GST_FLOW_ERROR;
> +               else
> +                       ret = 
> gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool),
> 
> 
> as expected, the code skips the failing buffer, than continues it works, 
> no problems whatsoever. So it's fine.
> 
> I see there is still ongoing discussion with you and Nicolas, so I'd say 
> I wait before sending V3 until it's clear?

Nicolas gave a Reviewed-by on this, so I think the other issues can be
addressed on top. I'll push this.

> >>>>>>>                  state->requests_.push(std::move(wrap));
> >>>>>>>     
> >>>>>>> -               /* The request will be deleted in the completion handler. */
> >>>>>>> -               request.release();
> >>>>>>> +               /* The RequestWrap will be deleted in the completion handler. */
> >>>>>>>          }
> >>>>>>>     
> >>>>>>>          GstFlowReturn ret = GST_FLOW_OK;

Patch
diff mbox series

diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 636c14df..e86c3d7f 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -52,19 +52,19 @@  GST_DEBUG_CATEGORY_STATIC(source_debug);
 #define GST_CAT_DEFAULT source_debug
 
 struct RequestWrap {
-	RequestWrap(Request *request);
+	RequestWrap(std::unique_ptr<Request> request);
 	~RequestWrap();
 
 	void attachBuffer(GstBuffer *buffer);
 	GstBuffer *detachBuffer(Stream *stream);
 
 	/* For ptr comparison only. */
-	Request *request_;
+	std::unique_ptr<Request> request_;
 	std::map<Stream *, GstBuffer *> buffers_;
 };
 
-RequestWrap::RequestWrap(Request *request)
-	: request_(request)
+RequestWrap::RequestWrap(std::unique_ptr<Request> request)
+	: request_(std::move(request))
 {
 }
 
@@ -74,8 +74,6 @@  RequestWrap::~RequestWrap()
 		if (item.second)
 			gst_buffer_unref(item.second);
 	}
-
-	delete request_;
 }
 
 void RequestWrap::attachBuffer(GstBuffer *buffer)
@@ -164,7 +162,7 @@  GstLibcameraSrcState::requestCompleted(Request *request)
 	std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
 	requests_.pop();
 
-	g_return_if_fail(wrap->request_ == request);
+	g_return_if_fail(wrap->request_.get() == request);
 
 	if ((request->status() == Request::RequestCancelled)) {
 		GST_DEBUG_OBJECT(src_, "Request was cancelled");
@@ -268,8 +266,7 @@  gst_libcamera_src_task_run(gpointer user_data)
 	GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
 	GstLibcameraSrcState *state = self->state;
 
-	std::unique_ptr<Request> request = state->cam_->createRequest();
-	auto wrap = std::make_unique<RequestWrap>(request.get());
+	auto wrap = std::make_unique<RequestWrap>(state->cam_->createRequest());
 	for (GstPad *srcpad : state->srcpads_) {
 		GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
 		GstBuffer *buffer;
@@ -279,24 +276,23 @@  gst_libcamera_src_task_run(gpointer user_data)
 						     &buffer, nullptr);
 		if (ret != GST_FLOW_OK) {
 			/*
-			 * RequestWrap does not take ownership, and we won't be
-			 * queueing this one due to lack of buffers.
+			 * RequestWrap has ownership of the rquest, and we
+			 * won't be queueing this one due to lack of buffers.
 			 */
-			request.reset();
+			wrap.release();
 			break;
 		}
 
 		wrap->attachBuffer(buffer);
 	}
 
-	if (request) {
+	if (wrap) {
 		GLibLocker lock(GST_OBJECT(self));
 		GST_TRACE_OBJECT(self, "Requesting buffers");
-		state->cam_->queueRequest(request.get());
+		state->cam_->queueRequest(wrap->request_.get());
 		state->requests_.push(std::move(wrap));
 
-		/* The request will be deleted in the completion handler. */
-		request.release();
+		/* The RequestWrap will be deleted in the completion handler. */
 	}
 
 	GstFlowReturn ret = GST_FLOW_OK;