[PATCH v2] libcamera: software_isp: Clean up pending requests on stop
Milan Zamazal
mzamazal at redhat.com
Fri Oct 18 11:29:24 CEST 2024
Milan Zamazal <mzamazal at redhat.com> writes:
> Laurent Pinchart <laurent.pinchart at ideasonboard.com> writes:
>
>> On Wed, Oct 09, 2024 at 09:43:42PM +0200, Milan Zamazal wrote:
>>> Laurent Pinchart writes:
>>> > On Wed, Oct 09, 2024 at 06:51:36PM +0200, Milan Zamazal wrote:
>>
>>> >> Laurent Pinchart writes:
>>> >> > On Wed, Oct 09, 2024 at 10:35:56AM +0200, Milan Zamazal wrote:
>>> >
>>> >> >> PipelineHandler::stop() calls stopDevice() method to perform pipeline
>>> >> >> specific cleanup and then completes waiting requests. If any queued
>>> >> >
>>> >> >> requests remain, an assertion error is raised.
>>> >> >>
>>> >> >> Software ISP stores request buffers in
>>> >> >> SimpleCameraData::conversionQueue_ and queues them as V4L2 signals
>>> >> >> bufferReady. stopDevice() cleanup forgets to clean up the buffers and
>>> >> >> their requests from conversionQueue_, possibly resulting in the
>>> >> >> assertion error.
>>> >> >>
>>> >> >> This patch fixes the omission. The request must be also canceled, which
>>> >> >> requires introducing a little PipelineHandler::cancelRequest helper in
>>> >> >> order to be able to access the private cancel() method.
>>> >> >>
>>> >> >> The problem wasn't very visible when
>>> >> >> SimplePipelineHandler::kNumInternalBuffers (the number of buffers
>>> >> >> allocated in V4L2) was equal to the number of buffers exported from
>>> >> >> software ISP. But when the number of the exported buffers was increased
>>> >> >> by one in commit abe2ec64f9e4e97bbdfe3a50372611bd7b5315c2, the assertion
>>> >> >> error started pop up in some environments. Increasing the number of the
>>> >> >> buffers much more, e.g. to 9, makes the problem very reproducible.
>>> >> >>
>>> >> >> Each pipeline uses its own mechanism to track the requests to clean up
>>> >> >> and it can't be excluded that similar omissions are present in other
>>> >> >> places. But there is no obvious way to make a common cleanup for all
>>> >> >> the pipelines (except for doing it instead of raising the assertion
>>> >> >> error, which is probably undesirable, in order not to hide incomplete
>>> >> >> pipeline specific cleanups).
>>> >> >>
>>> >> >> Bug: https://bugs.libcamera.org/show_bug.cgi?id=234
>>> >> >> Signed-off-by: Milan Zamazal <mzamazal at redhat.com>
>>> >> >> ---
>>> >> >> include/libcamera/internal/pipeline_handler.h | 1 +
>>> >> >> src/libcamera/pipeline/simple/simple.cpp | 14 +++++++++++
>>> >> >> src/libcamera/pipeline_handler.cpp | 23 +++++++++++++------
>>> >> >> 3 files changed, 31 insertions(+), 7 deletions(-)
>>> >> >>
>>> >> >> diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h
>>> >> >> index 0d3808036..fb28a18d0 100644
>>> >> >> --- a/include/libcamera/internal/pipeline_handler.h
>>> >> >> +++ b/include/libcamera/internal/pipeline_handler.h
>>> >> >> @@ -60,6 +60,7 @@ public:
>>> >> >>
>>> >> >> bool completeBuffer(Request *request, FrameBuffer *buffer);
>>> >> >> void completeRequest(Request *request);
>>> >> >> + void cancelRequest(Request *request);
>>> >> >>
>>> >> >> std::string configurationFile(const std::string &subdir,
>>> >> >> const std::string &name) const;
>>> >> >> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
>>> >> >> index 3ddce71d3..e862ef00f 100644
>>> >> >> --- a/src/libcamera/pipeline/simple/simple.cpp
>>> >> >> +++ b/src/libcamera/pipeline/simple/simple.cpp
>>> >> >> @@ -284,6 +284,7 @@ public:
>>> >> >> std::vector<std::unique_ptr<FrameBuffer>> conversionBuffers_;
>>> >> >> std::queue<std::map<const Stream *, FrameBuffer *>> conversionQueue_;
>>> >> >> bool useConversion_;
>>> >> >> + void clearIncompleteRequests();
>>> >> >>
>>> >> >> std::unique_ptr<Converter> converter_;
>>> >> >> std::unique_ptr<SoftwareIsp> swIsp_;
>>> >> >> @@ -897,6 +898,18 @@ void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer)
>>> >> >> pipe->completeRequest(request);
>>> >> >> }
>>> >> >>
>>> >> >> +void SimpleCameraData::clearIncompleteRequests()
>>> >> >> +{
>>> >> >> + while (!conversionQueue_.empty()) {
>>> >> >> + for (auto &item : conversionQueue_.front()) {
>>> >> >> + FrameBuffer *outputBuffer = item.second;
>>> >> >> + Request *request = outputBuffer->request();
>>> >> >> + pipe()->cancelRequest(request);
>>> >> >
>>> >> > Aren't you cancelling the same request multiple times ?
>>> >>
>>> >> Possibly yes. I'll add a check for RequestPending status.
>>> >
>>> > How about modifying conversionQueue_ to store instances of a structure
>>> > that contain a Request pointer in addition to the std::map ?
>>>
>>> I prefer keeping data structures simple. What would be the advantage
>>> worth of maintaining the extra pointer? I'd be inclined to have it if
>>> it served more purposes, but is it worth just for the cleanup?
>>
>> As far as I understand, the conversionQueue_ contains a map of streams
>> to output buffers for one request. If you look at
>> SimpleCameraData::bufferReady(), you'll see, in the
>> !FrameMetadata::FrameSuccess path at the top of the function,
>>
>> Request *request = nullptr;
>> for (auto &item : conversionQueue_.front()) {
>> FrameBuffer *outputBuffer = item.second;
>> request = outputBuffer->request();
>> pipe->completeBuffer(request, outputBuffer);
>> }
>> conversionQueue_.pop();
>>
>> if (request)
>> pipe->completeRequest(request);
>>
>> All buffers need to be completed individually, but the request needs to
>> be completed once only.
>
> But it is completed only if:
>
> - There is at least one output buffer
> - AND the buffer refers to the given request.
>
> Those look like reasonable assumptions but I'm not sure there is nothing
> subtle behind. You authored the code structure above when adding
> support for multiple streams so can you confirm that something like
>
> std::queue<std::pair<Request *, std::map<const Stream *, FrameBuffer *>>> conversionQueue_;
>
> ...
>
> Request *request = conversionQueue_.front().first;
> for (auto &item : conversionQueue_.front().second)
> pipe->completeBuffer(request, item.second);
> pipe->completeRequest(request);
> conversionQueue_.pop();
>
> would be equivalent under the right assumptions?
I take the silence as yes, so posted v4 with this change.
>> All the output buffers in the map relate to the same request, so I think
>> it makes sense to store the request pointer separately in the queue for
>> easy access. Alternatively, you could have a code construct similar to
>> the above, getting the request pointer from the buffer, and calling
>> completeRequest() once only. It would be nice if we could share more
>> code, replacing the above construct with something shared by the cancel
>> path.
>>
>>> >> >> + }
>>> >> >> + conversionQueue_.pop();
>>> >> >> + }
>>> >> >> +}
>>> >> >> +
>>> >> >> void SimpleCameraData::ispStatsReady(uint32_t frame, uint32_t bufferId)
>>> >> >> {
>>> >> >> swIsp_->processStats(frame, bufferId,
>>> >> >> @@ -1407,6 +1420,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
>>> >> >> video->bufferReady.disconnect(data, &SimpleCameraData::bufferReady);
>>> >> >>
>>> >> >> data->conversionBuffers_.clear();
>>> >> >> + data->clearIncompleteRequests();
>>> >> >>
>>> >> >> releasePipeline(data);
>>> >> >> }
>>> >> >> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
>>> >> >> index e59404691..c9cb11f0f 100644
>>> >> >> --- a/src/libcamera/pipeline_handler.cpp
>>> >> >> +++ b/src/libcamera/pipeline_handler.cpp
>>> >> >> @@ -367,9 +367,7 @@ void PipelineHandler::stop(Camera *camera)
>>> >> >> while (!waitingRequests_.empty()) {
>>> >> >> Request *request = waitingRequests_.front();
>>> >> >> waitingRequests_.pop();
>>> >> >> -
>>> >> >> - request->_d()->cancel();
>>> >> >> - completeRequest(request);
>>> >> >> + cancelRequest(request);
>>> >> >> }
>>> >> >>
>>> >> >> /* Make sure no requests are pending. */
>>> >> >> @@ -470,10 +468,8 @@ void PipelineHandler::doQueueRequest(Request *request)
>>> >> >> }
>>> >> >>
>>> >> >> int ret = queueRequestDevice(camera, request);
>>> >> >> - if (ret) {
>>> >> >> - request->_d()->cancel();
>>> >> >> - completeRequest(request);
>>> >> >> - }
>>> >> >> + if (ret)
>>> >> >> + cancelRequest(request);
>>> >> >> }
>>> >> >>
>>> >> >> /**
>>> >> >> @@ -568,6 +564,19 @@ void PipelineHandler::completeRequest(Request *request)
>>> >> >> }
>>> >> >> }
>>> >> >>
>>> >> >> +/**
>>> >> >> + * \brief Cancel request and signal its completion
>>> >> >> + * \param[in] request The request to cancel
>>> >> >> + *
>>> >> >> + * This function cancels the request in addition to its completion. The same
>>> >> >> + * rules as for completeRequest() apply.
>>> >> >> + */
>>> >> >> +void PipelineHandler::cancelRequest(Request *request)
>>> >> >> +{
>>> >> >> + request->_d()->cancel();
>>> >> >> + completeRequest(request);
>>> >> >> +}
>>> >> >> +
>>> >> >> /**
>>> >> >> * \brief Retrieve the absolute path to a platform configuration file
>>> >> >> * \param[in] subdir The pipeline handler specific subdirectory name
More information about the libcamera-devel
mailing list