[libcamera-devel,0/2] libcamera: Simplify error handling with Cleaner class
mbox series

Message ID 20221004021842.6345-1-laurent.pinchart@ideasonboard.com
Headers show
Series
  • libcamera: Simplify error handling with Cleaner class
Related show

Message

Laurent Pinchart Oct. 4, 2022, 2:18 a.m. UTC
Hello,

This small patch series stems from my review of "[PATCH 13/14]
libcamera: pipeline: rkisp1: Factorize errors handling path on start()".
While I agreed that the current error handling pattern in
PipelineHandlerRkISP1::start() is fairly bad, I wasn't thrilled by usage
of goto statements to fix it. This is an alternative proposal.

The Cleaner class introduced in patch 1/2 took multiple forms before
reaching this one. I first experimented with a class that handled a
single action and thus needed to be instantiated once per action. To
achieve the equivalent of the Cleaner::clear() call I had to either add
an 'if (ret)' statement to the lambda functions, or pass the ret
variable to the constructor of the Cleaner. I found ideas for syntactic
sugar in https://github.com/yuri-kilochek/boost.scope_guard, but in the
end, neither option made me very happy.

Handling multiple deferred cleanup actions with one Cleaner instance
makes the user code (in 2/2) simpler. The "defer" name for the
Cleaner::defer() function was influenced by an interesting C extension
proposal (https://hal.inria.fr/hal-03059076/document). It uses the name
"guard" for the conceptual equivalent of the Cleaner class, but that
didn't convince me. I'm not sure to really like the "Cleaner" name
though, alternative naming proposals are welcome.

One point I'm not happy with is the need to call Cleaner::clear() in the
success path. This is error-prone as it could be forgotten. We could
switch to the opposite, with cleanups disabled by default and an
explicit call in the error paths, but that could also be forgotten,
resulting in missing cleanups in some error paths. The result would
possibly be worse, as error paths are less tested. Another option would
be to require explicit calls in both the success and error paths, and
complain loudly in the destructor if none of those functions have been
called. I'm not sure to be convinced it would be the best idea, although
finding good names for the success/error functions may help. Alternative
ideas are welcome too, as well as opinions regarding whether this is
worth it or not.

Laurent Pinchart (2):
  libcamera: utils: Add Cleaner class
  pipeline: rkisp1: Use utils::Cleaner to simplify error handling in
    start

 include/libcamera/base/utils.h           |  13 +++
 src/libcamera/base/utils.cpp             | 130 +++++++++++++++++++++++
 src/libcamera/pipeline/rkisp1/rkisp1.cpp |  31 ++----
 3 files changed, 154 insertions(+), 20 deletions(-)

Comments

Kieran Bingham Oct. 11, 2022, 2:51 p.m. UTC | #1
Quoting Laurent Pinchart via libcamera-devel (2022-10-04 03:18:40)
> Hello,
> 
> This small patch series stems from my review of "[PATCH 13/14]
> libcamera: pipeline: rkisp1: Factorize errors handling path on start()".
> While I agreed that the current error handling pattern in
> PipelineHandlerRkISP1::start() is fairly bad, I wasn't thrilled by usage
> of goto statements to fix it. This is an alternative proposal.
> 
> The Cleaner class introduced in patch 1/2 took multiple forms before
> reaching this one. I first experimented with a class that handled a
> single action and thus needed to be instantiated once per action. To
> achieve the equivalent of the Cleaner::clear() call I had to either add
> an 'if (ret)' statement to the lambda functions, or pass the ret
> variable to the constructor of the Cleaner. I found ideas for syntactic
> sugar in https://github.com/yuri-kilochek/boost.scope_guard, but in the
> end, neither option made me very happy.
> 
> Handling multiple deferred cleanup actions with one Cleaner instance
> makes the user code (in 2/2) simpler. The "defer" name for the
> Cleaner::defer() function was influenced by an interesting C extension
> proposal (https://hal.inria.fr/hal-03059076/document). It uses the name
> "guard" for the conceptual equivalent of the Cleaner class, but that
> didn't convince me. I'm not sure to really like the "Cleaner" name
> though, alternative naming proposals are welcome.

I'm not sure I like 'Cleaner' either. To me, this looks very much like
the scope_exit experimental C++ features:

 https://en.cppreference.com/w/cpp/experimental/scope_exit

I like the ability to add multiple cleanups to a single instance though.

https://github.com/SergiusTheBest/ScopeExit is another interesting
implementation, but bases the choice for a Success or Failed return
using exceptions, so that's not of any use to us.

I (almost) like the syntatic sugar that means lambda's are hidden away:

  https://github.com/SergiusTheBest/ScopeExit/blob/master/include/ScopeExit/ScopeExit.h

  SCOPE_EXIT{ cout << "hello"; }; // will be called at the scope exit

Which could easily be ScopeExit { }; ... but that's creating a separate
instance for each declaration which wouldn't be easy to 'clean up' or
cancel..., so I think that's not any better.



> One point I'm not happy with is the need to call Cleaner::clear() in the
> success path. This is error-prone as it could be forgotten. We could
> switch to the opposite, with cleanups disabled by default and an
> explicit call in the error paths, but that could also be forgotten,
> resulting in missing cleanups in some error paths. The result would
> possibly be worse, as error paths are less tested. Another option would
> be to require explicit calls in both the success and error paths, and
> complain loudly in the destructor if none of those functions have been
> called. I'm not sure to be convinced it would be the best idea, although

Explicitly requiring either Cleaner.success() or Cleaner.fail() with an
error if one isn't called is interesting, but I am weary that then
someone will end up simply calling the 'wrong one' on a code path ...
But maybe that's worrying about 'possible' bugs, rather than actual
bugs.


> finding good names for the success/error functions may help. Alternative
> ideas are welcome too, as well as opinions regarding whether this is
> worth it or not.

If there is automatic operations at exit, I'd call it AtScopeExit or
ScopeExit ...
and use something like


	(At?)ScopeExit e;
	e.defer([&]{ std::out << "Failed" << std::endl; });

	if (fail)
		return -EINVAL;
	
	e.cancel();

	return 0;

But it's also just 'deferred work' so perhaps DeferredWorker would be a
better class name than Cleaner if it is enforced to be called at both
success/failure paths.


Remembering to cleanup before a successful exit is a pain, but I bet
that's easier to catch if it goes wrong than the alternative, as the
'success' path is likely to be more often used... So I actually think
this would be 'safer'?

Anyway, overall - I like the idea of a cleanup helper...

--
Kieran


> 
> Laurent Pinchart (2):
>   libcamera: utils: Add Cleaner class
>   pipeline: rkisp1: Use utils::Cleaner to simplify error handling in
>     start
> 
>  include/libcamera/base/utils.h           |  13 +++
>  src/libcamera/base/utils.cpp             | 130 +++++++++++++++++++++++
>  src/libcamera/pipeline/rkisp1/rkisp1.cpp |  31 ++----
>  3 files changed, 154 insertions(+), 20 deletions(-)
> 
> -- 
> Regards,
> 
> Laurent Pinchart
>
Laurent Pinchart Oct. 11, 2022, 8:27 p.m. UTC | #2
Hi Kieran,

On Tue, Oct 11, 2022 at 03:51:19PM +0100, Kieran Bingham wrote:
> Quoting Laurent Pinchart via libcamera-devel (2022-10-04 03:18:40)
> > Hello,
> > 
> > This small patch series stems from my review of "[PATCH 13/14]
> > libcamera: pipeline: rkisp1: Factorize errors handling path on start()".
> > While I agreed that the current error handling pattern in
> > PipelineHandlerRkISP1::start() is fairly bad, I wasn't thrilled by usage
> > of goto statements to fix it. This is an alternative proposal.
> > 
> > The Cleaner class introduced in patch 1/2 took multiple forms before
> > reaching this one. I first experimented with a class that handled a
> > single action and thus needed to be instantiated once per action. To
> > achieve the equivalent of the Cleaner::clear() call I had to either add
> > an 'if (ret)' statement to the lambda functions, or pass the ret
> > variable to the constructor of the Cleaner. I found ideas for syntactic
> > sugar in https://github.com/yuri-kilochek/boost.scope_guard, but in the
> > end, neither option made me very happy.
> > 
> > Handling multiple deferred cleanup actions with one Cleaner instance
> > makes the user code (in 2/2) simpler. The "defer" name for the
> > Cleaner::defer() function was influenced by an interesting C extension
> > proposal (https://hal.inria.fr/hal-03059076/document). It uses the name
> > "guard" for the conceptual equivalent of the Cleaner class, but that
> > didn't convince me. I'm not sure to really like the "Cleaner" name
> > though, alternative naming proposals are welcome.
> 
> I'm not sure I like 'Cleaner' either. To me, this looks very much like
> the scope_exit experimental C++ features:
> 
>  https://en.cppreference.com/w/cpp/experimental/scope_exit
> 
> I like the ability to add multiple cleanups to a single instance though.

Me too. I considered going for something more similar to scope_exit, but
it would have required calling the .release() function on each scope
guard individually at the end, which isn't very nice.

> https://github.com/SergiusTheBest/ScopeExit is another interesting
> implementation, but bases the choice for a Success or Failed return
> using exceptions, so that's not of any use to us.

Same as https://en.cppreference.com/w/cpp/experimental/scope_fail.

> I (almost) like the syntatic sugar that means lambda's are hidden away:
> 
>   https://github.com/SergiusTheBest/ScopeExit/blob/master/include/ScopeExit/ScopeExit.h
> 
>   SCOPE_EXIT{ cout << "hello"; }; // will be called at the scope exit

Almost indeed.

> Which could easily be ScopeExit { }; ... but that's creating a separate
> instance for each declaration which wouldn't be easy to 'clean up' or
> cancel..., so I think that's not any better.

Exactly :-)

> > One point I'm not happy with is the need to call Cleaner::clear() in the
> > success path. This is error-prone as it could be forgotten. We could
> > switch to the opposite, with cleanups disabled by default and an
> > explicit call in the error paths, but that could also be forgotten,
> > resulting in missing cleanups in some error paths. The result would
> > possibly be worse, as error paths are less tested. Another option would
> > be to require explicit calls in both the success and error paths, and
> > complain loudly in the destructor if none of those functions have been
> > called. I'm not sure to be convinced it would be the best idea, although
> 
> Explicitly requiring either Cleaner.success() or Cleaner.fail() with an
> error if one isn't called is interesting, but I am weary that then
> someone will end up simply calling the 'wrong one' on a code path ...
> But maybe that's worrying about 'possible' bugs, rather than actual
> bugs.

That's a possible issue too.

> > finding good names for the success/error functions may help. Alternative
> > ideas are welcome too, as well as opinions regarding whether this is
> > worth it or not.
> 
> If there is automatic operations at exit, I'd call it AtScopeExit or
> ScopeExit ...

The name sounds good.

> and use something like
> 
> 
> 	(At?)ScopeExit e;
> 	e.defer([&]{ std::out << "Failed" << std::endl; });

I could also add a constructor that takes a functor, so an instance that
would have a single action wouldn't require a separate call to defer().

> 
> 	if (fail)
> 		return -EINVAL;
> 	
> 	e.cancel();
> 
> 	return 0;
> 
> But it's also just 'deferred work' so perhaps DeferredWorker would be a
> better class name than Cleaner if it is enforced to be called at both
> success/failure paths.
> 
> 
> Remembering to cleanup before a successful exit is a pain, but I bet
> that's easier to catch if it goes wrong than the alternative, as the
> 'success' path is likely to be more often used... So I actually think
> this would be 'safer'?

I think so too.

> Anyway, overall - I like the idea of a cleanup helper...
> 
> > Laurent Pinchart (2):
> >   libcamera: utils: Add Cleaner class
> >   pipeline: rkisp1: Use utils::Cleaner to simplify error handling in
> >     start
> > 
> >  include/libcamera/base/utils.h           |  13 +++
> >  src/libcamera/base/utils.cpp             | 130 +++++++++++++++++++++++
> >  src/libcamera/pipeline/rkisp1/rkisp1.cpp |  31 ++----
> >  3 files changed, 154 insertions(+), 20 deletions(-)