[libcamera-devel] [PATCH v2 1/2] libcamera: utils: Add ScopeExitActions class

Laurent Pinchart laurent.pinchart at ideasonboard.com
Sun Oct 16 23:02:01 CEST 2022


The ScopeExitActions class is a simple object that performs
user-provided actions upon destruction. It is meant to simplify cleanup
tasks in error handling paths.

Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
Reviewed-by: Xavier Roumegue <xavier.roumegue at oss.nxp.com>
---
Changes since v1:

- Rename to ScopeExitActions
- Replace defer() with operator+=()
- Replace clear() with release()
---
 include/libcamera/base/utils.h |  13 ++++
 src/libcamera/base/utils.cpp   | 132 +++++++++++++++++++++++++++++++++
 2 files changed, 145 insertions(+)

diff --git a/include/libcamera/base/utils.h b/include/libcamera/base/utils.h
index eb7bcdf4c173..2941d9c45232 100644
--- a/include/libcamera/base/utils.h
+++ b/include/libcamera/base/utils.h
@@ -9,6 +9,7 @@
 
 #include <algorithm>
 #include <chrono>
+#include <functional>
 #include <iterator>
 #include <memory>
 #include <ostream>
@@ -367,6 +368,18 @@ decltype(auto) abs_diff(const T &a, const T &b)
 		return a - b;
 }
 
+class ScopeExitActions
+{
+public:
+	~ScopeExitActions();
+
+	void operator+=(std::function<void()> &&action);
+	void release();
+
+private:
+	std::vector<std::function<void()>> actions_;
+};
+
 } /* namespace utils */
 
 #ifndef __DOXYGEN__
diff --git a/src/libcamera/base/utils.cpp b/src/libcamera/base/utils.cpp
index 6a307940448e..eb91da297c57 100644
--- a/src/libcamera/base/utils.cpp
+++ b/src/libcamera/base/utils.cpp
@@ -463,6 +463,138 @@ std::string toAscii(const std::string &str)
  * \a b
  */
 
+/**
+ * \class ScopeExitActions
+ * \brief An object that performs actions upon destruction
+ *
+ * The ScopeExitActions class is a simple object that performs user-provided
+ * actions upon destruction. It is meant to simplify cleanup tasks in error
+ * handling paths.
+ *
+ * When the code flow performs multiple sequential actions that each need a
+ * corresponding cleanup action, error handling quickly become tedious:
+ *
+ * \code{.cpp}
+ * {
+ * 	int ret = allocateMemory();
+ * 	if (ret)
+ * 		return ret;
+ *
+ * 	ret = startProducer();
+ * 	if (ret) {
+ * 		freeMemory();
+ * 		return ret;
+ * 	}
+ *
+ * 	ret = startConsumer();
+ * 	if (ret) {
+ * 		stopProducer();
+ * 		freeMemory();
+ * 		return ret;
+ * 	}
+ *
+ * 	return 0;
+ * }
+ * \endcode
+ *
+ * This is prone to programming mistakes, as cleanup actions can easily be
+ * forgotten or ordered incorrectly. One strategy to simplify error handling is
+ * to use goto statements:
+ *
+ * \code{.cpp}
+ * {
+ * 	int ret = allocateMemory();
+ * 	if (ret)
+ * 		return ret;
+ *
+ * 	ret = startProducer();
+ * 	if (ret)
+ * 		goto error_free;
+ *
+ * 	ret = startConsumer();
+ * 	if (ret)
+ * 		goto error_stop;
+ *
+ * 	return 0;
+ *
+ * error_stop:
+ * 	stopProducer();
+ * error_free:
+ * 	freeMemory();
+ * 	return ret;
+ * }
+ * \endcode
+ *
+ * While this may be considered better, this solution is still quite
+ * error-prone. Beside the risk of picking the wrong error label, the error
+ * handling logic is separated from the normal code flow, which increases the
+ * risk of error when refactoring the code. Additionally, C++ doesn't allow
+ * goto statements to jump over local variable declarations, which can make
+ * usage of this pattern more difficult.
+ *
+ * The ScopeExitActions class solves these issues by allowing code that
+ * requires cleanup actions to be grouped with its corresponding deferred error
+ * handling code:
+ *
+ * \code{.cpp}
+ * {
+ * 	ScopeExitActions actions;
+ *
+ * 	int ret = allocateMemory();
+ * 	if (ret)
+ * 		return ret;
+ *
+ * 	actions += [&]() { freeMemory(); };
+ *
+ * 	ret = startProducer();
+ * 	if (ret)
+ * 		return ret;
+ *
+ * 	actions += [&]() { stopProducer(); };
+ *
+ * 	ret = startConsumer();
+ * 	if (ret)
+ * 		return ret;
+ *
+ * 	actions.release();
+ * 	return 0;
+ * }
+ * \endcode
+ *
+ * Deferred error handlers are executed when the ScopeExitActions instance is
+ * destroyed, in the reverse order of their addition.
+ */
+
+ScopeExitActions::~ScopeExitActions()
+{
+	for (const auto &action : utils::reverse(actions_))
+		action();
+}
+
+/**
+ * \brief Add a deferred action
+ * \param[in] action The action
+ *
+ * Add a deferred action to the ScopeExitActions. Actions will be called upon
+ * destruction in the reverse order of their addition.
+ */
+void ScopeExitActions::operator+=(std::function<void()> &&action)
+{
+	actions_.push_back(std::move(action));
+}
+
+/**
+ * \brief Remove all deferred actions
+ *
+ * This function should be called in scope exit paths that don't need the
+ * actions to be executed, such as success return paths from a function when
+ * the ScopeExitActions is used for error cleanup.
+ */
+void ScopeExitActions::release()
+{
+	actions_.clear();
+}
+
 } /* namespace utils */
 
 #ifndef __DOXYGEN__
-- 
Regards,

Laurent Pinchart



More information about the libcamera-devel mailing list