source/base: Refactor out callback routine

The callback mechanism will be used by other callback methods in the
same fashion, so we can template and re-use this functionality
diff --git a/.gitignore b/.gitignore
index 884d17b..61aa582 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,4 +46,5 @@
 /test/event
 /test/exception
 /test/internal_sdref
+/test/internal_utils
 /test/source_base
diff --git a/src/sdeventplus/internal/utils.hpp b/src/sdeventplus/internal/utils.hpp
index 253e10d..c0b0e68 100644
--- a/src/sdeventplus/internal/utils.hpp
+++ b/src/sdeventplus/internal/utils.hpp
@@ -1,6 +1,11 @@
 #pragma once
 
+#include <cerrno>
 #include <chrono>
+#include <cstdio>
+#include <exception>
+#include <sdeventplus/exception.hpp>
+#include <stdexcept>
 
 namespace sdeventplus
 {
@@ -9,4 +14,34 @@
 using SdEventDuration =
     std::chrono::duration<uint64_t, std::chrono::microseconds::period>;
 
+namespace internal
+{
+
+// Helpers for sd_event callbacks to handle exceptions gracefully
+template <typename Func, typename... Args>
+static int performCallback(Func func, Args... args)
+{
+    try
+    {
+        func(args...);
+        return 0;
+    }
+    catch (const std::system_error& e)
+    {
+        fprintf(stderr, "sdeventplus: callback: %s\n", e.what());
+        return -e.code().value();
+    }
+    catch (const std::exception& e)
+    {
+        fprintf(stderr, "sdeventplus: callback: %s\n", e.what());
+        return -ENOSYS;
+    }
+    catch (...)
+    {
+        fprintf(stderr, "sdeventplus: callback: Unknown error\n");
+        return -ENOSYS;
+    }
+}
+
+} // namespace internal
 } // namespace sdeventplus
diff --git a/src/sdeventplus/source/base.cpp b/src/sdeventplus/source/base.cpp
index 83908d1..5f202db 100644
--- a/src/sdeventplus/source/base.cpp
+++ b/src/sdeventplus/source/base.cpp
@@ -1,10 +1,10 @@
 #include <cerrno>
 #include <cstdio>
-#include <exception>
+#include <functional>
 #include <sdeventplus/exception.hpp>
 #include <sdeventplus/internal/sdevent.hpp>
+#include <sdeventplus/internal/utils.hpp>
 #include <sdeventplus/source/base.hpp>
-#include <stdexcept>
 #include <type_traits>
 #include <utility>
 
@@ -21,30 +21,6 @@
     }
 }
 
-int Base::prepareCallback()
-{
-    try
-    {
-        prepare(*this);
-        return 0;
-    }
-    catch (const std::system_error& e)
-    {
-        fprintf(stderr, "sdeventplus: prepareCallback: %s\n", e.what());
-        return -e.code().value();
-    }
-    catch (const std::exception& e)
-    {
-        fprintf(stderr, "sdeventplus: prepareCallback: %s\n", e.what());
-        return -ENOSYS;
-    }
-    catch (...)
-    {
-        fprintf(stderr, "sdeventplus: prepareCallback: Unknown error\n");
-        return -ENOSYS;
-    }
-}
-
 sd_event_source* Base::get() const
 {
     return source.get();
@@ -84,7 +60,8 @@
         fprintf(stderr, "sdeventplus: prepare_callback: Missing userdata\n");
         return -EINVAL;
     }
-    return reinterpret_cast<Base*>(userdata)->prepareCallback();
+    Base* base = reinterpret_cast<Base*>(userdata);
+    return internal::performCallback(base->get_prepare(), std::ref(*base));
 }
 
 void Base::set_prepare(Callback&& callback)
diff --git a/src/sdeventplus/source/base.hpp b/src/sdeventplus/source/base.hpp
index ed30016..9253b71 100644
--- a/src/sdeventplus/source/base.hpp
+++ b/src/sdeventplus/source/base.hpp
@@ -19,8 +19,6 @@
 
     virtual ~Base();
 
-    int prepareCallback();
-
     sd_event_source* get() const;
     const Event& get_event() const;
 
diff --git a/test/Makefile.am b/test/Makefile.am
index d1923fa..4b94144 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -27,6 +27,11 @@
 internal_sdref_CPPFLAGS = $(gtest_cppflags)
 internal_sdref_LDADD = $(gtest_ldadd)
 
+check_PROGRAMS += internal_utils
+internal_utils_SOURCES = internal/utils.cpp
+internal_utils_CPPFLAGS = $(gtest_cppflags)
+internal_utils_LDADD = $(gtest_ldadd)
+
 check_PROGRAMS += source_base
 source_base_SOURCES = source/base.cpp
 source_base_CPPFLAGS = $(gtest_cppflags)
diff --git a/test/internal/utils.cpp b/test/internal/utils.cpp
new file mode 100644
index 0000000..2a347ca
--- /dev/null
+++ b/test/internal/utils.cpp
@@ -0,0 +1,38 @@
+#include <gtest/gtest.h>
+#include <sdeventplus/internal/utils.hpp>
+#include <stdexcept>
+#include <system_error>
+
+namespace sdeventplus
+{
+namespace internal
+{
+namespace
+{
+
+TEST(UtilsTest, PerformCallbackSuccess)
+{
+    EXPECT_EQ(0, performCallback([]() {}));
+}
+
+TEST(UtilsTest, SetPrepareSystemError)
+{
+    EXPECT_EQ(-EBUSY, performCallback([]() {
+        throw std::system_error(EBUSY, std::generic_category());
+    }));
+}
+
+TEST(UtilsTest, SetPrepareException)
+{
+    EXPECT_EQ(-ENOSYS,
+              performCallback([]() { throw std::runtime_error("Exception"); }));
+}
+
+TEST(UtilsTest, SetPrepareUnknownException)
+{
+    EXPECT_EQ(-ENOSYS, performCallback([]() { throw static_cast<int>(1); }));
+}
+
+} // namespace
+} // namespace internal
+} // namespace sdeventplus
diff --git a/test/source/base.cpp b/test/source/base.cpp
index 69ae8e2..435b6f2 100644
--- a/test/source/base.cpp
+++ b/test/source/base.cpp
@@ -8,7 +8,6 @@
 #include <sdeventplus/source/base.hpp>
 #include <sdeventplus/test/sdevent.hpp>
 #include <string>
-#include <system_error>
 #include <systemd/sd-event.h>
 #include <type_traits>
 #include <utility>
@@ -322,47 +321,32 @@
     EXPECT_EQ(-EINVAL, event_handler(nullptr, nullptr));
 }
 
-TEST_F(BaseMethodTest, SetPrepareNull)
-{
-    EXPECT_CALL(mock, sd_event_source_set_prepare(expected_source, nullptr))
-        .WillOnce(Return(0));
-    base->set_prepare(nullptr);
-    EXPECT_EQ(-ENOSYS, base->prepareCallback());
-}
-
-TEST_F(BaseMethodTest, SetPrepareSystemError)
-{
-    Base::Callback callback = [](Base&) {
-        throw std::system_error(EBUSY, std::generic_category());
-    };
-    EXPECT_CALL(mock, sd_event_source_set_prepare(expected_source, testing::_))
-        .WillOnce(Return(0));
-    base->set_prepare(std::move(callback));
-    EXPECT_TRUE(base->get_prepare());
-    EXPECT_FALSE(callback);
-    EXPECT_EQ(-EBUSY, base->prepareCallback());
-}
-
-TEST_F(BaseMethodTest, SetPrepareUnknownException)
-{
-    Base::Callback callback = [](Base&) { throw static_cast<int>(1); };
-    EXPECT_CALL(mock, sd_event_source_set_prepare(expected_source, testing::_))
-        .WillOnce(Return(0));
-    base->set_prepare(std::move(callback));
-    EXPECT_TRUE(base->get_prepare());
-    EXPECT_FALSE(callback);
-    EXPECT_EQ(-ENOSYS, base->prepareCallback());
-}
-
 TEST_F(BaseMethodTest, SetPrepareError)
 {
+    EXPECT_CALL(mock, sd_event_source_set_prepare(expected_source, testing::_))
+        .WillOnce(Return(0));
+    base->set_prepare(std::move([](Base&) {}));
+    EXPECT_TRUE(base->get_prepare());
+
     Base::Callback callback = [](Base&) {};
     EXPECT_CALL(mock, sd_event_source_set_prepare(expected_source, testing::_))
         .WillOnce(Return(-EINVAL));
     EXPECT_THROW(base->set_prepare(std::move(callback)), SdEventError);
     EXPECT_FALSE(base->get_prepare());
     EXPECT_TRUE(callback);
-    EXPECT_EQ(-ENOSYS, base->prepareCallback());
+}
+
+TEST_F(BaseMethodTest, SetPrepareNull)
+{
+    EXPECT_CALL(mock, sd_event_source_set_prepare(expected_source, testing::_))
+        .WillOnce(Return(0));
+    base->set_prepare(std::move([](Base&) {}));
+    EXPECT_TRUE(base->get_prepare());
+
+    EXPECT_CALL(mock, sd_event_source_set_prepare(expected_source, nullptr))
+        .WillOnce(Return(0));
+    base->set_prepare(nullptr);
+    EXPECT_FALSE(base->get_prepare());
 }
 
 TEST_F(BaseMethodTest, GetPendingSuccess)