libcamera: software_isp: Handle signals in the proper thread
diff mbox series

Message ID 20241219103108.244243-1-mzamazal@redhat.com
State Superseded
Headers show
Series
  • libcamera: software_isp: Handle signals in the proper thread
Related show

Commit Message

Milan Zamazal Dec. 19, 2024, 10:31 a.m. UTC
inputBufferReady ready signal in the simple pipeline is handled in the
pipeline handler thread.  Similarly, outputBufferReady and ispStatsReady
signals should be handled there too, everything should be called from
the right thread and not block the callers.

Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
---
 src/libcamera/pipeline/simple/simple.cpp | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

Comments

Barnabás Pőcze Dec. 19, 2024, 3:05 p.m. UTC | #1
Hi


2024. december 19., csütörtök 11:31 keltezéssel, Milan Zamazal <mzamazal@redhat.com> írta:

> inputBufferReady ready signal in the simple pipeline is handled in the
> pipeline handler thread.  Similarly, outputBufferReady and ispStatsReady
> signals should be handled there too, everything should be called from
> the right thread and not block the callers.
> 
> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> ---
>  src/libcamera/pipeline/simple/simple.cpp | 9 +++++++--
>  1 file changed, 7 insertions(+), 2 deletions(-)
> 
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index 8ac24e6e..280112f4 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -548,12 +548,17 @@ int SimpleCameraData::init()
>  			 * bound explicitly to the pipe, which is bound to the pipeline
>  			 * handler thread. The function then simply forwards the call to
>  			 * conversionInputDone().
> +			 * Similarly for outputBufferReady and ispStatsReady signals.
>  			 */
>  			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
>  				this->conversionInputDone(buffer);
>  			});
> -			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
> -			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
> +			swIsp_->outputBufferReady.connect(this, [this](FrameBuffer *buffer) {
> +				this->conversionOutputDone(buffer);
> +			});
> +			swIsp_->ispStatsReady.connect(this, [this](uint32_t frame, uint32_t bufferId) {
> +				this->ispStatsReady(frame, bufferId);
> +			});
> [...]

The object is still `this`, so I wouldn't expect any difference in behaviour. Is there?


Regards,
Barnabás Pőcze
Milan Zamazal Dec. 19, 2024, 9:13 p.m. UTC | #2
Hi Barnabás,

thank you for review.

Barnabás Pőcze <pobrn@protonmail.com> writes:

> Hi
>
>
> 2024. december 19., csütörtök 11:31 keltezéssel, Milan Zamazal <mzamazal@redhat.com> írta:
>
>> inputBufferReady ready signal in the simple pipeline is handled in the
>> pipeline handler thread.  Similarly, outputBufferReady and ispStatsReady
>> signals should be handled there too, everything should be called from
>> the right thread and not block the callers.
>> 
>> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
>> ---
>>  src/libcamera/pipeline/simple/simple.cpp | 9 +++++++--
>>  1 file changed, 7 insertions(+), 2 deletions(-)
>> 
>> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
>> index 8ac24e6e..280112f4 100644
>> --- a/src/libcamera/pipeline/simple/simple.cpp
>> +++ b/src/libcamera/pipeline/simple/simple.cpp
>> @@ -548,12 +548,17 @@ int SimpleCameraData::init()
>>  			 * bound explicitly to the pipe, which is bound to the pipeline
>>  			 * handler thread. The function then simply forwards the call to
>>  			 * conversionInputDone().
>> +			 * Similarly for outputBufferReady and ispStatsReady signals.
>>  			 */
>>  			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
>>  				this->conversionInputDone(buffer);
>>  			});
>> -			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
>> -			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
>> +			swIsp_->outputBufferReady.connect(this, [this](FrameBuffer *buffer) {
>> +				this->conversionOutputDone(buffer);
>> +			});
>> +			swIsp_->ispStatsReady.connect(this, [this](uint32_t frame, uint32_t bufferId) {
>> +				this->ispStatsReady(frame, bufferId);
>> +			});
>> [...]
>
> The object is still `this`, so I wouldn't expect any difference in behaviour. Is there?

Ah, right, should be `pipe', will fix it in v2.

> Regards,
> Barnabás Pőcze
Laurent Pinchart Dec. 19, 2024, 11:22 p.m. UTC | #3
Hi Milan,

On Thu, Dec 19, 2024 at 10:13:17PM +0100, Milan Zamazal wrote:
> Barnabás Pőcze writes:
> > 2024. december 19., csütörtök 11:31 keltezéssel, Milan Zamazal írta:
> >
> >> inputBufferReady ready signal in the simple pipeline is handled in the
> >> pipeline handler thread.  Similarly, outputBufferReady and ispStatsReady
> >> signals should be handled there too, everything should be called from
> >> the right thread and not block the callers.
> >> 
> >> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> >> ---
> >>  src/libcamera/pipeline/simple/simple.cpp | 9 +++++++--
> >>  1 file changed, 7 insertions(+), 2 deletions(-)
> >> 
> >> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> >> index 8ac24e6e..280112f4 100644
> >> --- a/src/libcamera/pipeline/simple/simple.cpp
> >> +++ b/src/libcamera/pipeline/simple/simple.cpp
> >> @@ -548,12 +548,17 @@ int SimpleCameraData::init()
> >>  			 * bound explicitly to the pipe, which is bound to the pipeline
> >>  			 * handler thread. The function then simply forwards the call to
> >>  			 * conversionInputDone().
> >> +			 * Similarly for outputBufferReady and ispStatsReady signals.
> >>  			 */
> >>  			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
> >>  				this->conversionInputDone(buffer);
> >>  			});
> >> -			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
> >> -			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
> >> +			swIsp_->outputBufferReady.connect(this, [this](FrameBuffer *buffer) {
> >> +				this->conversionOutputDone(buffer);
> >> +			});
> >> +			swIsp_->ispStatsReady.connect(this, [this](uint32_t frame, uint32_t bufferId) {
> >> +				this->ispStatsReady(frame, bufferId);
> >> +			});
> >> [...]
> >
> > The object is still `this`, so I wouldn't expect any difference in behaviour. Is there?
> 
> Ah, right, should be `pipe', will fix it in v2.

I think it would be better to emit the signals from the camera manager
thread, instead of relying on the user of the soft ISP to perform
cross-thread synchronization. That will be less error-prone.
Milan Zamazal Jan. 3, 2025, 6:31 p.m. UTC | #4
Hi Laurent,

Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:

> Hi Milan,
>
> On Thu, Dec 19, 2024 at 10:13:17PM +0100, Milan Zamazal wrote:
>> Barnabás Pőcze writes:
>> > 2024. december 19., csütörtök 11:31 keltezéssel, Milan Zamazal írta:
>> >
>> >> inputBufferReady ready signal in the simple pipeline is handled in the
>> >> pipeline handler thread.  Similarly, outputBufferReady and ispStatsReady
>> >> signals should be handled there too, everything should be called from
>> >> the right thread and not block the callers.
>> >> 
>> >> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
>> >> ---
>> >>  src/libcamera/pipeline/simple/simple.cpp | 9 +++++++--
>> >>  1 file changed, 7 insertions(+), 2 deletions(-)
>> >> 
>> >> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
>> >> index 8ac24e6e..280112f4 100644
>> >> --- a/src/libcamera/pipeline/simple/simple.cpp
>> >> +++ b/src/libcamera/pipeline/simple/simple.cpp
>> >> @@ -548,12 +548,17 @@ int SimpleCameraData::init()
>> >>  			 * bound explicitly to the pipe, which is bound to the pipeline
>> >>  			 * handler thread. The function then simply forwards the call to
>> >>  			 * conversionInputDone().
>> >> +			 * Similarly for outputBufferReady and ispStatsReady signals.
>> >>  			 */
>> >>  			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
>> >>  				this->conversionInputDone(buffer);
>> >>  			});
>> >> -			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
>> >> -			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
>> >> +			swIsp_->outputBufferReady.connect(this, [this](FrameBuffer *buffer) {
>> >> +				this->conversionOutputDone(buffer);
>> >> +			});
>> >> +			swIsp_->ispStatsReady.connect(this, [this](uint32_t frame, uint32_t bufferId) {
>> >> +				this->ispStatsReady(frame, bufferId);
>> >> +			});
>> >> [...]
>> >
>> > The object is still `this`, so I wouldn't expect any difference in behaviour. Is there?
>> 
>> Ah, right, should be `pipe', will fix it in v2.
>
> I think it would be better to emit the signals from the camera manager
> thread, instead of relying on the user of the soft ISP to perform
> cross-thread synchronization. That will be less error-prone.

Maybe a stupid question, but how to do it?  The signals are emitted from
the corresponding place/thread asynchronously and there is little choice
of the thread to use.  At least without passing around some Object bound
to the camera manager thread, which wouldn't be nice either I suppose.
What do I miss?
Laurent Pinchart Jan. 27, 2025, 7:43 a.m. UTC | #5
Hi Milan,

On Fri, Jan 03, 2025 at 07:31:54PM +0100, Milan Zamazal wrote:
> Laurent Pinchart writes:
> > On Thu, Dec 19, 2024 at 10:13:17PM +0100, Milan Zamazal wrote:
> >> Barnabás Pőcze writes:
> >> > 2024. december 19., csütörtök 11:31 keltezéssel, Milan Zamazal írta:
> >> >
> >> >> inputBufferReady ready signal in the simple pipeline is handled in the
> >> >> pipeline handler thread.  Similarly, outputBufferReady and ispStatsReady
> >> >> signals should be handled there too, everything should be called from
> >> >> the right thread and not block the callers.
> >> >> 
> >> >> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> >> >> ---
> >> >>  src/libcamera/pipeline/simple/simple.cpp | 9 +++++++--
> >> >>  1 file changed, 7 insertions(+), 2 deletions(-)
> >> >> 
> >> >> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> >> >> index 8ac24e6e..280112f4 100644
> >> >> --- a/src/libcamera/pipeline/simple/simple.cpp
> >> >> +++ b/src/libcamera/pipeline/simple/simple.cpp
> >> >> @@ -548,12 +548,17 @@ int SimpleCameraData::init()
> >> >>  			 * bound explicitly to the pipe, which is bound to the pipeline
> >> >>  			 * handler thread. The function then simply forwards the call to
> >> >>  			 * conversionInputDone().
> >> >> +			 * Similarly for outputBufferReady and ispStatsReady signals.
> >> >>  			 */
> >> >>  			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
> >> >>  				this->conversionInputDone(buffer);
> >> >>  			});
> >> >> -			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
> >> >> -			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
> >> >> +			swIsp_->outputBufferReady.connect(this, [this](FrameBuffer *buffer) {
> >> >> +				this->conversionOutputDone(buffer);
> >> >> +			});
> >> >> +			swIsp_->ispStatsReady.connect(this, [this](uint32_t frame, uint32_t bufferId) {
> >> >> +				this->ispStatsReady(frame, bufferId);
> >> >> +			});
> >> >> [...]
> >> >
> >> > The object is still `this`, so I wouldn't expect any difference
> >> > in behaviour. Is there?
> >> 
> >> Ah, right, should be `pipe', will fix it in v2.
> >
> > I think it would be better to emit the signals from the camera manager
> > thread, instead of relying on the user of the soft ISP to perform
> > cross-thread synchronization. That will be less error-prone.
> 
> Maybe a stupid question, but how to do it?  The signals are emitted from
> the corresponding place/thread asynchronously and there is little choice
> of the thread to use.  At least without passing around some Object bound
> to the camera manager thread, which wouldn't be nice either I suppose.
> What do I miss?

Not a stupid question at all, I should have made this clearer.

Signals are emitted from the thread running when the emit() function is
called. If a signal is connected to an object that does not inherit from
the Object class, the connected slot will be called synchronously, in
the same thread.

If the connected object inherits from the Object class, the behaviour
differs and depends on the connection type, specified when calling the
connect() function on the signal:

- If the connection type is ConnectionTypeDirect, the slot is called
  synchronously, in the same thread as the emit() calls.

- If the connection type is ConnectionTypeQueued, the slot is called
  asynchronously, in the thread of the connected object. The emit() call
  returns immediately, before the slot is called.

- If the connection type is ConnectionTypeBlocking, the slot is called
  synchronously, in the thread of the connected object. The emit() call
  waits until the synchronous call completes in the other thread before
  returning. If the emitter and receiver are in the same thread, this
  behaves like ConnectionTypeDirect.

- If the connection type is ConnectionTypeAuto (the default),
  ConnectionTypeDirect is used if the emitter and receiver are in the
  same thread, and ConnectionTypeQueued is used otherwise.

The SimpleCameraData class does not inherit from Object, so slots are
called synchronously, in the thread of the emitter. This is why the
inputBufferReady signal is handled with a lambda function:

	swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
		this->conversionInputDone(buffer);
	});

The first argument to the connect() function is the receiver object,
which here inherits from the Object class. As a result, the lambda
function is called asynchronusly in the pipeline handler thread, not the
soft ISP worker thread.

This construct is fragile, as shown by this patch: the outputBufferReady
and ispStatsReady signals were mistakenly connected to the camera data
object, resulting in the slots being called from the wrong thread. I
think it would be better to handle this issue in the soft ISP class, to
avoid depending on the implementation of the receiver. One easy way to
do so is to make the SoftwareIsp class inherit from the Object class.
That way, SoftwareIsp::inputReady() will be called in the thread in
which the SoftwareIsp instance lives, which is the pipeline handler
thread. The inputBufferReady signal that is emitted from there will
always come from the pipeline handler thread.
Milan Zamazal Jan. 31, 2025, 7:13 p.m. UTC | #6
Hi Laurent,

Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:

> Hi Milan,
>
> On Fri, Jan 03, 2025 at 07:31:54PM +0100, Milan Zamazal wrote:
>> Laurent Pinchart writes:
>> > On Thu, Dec 19, 2024 at 10:13:17PM +0100, Milan Zamazal wrote:
>> >> Barnabás Pőcze writes:
>> >> > 2024. december 19., csütörtök 11:31 keltezéssel, Milan Zamazal írta:
>> >> >
>> >> >> inputBufferReady ready signal in the simple pipeline is handled in the
>> >> >> pipeline handler thread.  Similarly, outputBufferReady and ispStatsReady
>> >> >> signals should be handled there too, everything should be called from
>> >> >> the right thread and not block the callers.
>> >> >> 
>> >> >> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
>> >> >> ---
>> >> >>  src/libcamera/pipeline/simple/simple.cpp | 9 +++++++--
>> >> >>  1 file changed, 7 insertions(+), 2 deletions(-)
>> >> >> 
>> >> >> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
>> >> >> index 8ac24e6e..280112f4 100644
>> >> >> --- a/src/libcamera/pipeline/simple/simple.cpp
>> >> >> +++ b/src/libcamera/pipeline/simple/simple.cpp
>> >> >> @@ -548,12 +548,17 @@ int SimpleCameraData::init()
>> >> >>  			 * bound explicitly to the pipe, which is bound to the pipeline
>> >> >>  			 * handler thread. The function then simply forwards the call to
>> >> >>  			 * conversionInputDone().
>> >> >> +			 * Similarly for outputBufferReady and ispStatsReady signals.
>> >> >>  			 */
>> >> >>  			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
>> >> >>  				this->conversionInputDone(buffer);
>> >> >>  			});
>> >> >> -			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
>> >> >> -			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
>> >> >> +			swIsp_->outputBufferReady.connect(this, [this](FrameBuffer *buffer) {
>> >> >> +				this->conversionOutputDone(buffer);
>> >> >> +			});
>> >> >> +			swIsp_->ispStatsReady.connect(this, [this](uint32_t frame, uint32_t bufferId) {
>> >> >> +				this->ispStatsReady(frame, bufferId);
>> >> >> +			});
>> >> >> [...]
>> >> >
>> >> > The object is still `this`, so I wouldn't expect any difference
>> >> > in behaviour. Is there?
>> >> 
>> >> Ah, right, should be `pipe', will fix it in v2.
>> >
>> > I think it would be better to emit the signals from the camera manager
>> > thread, instead of relying on the user of the soft ISP to perform
>> > cross-thread synchronization. That will be less error-prone.
>> 
>> Maybe a stupid question, but how to do it?  The signals are emitted from
>> the corresponding place/thread asynchronously and there is little choice
>> of the thread to use.  At least without passing around some Object bound
>> to the camera manager thread, which wouldn't be nice either I suppose.
>> What do I miss?
>
> Not a stupid question at all, I should have made this clearer.
>
> Signals are emitted from the thread running when the emit() function is
> called. If a signal is connected to an object that does not inherit from
> the Object class, the connected slot will be called synchronously, in
> the same thread.
>
> If the connected object inherits from the Object class, the behaviour
> differs and depends on the connection type, specified when calling the
> connect() function on the signal:
>
> - If the connection type is ConnectionTypeDirect, the slot is called
>   synchronously, in the same thread as the emit() calls.
>
> - If the connection type is ConnectionTypeQueued, the slot is called
>   asynchronously, in the thread of the connected object. The emit() call
>   returns immediately, before the slot is called.
>
> - If the connection type is ConnectionTypeBlocking, the slot is called
>   synchronously, in the thread of the connected object. The emit() call
>   waits until the synchronous call completes in the other thread before
>   returning. If the emitter and receiver are in the same thread, this
>   behaves like ConnectionTypeDirect.
>
> - If the connection type is ConnectionTypeAuto (the default),
>   ConnectionTypeDirect is used if the emitter and receiver are in the
>   same thread, and ConnectionTypeQueued is used otherwise.
>
> The SimpleCameraData class does not inherit from Object, so slots are
> called synchronously, in the thread of the emitter. This is why the
> inputBufferReady signal is handled with a lambda function:
>
> 	swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
> 		this->conversionInputDone(buffer);
> 	});
>
> The first argument to the connect() function is the receiver object,
> which here inherits from the Object class. As a result, the lambda
> function is called asynchronusly in the pipeline handler thread, not the
> soft ISP worker thread.
>
> This construct is fragile, as shown by this patch: the outputBufferReady
> and ispStatsReady signals were mistakenly connected to the camera data
> object, resulting in the slots being called from the wrong thread. I
> think it would be better to handle this issue in the soft ISP class, to
> avoid depending on the implementation of the receiver. One easy way to
> do so is to make the SoftwareIsp class inherit from the Object class.
> That way, SoftwareIsp::inputReady() will be called in the thread in
> which the SoftwareIsp instance lives, which is the pipeline handler
> thread. The inputBufferReady signal that is emitted from there will
> always come from the pipeline handler thread.

Thank you for thorough explanation.  The idea of making SoftwareIsp to
inherit Object looks fine and working, I'll post v2 with it.

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index 8ac24e6e..280112f4 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -548,12 +548,17 @@  int SimpleCameraData::init()
 			 * bound explicitly to the pipe, which is bound to the pipeline
 			 * handler thread. The function then simply forwards the call to
 			 * conversionInputDone().
+			 * Similarly for outputBufferReady and ispStatsReady signals.
 			 */
 			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
 				this->conversionInputDone(buffer);
 			});
-			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
-			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
+			swIsp_->outputBufferReady.connect(this, [this](FrameBuffer *buffer) {
+				this->conversionOutputDone(buffer);
+			});
+			swIsp_->ispStatsReady.connect(this, [this](uint32_t frame, uint32_t bufferId) {
+				this->ispStatsReady(frame, bufferId);
+			});
 			swIsp_->setSensorControls.connect(this, &SimpleCameraData::setSensorControls);
 		}
 	}