This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/797-additional-macos-fix
in repository https://gitbox.apache.org/repos/asf/celix.git

commit 14dfeef72aac23b3099654341c18da88e40f8942
Author: Pepijn Noltes <pnol...@apache.org>
AuthorDate: Sun Aug 17 21:09:59 2025 +0200

    Refactor global curl init in celix launcher
---
 conanfile.py                                       |  2 +
 libs/error_injector/CMakeLists.txt                 |  5 ++
 libs/error_injector/curl/CMakeLists.txt            | 29 +++++++++
 libs/error_injector/curl/include/curl_ei.h         | 36 +++++++++++
 libs/error_injector/curl/src/curl_ei.cc            | 31 ++++++++++
 libs/framework/gtest/CMakeLists.txt                | 15 +++++
 .../CelixLauncherCurlErrorInjectionTestSuite.cc    | 68 +++++++++++++++++++++
 libs/framework/src/celix_launcher.c                | 71 +++++++++++++++++++---
 8 files changed, 248 insertions(+), 9 deletions(-)

diff --git a/conanfile.py b/conanfile.py
index cfce0de63..43e354ba5 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -369,6 +369,8 @@ class CelixConan(ConanFile):
                 tc.cache_variables["BUILD_ERROR_INJECTOR_JANSSON"] = "ON"
             if "mosquitto" in lst:
                 tc.cache_variables["BUILD_ERROR_INJECTOR_MOSQUITTO"] = "ON"
+            if "libcurl" in lst:
+                tc.cache_variables["BUILD_ERROR_INJECTOR_CURL"] = "ON"
         tc.cache_variables["CELIX_ERR_BUFFER_SIZE"] = 
str(self.options.celix_err_buffer_size)
         # tc.cache_variables["CMAKE_PROJECT_Celix_INCLUDE"] = 
os.path.join(self.build_folder, "conan_paths.cmake")
         # the following is workaround for 
https://github.com/conan-io/conan/issues/7192
diff --git a/libs/error_injector/CMakeLists.txt 
b/libs/error_injector/CMakeLists.txt
index c34fd0d26..769f7bd67 100644
--- a/libs/error_injector/CMakeLists.txt
+++ b/libs/error_injector/CMakeLists.txt
@@ -53,3 +53,8 @@ celix_subproject(ERROR_INJECTOR_MOSQUITTO "Option to enable 
building the mosquit
 if (ERROR_INJECTOR_MOSQUITTO)
     add_subdirectory(mosquitto)
 endif ()
+
+celix_subproject(ERROR_INJECTOR_CURL "Option to enable building the curl error 
injector" ON)
+if (ERROR_INJECTOR_CURL)
+    add_subdirectory(curl)
+endif ()
diff --git a/libs/error_injector/curl/CMakeLists.txt 
b/libs/error_injector/curl/CMakeLists.txt
new file mode 100644
index 000000000..6aeae0f8f
--- /dev/null
+++ b/libs/error_injector/curl/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+add_library(curl_ei STATIC src/curl_ei.cc)
+
+find_package(CURL REQUIRED)
+
+target_include_directories(curl_ei PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
+target_link_libraries(curl_ei PUBLIC Celix::error_injector CURL::libcurl)
+
+target_link_options(curl_ei INTERFACE
+        LINKER:--wrap,curl_global_init
+)
+
+add_library(Celix::curl_ei ALIAS curl_ei)
diff --git a/libs/error_injector/curl/include/curl_ei.h 
b/libs/error_injector/curl/include/curl_ei.h
new file mode 100644
index 000000000..80ddad351
--- /dev/null
+++ b/libs/error_injector/curl/include/curl_ei.h
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef CELIX_CURL_EI_H
+#define CELIX_CURL_EI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <curl/curl.h>
+#include "celix_error_injector.h"
+
+CELIX_EI_DECLARE(curl_global_init, CURLcode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //CELIX_CURL_EI_H
diff --git a/libs/error_injector/curl/src/curl_ei.cc 
b/libs/error_injector/curl/src/curl_ei.cc
new file mode 100644
index 000000000..d79b8974b
--- /dev/null
+++ b/libs/error_injector/curl/src/curl_ei.cc
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "curl_ei.h"
+
+extern "C" {
+
+CURLcode __real_curl_global_init(long flags);
+CELIX_EI_DEFINE(curl_global_init, CURLcode)
+CURLcode __wrap_curl_global_init(long flags) {
+    CELIX_EI_IMPL(curl_global_init);
+    return __real_curl_global_init(flags);
+}
+
+} // extern "C"
diff --git a/libs/framework/gtest/CMakeLists.txt 
b/libs/framework/gtest/CMakeLists.txt
index 08e0c4038..662af0984 100644
--- a/libs/framework/gtest/CMakeLists.txt
+++ b/libs/framework/gtest/CMakeLists.txt
@@ -179,4 +179,19 @@ if (EI_TESTS)
 
     add_test(NAME test_framework_with_ei COMMAND test_framework_with_ei)
     setup_target_for_coverage(test_framework_with_ei SCAN_DIR ..)
+
+    if (FRAMEWORK_CURLINIT)
+        #Note separate target, so that call to curl_global_init() can be 
intercepted (depends on global atomic counter)
+        add_executable(test_framework_with_curl_ei
+            src/CelixLauncherCurlErrorInjectionTestSuite.cc
+        )
+        target_link_libraries(test_framework_with_curl_ei PRIVATE 
+            framework_cut 
+            Celix::curl_ei
+            GTest::gtest GTest::gtest_main
+        )
+
+        add_test(NAME test_framework_with_curl_ei COMMAND 
test_framework_with_curl_ei)
+        setup_target_for_coverage(test_framework_with_curl_ei SCAN_DIR ..)
+    endif ()
 endif ()
diff --git 
a/libs/framework/gtest/src/CelixLauncherCurlErrorInjectionTestSuite.cc 
b/libs/framework/gtest/src/CelixLauncherCurlErrorInjectionTestSuite.cc
new file mode 100644
index 000000000..12aa82670
--- /dev/null
+++ b/libs/framework/gtest/src/CelixLauncherCurlErrorInjectionTestSuite.cc
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "celix_launcher.h"
+#include "curl_ei.h"
+
+#include <future>
+
+#define LAUNCH_WAIT_TIMEOUT_IN_MS 100
+
+
+class CelixLauncherCurlErrorInjectionTestSuite : public ::testing::Test {
+  public:
+    CelixLauncherCurlErrorInjectionTestSuite() {
+        celix_ei_expect_curl_global_init(nullptr, 0, CURLE_OK);
+    }
+
+    static void launchCelixWithCurlInitError() {
+        //Given an error injection for curl_global_init from 
celix_launcher_launchAndWait
+        celix_ei_expect_curl_global_init((void*)celix_launcher_launchAndWait, 
1, CURLE_FAILED_INIT);
+
+        //When calling celix_launcher_launchAndWait
+        auto rc = celix_launcher_launchAndWait(0, nullptr, nullptr);
+
+        //Then the expected error code should be returned
+        EXPECT_EQ(rc, 1);    
+    }
+};
+
+TEST_F(CelixLauncherCurlErrorInjectionTestSuite, LaunchCelixWithCurlInitError) 
{
+    launchCelixWithCurlInitError();
+
+    //When launcher several times, the result should be the same
+    for (int i = 0; i < 5; ++i) {
+        launchCelixWithCurlInitError();
+    }
+
+    //When launching without an error injection
+    celix_ei_expect_curl_global_init((void*)nullptr, 0, CURLE_OK);
+    std::future<int> futureRc = std::async(std::launch::async, [] {
+        return celix_launcher_launchAndWait(0, nullptr, nullptr);
+    });
+
+    //And framework is given time to launch
+    futureRc.wait_for(std::chrono::milliseconds(LAUNCH_WAIT_TIMEOUT_IN_MS));
+
+    //Then the framework should be launched successfully, and return a 0 code 
after exiting
+    celix_launcher_triggerStop();
+    EXPECT_EQ(futureRc.get(), 0);
+}
diff --git a/libs/framework/src/celix_launcher.c 
b/libs/framework/src/celix_launcher.c
index ab6832943..7966fedcd 100644
--- a/libs/framework/src/celix_launcher.c
+++ b/libs/framework/src/celix_launcher.c
@@ -28,6 +28,7 @@
 #include <libgen.h>
 
 #ifndef CELIX_NO_CURLINIT
+#include <stdbool.h>
 #include <curl/curl.h>
 #endif
 
@@ -127,6 +128,33 @@ static celix_status_t 
celix_launcher_createBundleCache(celix_properties_t* embed
  */
 static celix_status_t celix_launcher_loadRuntimeProperties(const char* 
configFile, celix_properties_t** outConfigProperties);
 
+#ifndef CELIX_NO_CURLINIT
+/**
+ * @brief Initializes the CURL library if it has not been initialized yet.
+ *
+ * This function ensures that the CURL initialization function is 
+ * called only once, regardless of how many times a celix framework is 
launched.
+ *
+ * @return CELIX_SUCCESS if CURL was initialized successfully, or 
+ * CELIX_ILLEGAL_STATE if curl initialization failed.
+ */
+static celix_status_t celix_launcher_initializeCurl();
+
+/**
+ * @brief Cleans up the CURL library if it was previously initialized.
+ *
+ * This function is called with __attribute__(destructor) to ensure that the
+ * CURL cleanup function is called only once, regardless of how many times
+ * a (global) launched celix framework is stopped and started again.
+ */
+static void celix_launcher_cleanupCurl() __attribute__((destructor));
+
+/**
+ * @brief CURL initialization bool, used to check if CURL has been initialized.
+ */
+static bool g_curl_initialized = 0;
+#endif
+
 /**
  * @brief Set the global framework instance.
  */
@@ -186,12 +214,18 @@ int celix_launcher_launchAndWait(int argc, char* argv[], 
const char* embeddedCon
         
celix_bundleContext_log(celix_framework_getFrameworkContext(framework), 
CELIX_LOG_LEVEL_WARNING,
                                 "Failed to schedule celix_shutdown_check");
     }
-    celix_framework_waitForStop(framework);
-    celix_launcher_resetLauncher();
+
 #ifndef CELIX_NO_CURLINIT
-    // Cleanup Curl
-    curl_global_cleanup();
+    status = celix_launcher_initializeCurl();
+    if (status != CELIX_SUCCESS) {
+        celix_launcher_resetLauncher();
+        return CELIX_LAUNCHER_ERROR_EXIT_CODE;
+    }
 #endif
+
+    celix_framework_waitForStop(framework);
+    celix_launcher_resetLauncher();
+
     return CELIX_LAUNCHER_OK_EXIT_CODE;
 }
 
@@ -249,11 +283,6 @@ static celix_status_t 
celix_launcher_createFramework(celix_properties_t* embedde
     sigaction(SIGUSR1, &sigact, NULL);
     sigaction(SIGUSR2, &sigact, NULL);
 
-#ifndef CELIX_NO_CURLINIT
-    // Before doing anything else, lets setup Curl
-    curl_global_init(CURL_GLOBAL_ALL);
-#endif
-
     *frameworkOut = celix_frameworkFactory_createFramework(embeddedProps);
     return *frameworkOut != NULL ? CELIX_SUCCESS : CELIX_FRAMEWORK_EXCEPTION;
 }
@@ -449,3 +478,27 @@ static void celix_launcher_resetLauncher() {
     }
     g_launcher.launched = false;
 }
+
+#ifndef CELIX_NO_CURLINIT
+celix_status_t celix_launcher_initializeCurl() {
+    bool alreadyInitialized = __atomic_exchange_n(&g_curl_initialized, true, 
__ATOMIC_SEQ_CST);
+    if (alreadyInitialized) {
+        return CELIX_SUCCESS;
+    }
+    CURLcode cc = curl_global_init(CURL_GLOBAL_DEFAULT);
+    if (cc != CURLE_OK) {
+        fprintf(stderr, "Failed to initialize Curl: %s\n", 
curl_easy_strerror(cc));
+        //note only 1 framework can be launcher with celix launcher and the 
launcher is set, so no startup race
+        __atomic_store_n(&g_curl_initialized, false, __ATOMIC_SEQ_CST); 
+        return CELIX_ILLEGAL_STATE;
+    }
+    return CELIX_SUCCESS;
+}
+
+static void celix_launcher_cleanupCurl() {
+    bool wasInitialized = __atomic_load_n(&g_curl_initialized, 
__ATOMIC_SEQ_CST);
+    if (wasInitialized) {
+        curl_global_cleanup();
+    }
+}
+#endif

Reply via email to