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