From 1af0812458d2f06fccafe85197e1420a82f8d6e6 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 27 Jun 2023 20:05:50 +0200 Subject: [PATCH 01/33] Add initial setup for OSGi-like conditions --- documents/conditions.md | 23 ++++ libs/framework/CMakeLists.txt | 1 + libs/framework/include/celix_condition.h | 92 +++++++++++++++ .../src/celix_framework_conditions.c | 107 +++++++++++++++++ .../src/celix_framework_conditions.h | 73 ++++++++++++ libs/framework/src/framework.c | 108 +++++++++++------- libs/framework/src/framework_private.h | 6 + 7 files changed, 371 insertions(+), 39 deletions(-) create mode 100644 documents/conditions.md create mode 100644 libs/framework/include/celix_condition.h create mode 100644 libs/framework/src/celix_framework_conditions.c create mode 100644 libs/framework/src/celix_framework_conditions.h diff --git a/documents/conditions.md b/documents/conditions.md new file mode 100644 index 000000000..b2c170ee5 --- /dev/null +++ b/documents/conditions.md @@ -0,0 +1,23 @@ +--- +title: Apache Celix Conditions +--- + + + +# Apache Celix Conditions +TODO \ No newline at end of file diff --git a/libs/framework/CMakeLists.txt b/libs/framework/CMakeLists.txt index 82968846b..a0c71fc82 100644 --- a/libs/framework/CMakeLists.txt +++ b/libs/framework/CMakeLists.txt @@ -34,6 +34,7 @@ set(FRAMEWORK_SRC src/framework_bundle_lifecycle_handler.c src/celix_bundle_state.c src/celix_framework_utils.c + src/celix_framework_conditions.c src/celix_module_private.h) set(FRAMEWORK_DEPS libuuid::libuuid CURL::libcurl ZLIB::ZLIB ${CMAKE_DL_LIBS}) diff --git a/libs/framework/include/celix_condition.h b/libs/framework/include/celix_condition.h new file mode 100644 index 000000000..55fe37a1b --- /dev/null +++ b/libs/framework/include/celix_condition.h @@ -0,0 +1,92 @@ +/* + * 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_CONDITION_H_ +#define CELIX_CONDITION_H_ + +#include "celix_framework_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * @brief The name of the condition service. + */ +#define CELIX_CONDITION_SERVICE_NAME "celix_condition" + +/*! + * @brief The version of the condition service. + */ +#define CELIX_CONDITION_SERVICE_VERSION "1.0.0" + +/*! + * @brief The property key used to specify the condition id. + * A condition id can only identify a single condition. + */ +#define CELIX_CONDITION_ID "celix.condition.id" + +/*! + * @brief The unique identifier for the default True condition. + * The default True condition is registered by the framework during framework initialization and therefore + * can always be relied upon. + */ +#define CELIX_CONDITION_ID_TRUE "true" + +/*! + * @brief The unique identifier for the framework ready condition. + * The framework ready condition is registered by the framework after all install configured bundles are installed, + * all start configured bundles are started and after the Apache Celix Event Queue becomes empty. + * + * Note that after the framework ready condition is registered, the event queue can become non-empty again. + * For example if a component depends on the "framework.ready" condition. + */ +#define CELIX_CONDITION_ID_FRAMEWORK_READY "framework.ready" + +/** + * @brief Celix condition service struct. + * + * In dynamic systems, such as OSGi and Apache Celix, one of the more challenging problems can be to define when + * a system or part of it is ready to do work. The answer can change depending on the individual perspective. + * The developer of a web server might say, the system is ready when the server starts listening on port 80. + * An application developer however would define the system as ready when the database connection is up and all + * servlets are registered. Taking the application developers view, the web server should start listening on + * port 80 when the application is ready and not beforehand. + * + * The Condition service interface is a marker interface designed to address this issue. + * Its role is to provide a dependency that can be tracked. It acts as a defined signal to other services. + * + * A Condition service must be registered with the "celix.condition.id" service property. + */ +typedef struct celix_condition { + void* handle; /**< private dummy handle, note not used in marker service struct, but added to ensure + sizeof(celix_condition_t) != 0 */ +} celix_condition_t; + +/** + * @brief A condition service struct instance that can be used to register celix_condition services. + * This can be helpful to avoid a bundle having to implement this service to register a celix_condition service. + */ +CELIX_FRAMEWORK_EXPORT celix_condition_t CELIX_CONDITION_INSTANCE; + +#ifdef __cplusplus +} +#endif + +#endif /* CELIX_CONDITION_H_ */ diff --git a/libs/framework/src/celix_framework_conditions.c b/libs/framework/src/celix_framework_conditions.c new file mode 100644 index 000000000..3105baf38 --- /dev/null +++ b/libs/framework/src/celix_framework_conditions.c @@ -0,0 +1,107 @@ +/* +* 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 "framework_private.h" +#include "celix_condition.h" +#include "celix_constants.h" + +void celix_frameworkConditions_registerInitialConditions(celix_framework_t* framework) { + long svcId = -1L; + celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); + celixThreadMutex_lock(&framework->conditions.mutex); + celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + opts.svc = &CELIX_CONDITION_INSTANCE; + opts.properties = celix_properties_create(); + if (opts.properties) { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); + svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); + } + celix_status_t addStatus = celix_arrayList_add(framework->conditions.initialConditionSvcIds, (void*)svcId); + if (addStatus != CELIX_SUCCESS) { + celix_bundleContext_unregisterService(ctx, svcId); + fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Error adding initial condition service id to list"); + } + celixThreadMutex_unlock(&framework->conditions.mutex); +} + +void celix_frameworkConditions_unregisterInitialConditions(celix_framework_t* framework) { + celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); + celixThreadMutex_lock(&framework->conditions.mutex); + for (int i = 0; i < celix_arrayList_size(framework->conditions.initialConditionSvcIds); ++i) { + long svcId = (long)celix_arrayList_get(framework->conditions.initialConditionSvcIds, i); + celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); + } + celix_arrayList_clear(framework->conditions.initialConditionSvcIds); + celixThreadMutex_unlock(&framework->conditions.mutex); +} + +static void celix_frameworkConditions_checkFrameworkReady(void* data) { + celix_framework_t* framework = data; + celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); + + celixThreadMutex_lock(&framework->dispatcher.mutex); + bool isEventQueueEmpty = framework->dispatcher.eventQueueSize == 0 && celix_arrayList_size(framework->dispatcher.dynamicEventQueue) == 0; + bool isActive = framework->dispatcher.active; + celixThreadMutex_unlock(&framework->dispatcher.mutex); + + if (!isActive) { + fw_log(framework->logger, CELIX_LOG_LEVEL_DEBUG, "Framework is not active anymore, so no need to register the framework ready condition"); + return; + } + + if (isEventQueueEmpty) { + celixThreadMutex_lock(&framework->conditions.mutex); + celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + opts.svc = &CELIX_CONDITION_INSTANCE; + opts.properties = celix_properties_create(); + if (opts.properties) { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); + long svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); + celix_status_t addStatus = celix_arrayList_add(framework->conditions.frameworkReadyConditionSvcIds, (void*)svcId); + if (addStatus != CELIX_SUCCESS) { + celix_bundleContext_unregisterService(ctx, svcId); + fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Error adding framework ready condition service id to list"); + } + } + celixThreadMutex_unlock(&framework->conditions.mutex); + } else { + //try again later on the event queue + celix_framework_fireGenericEvent(framework, -1, CELIX_FRAMEWORK_BUNDLE_ID, "Check event queue for framework ready condition service", framework, celix_frameworkConditions_checkFrameworkReady, NULL, NULL); + } +} + +void celix_frameworkConditions_registerFrameworkReadyConditions(celix_framework_t* framework) { + //note called when all bundles are installed and started + celix_frameworkConditions_checkFrameworkReady(framework); +} + +void celix_frameworkConditions_unregisterFrameworkReadyConditions(celix_framework_t* framework) { + celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); + celixThreadMutex_lock(&framework->conditions.mutex); + for (int i = 0; i < celix_arrayList_size(framework->conditions.frameworkReadyConditionSvcIds); ++i) { + long svcId = (long)celix_arrayList_get(framework->conditions.frameworkReadyConditionSvcIds, i); + celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); + } + celix_arrayList_clear(framework->conditions.frameworkReadyConditionSvcIds); + celixThreadMutex_unlock(&framework->conditions.mutex); +} \ No newline at end of file diff --git a/libs/framework/src/celix_framework_conditions.h b/libs/framework/src/celix_framework_conditions.h new file mode 100644 index 000000000..cf9c14ff8 --- /dev/null +++ b/libs/framework/src/celix_framework_conditions.h @@ -0,0 +1,73 @@ +/* +* 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_CELIX_FRAMEWORK_CONDITIONS_H_ +#define CELIX_CELIX_FRAMEWORK_CONDITIONS_H_ + +#include "celix_types.h" +#include "celix_errno.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Register the initial celix_condition services for the framework. + * + * The initial celix_condition services are: + * - The true celix_condition service + * + * Will log an error if the initial celix_condition services administration objects or services registrations + * fail. + * + * @param[in] framework The framework. + */ +void celix_frameworkConditions_registerInitialConditions(celix_framework_t* framework); + +/** + * @brief Unregister the initial celix_condition services for the framework. + * @param[in] framework The framework. + */ +void celix_frameworkConditions_unregisterInitialConditions(celix_framework_t* framework); + +/** + * @brief Register the framework ready celix_condition services for the framework. + * + * The framework ready celix_condition services are: + * - The framework.ready celix_condition service + * + * Will log an error if the framework read celix_condition services administration objects or services registrations + * fail. + * + * @param framework The framework. + */ +void celix_frameworkConditions_registerFrameworkReadyConditions(celix_framework_t* framework); + +/** + * @brief Unregister the framework ready celix_condition services for the framework. + * @param framework The framework. + */ +void celix_frameworkConditions_unregisterFrameworkReadyConditions(celix_framework_t* framework); + + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_FRAMEWORK_CONDITIONS_H_ diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index 0a2d1dc6a..8962d0f89 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -36,6 +36,7 @@ #include "celix_libloader.h" #include "celix_log_constants.h" #include "celix_module_private.h" +#include "celix_framework_conditions.h" #include "bundle_archive_private.h" #include "bundle_context_private.h" @@ -176,10 +177,10 @@ static celix_status_t frameworkActivator_start(void * userData, bundle_context_t static celix_status_t frameworkActivator_stop(void * userData, bundle_context_t *context); static celix_status_t frameworkActivator_destroy(void * userData, bundle_context_t *context); -static void framework_autoStartConfiguredBundles(celix_framework_t *fw); -static void framework_autoInstallConfiguredBundles(celix_framework_t *fw); -static void framework_autoInstallConfiguredBundlesForList(celix_framework_t *fw, const char *autoStart, celix_array_list_t *installedBundles); -static void framework_autoStartConfiguredBundlesForList(celix_framework_t* fw, const celix_array_list_t *installedBundles); +static celix_status_t framework_autoStartConfiguredBundles(celix_framework_t *fw); +static celix_status_t framework_autoInstallConfiguredBundles(celix_framework_t *fw); +static celix_status_t framework_autoInstallConfiguredBundlesForList(celix_framework_t *fw, const char *autoStart, celix_array_list_t *installedBundles); +static celix_status_t framework_autoStartConfiguredBundlesForList(celix_framework_t* fw, const celix_array_list_t *installedBundles); static void celix_framework_addToEventQueue(celix_framework_t *fw, const celix_framework_event_t* event); struct fw_bundleListener { @@ -255,6 +256,10 @@ celix_status_t framework_create(framework_pt *out, celix_properties_t* config) { framework->dispatcher.eventQueue = malloc(sizeof(celix_framework_event_t) * framework->dispatcher.eventQueueCap); framework->dispatcher.dynamicEventQueue = celix_arrayList_create(); + celixThreadMutex_create(&framework->conditions.mutex, NULL); + framework->conditions.initialConditionSvcIds = celix_arrayList_create(); + framework->conditions.frameworkReadyConditionSvcIds = celix_arrayList_create(); + //create and store framework uuid char uuid[37]; uuid_t uid; @@ -456,47 +461,59 @@ celix_status_t fw_init(framework_pt framework) { return status; } -celix_status_t framework_start(framework_pt framework) { - celix_status_t status = CELIX_SUCCESS; - bundle_state_e state = CELIX_BUNDLE_STATE_UNKNOWN; +celix_status_t framework_start(celix_framework_t* framework) { + celix_status_t status = CELIX_SUCCESS; + bundle_state_e state = CELIX_BUNDLE_STATE_UNKNOWN; - status = CELIX_DO_IF(status, bundle_getState(framework->bundle, &state)); - if (status == CELIX_SUCCESS) { - if ((state == CELIX_BUNDLE_STATE_INSTALLED) || (state == CELIX_BUNDLE_STATE_RESOLVED)) { - status = CELIX_DO_IF(status, fw_init(framework)); + status = CELIX_DO_IF(status, bundle_getState(framework->bundle, &state)); + if (status == CELIX_SUCCESS) { + if ((state == CELIX_BUNDLE_STATE_INSTALLED) || (state == CELIX_BUNDLE_STATE_RESOLVED)) { + status = CELIX_DO_IF(status, fw_init(framework)); } - } + } - status = CELIX_DO_IF(status, bundle_getState(framework->bundle, &state)); - if (status == CELIX_SUCCESS && state == CELIX_BUNDLE_STATE_STARTING) { + status = CELIX_DO_IF(status, bundle_getState(framework->bundle, &state)); + if (status == CELIX_SUCCESS && state == CELIX_BUNDLE_STATE_STARTING) { bundle_setState(framework->bundle, CELIX_BUNDLE_STATE_ACTIVE); - } + } - celix_framework_bundle_entry_t* entry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(framework, - framework->bundleId); - CELIX_DO_IF(status, fw_fireBundleEvent(framework, OSGI_FRAMEWORK_BUNDLE_EVENT_STARTED, entry)); + celix_framework_bundle_entry_t* entry = + celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(framework, framework->bundleId); + CELIX_DO_IF(status, fw_fireBundleEvent(framework, OSGI_FRAMEWORK_BUNDLE_EVENT_STARTED, entry)); celix_framework_bundleEntry_decreaseUseCount(entry); - CELIX_DO_IF(status, fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, framework->bundleId)); + celix_frameworkConditions_registerInitialConditions(framework); + CELIX_DO_IF(status, fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, framework->bundleId)); - if (status != CELIX_SUCCESS) { - status = CELIX_BUNDLE_EXCEPTION; - fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not start framework"); - fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, status); + if (status != CELIX_SUCCESS) { + status = CELIX_BUNDLE_EXCEPTION; + fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not start framework"); + fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, status); } - framework_autoStartConfiguredBundles(framework); - framework_autoInstallConfiguredBundles(framework); + celix_status_t startStatus = framework_autoStartConfiguredBundles(framework); + celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); + if (startStatus == CELIX_SUCCESS && installStatus == CELIX_SUCCESS) { + celix_frameworkConditions_registerFrameworkReadyConditions(framework); + } else { + status = CELIX_BUNDLE_EXCEPTION; //error already logged + } - if (status == CELIX_SUCCESS) { + if (status == CELIX_SUCCESS) { fw_log(framework->logger, CELIX_LOG_LEVEL_INFO, "Celix framework started"); - fw_log(framework->logger, CELIX_LOG_LEVEL_TRACE, "Celix framework started with uuid %s", celix_framework_getUUID(framework)); - } + fw_log(framework->logger, + CELIX_LOG_LEVEL_TRACE, + "Celix framework started with uuid %s", + celix_framework_getUUID(framework)); + } else { + fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Celix framework failed to start"); + } - return status; + return status; } -static void framework_autoStartConfiguredBundles(celix_framework_t* fw) { +static celix_status_t framework_autoStartConfiguredBundles(celix_framework_t* fw) { + celix_status_t status = CELIX_SUCCESS; const char* const cosgiKeys[] = {"cosgi.auto.start.0","cosgi.auto.start.1","cosgi.auto.start.2","cosgi.auto.start.3","cosgi.auto.start.4","cosgi.auto.start.5","cosgi.auto.start.6", NULL}; const char* const celixKeys[] = {CELIX_AUTO_START_0, CELIX_AUTO_START_1, CELIX_AUTO_START_2, CELIX_AUTO_START_3, CELIX_AUTO_START_4, CELIX_AUTO_START_5, CELIX_AUTO_START_6, NULL}; CELIX_BUILD_ASSERT(sizeof(*cosgiKeys) == sizeof(*celixKeys)); @@ -507,22 +524,30 @@ static void framework_autoStartConfiguredBundles(celix_framework_t* fw) { autoStart = celix_framework_getConfigProperty(fw, cosgiKeys[i], NULL, NULL); } if (autoStart != NULL) { - framework_autoInstallConfiguredBundlesForList(fw, autoStart, installedBundles); + if (framework_autoInstallConfiguredBundlesForList(fw, autoStart, installedBundles) != CELIX_SUCCESS) { + status = CELIX_BUNDLE_EXCEPTION; + } } } - framework_autoStartConfiguredBundlesForList(fw, installedBundles); + celix_status_t startStatus = framework_autoStartConfiguredBundlesForList(fw, installedBundles); + if (status == CELIX_SUCCESS) { + status = startStatus; + } celix_arrayList_destroy(installedBundles); + return status; } -static void framework_autoInstallConfiguredBundles(celix_framework_t* fw) { +static celix_status_t framework_autoInstallConfiguredBundles(celix_framework_t* fw) { const char* autoInstall = celix_framework_getConfigProperty(fw, CELIX_AUTO_INSTALL, NULL, NULL); if (autoInstall != NULL) { - framework_autoInstallConfiguredBundlesForList(fw, autoInstall, NULL); + return framework_autoInstallConfiguredBundlesForList(fw, autoInstall, NULL); } + return CELIX_SUCCESS; } -static void framework_autoInstallConfiguredBundlesForList(celix_framework_t* fw, const char *autoStartIn, celix_array_list_t *installedBundles) { +static celix_status_t framework_autoInstallConfiguredBundlesForList(celix_framework_t* fw, const char *autoStartIn, celix_array_list_t *installedBundles) { + celix_status_t status = CELIX_SUCCESS; char delims[] = " "; char *save_ptr = NULL; char autoStartBuffer[CELIX_DEFAULT_STRING_CREATE_BUFFER_SIZE]; @@ -538,6 +563,7 @@ static void framework_autoInstallConfiguredBundlesForList(celix_framework_t* fw, } } else { fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Could not install bundle from location '%s'.", location); + status = CELIX_BUNDLE_EXCEPTION; } location = strtok_r(NULL, delims, &save_ptr); } @@ -545,9 +571,11 @@ static void framework_autoInstallConfiguredBundlesForList(celix_framework_t* fw, fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Could not auto install bundles, out of memory."); } celix_utils_freeStringIfNotEqual(autoStartBuffer, autoStart); + return status;; } -static void framework_autoStartConfiguredBundlesForList(celix_framework_t* fw, const celix_array_list_t *installedBundles) { +static celix_status_t framework_autoStartConfiguredBundlesForList(celix_framework_t* fw, const celix_array_list_t *installedBundles) { + celix_status_t status = CELIX_SUCCESS; assert(!celix_framework_isCurrentThreadTheEventLoop(fw)); for (int i = 0; i < celix_arrayList_size(installedBundles); ++i) { long bndId = celix_arrayList_getLong(installedBundles, i); @@ -559,8 +587,10 @@ static void framework_autoStartConfiguredBundlesForList(celix_framework_t* fw, c } } else { fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "Cannot start bundle %s (bnd id = %li), because it is already started\n", bnd->symbolicName, bndId); + status = CELIX_BUNDLE_EXCEPTION; } } + return status; } celix_status_t framework_stop(framework_pt framework) { @@ -1177,7 +1207,6 @@ static void* framework_shutdown(void *framework) { } celixThreadMutex_unlock(&fw->installedBundles.mutex); - size = celix_arrayList_size(stopEntries); for (int i = size-1; i >= 0; --i) { //note loop in reverse order -> stop later installed bundle first celix_framework_bundle_entry_t *entry = celix_arrayList_get(stopEntries, i); @@ -1196,7 +1225,6 @@ static void* framework_shutdown(void *framework) { } celix_arrayList_destroy(stopEntries); - // 'stop' framework bundle if (fwEntry != NULL) { bundle_t *bnd = fwEntry->bnd; @@ -1494,8 +1522,10 @@ static celix_status_t frameworkActivator_stop(void * userData, bundle_context_t if (bundleContext_getFramework(context, &framework) == CELIX_SUCCESS) { - fw_log(framework->logger, CELIX_LOG_LEVEL_TRACE, "Start shutdown thread for framework %s", celix_framework_getUUID(framework)); + celix_frameworkConditions_unregisterFrameworkReadyConditions(framework); + celix_frameworkConditions_unregisterInitialConditions(framework); + fw_log(framework->logger, CELIX_LOG_LEVEL_TRACE, "Start shutdown thread for framework %s", celix_framework_getUUID(framework)); celixThreadMutex_lock(&framework->shutdown.mutex); bool alreadyInitialized = framework->shutdown.initialized; framework->shutdown.initialized = true; diff --git a/libs/framework/src/framework_private.h b/libs/framework/src/framework_private.h index d2a36cfc0..a06a8c3bb 100644 --- a/libs/framework/src/framework_private.h +++ b/libs/framework/src/framework_private.h @@ -188,6 +188,12 @@ struct celix_framework { celix_thread_mutex_t mutex; //protects below celix_array_list_t* bundleLifecycleHandlers; //entry = celix_framework_bundle_lifecycle_handler_t* } bundleLifecycleHandling; + + struct { + celix_thread_mutex_t mutex; /**< protects below*/ + celix_array_list_t* initialConditionSvcIds; /**< entry = long (service id) */ + celix_array_list_t* frameworkReadyConditionSvcIds; /**< entry = long (service id) */ + } conditions; /**< Struct to store the framework celix_conditions admin */ }; /** From cb9f45dd4715b76d74850e293f0ecaefad8c9455 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 27 Jun 2023 20:06:04 +0200 Subject: [PATCH 02/33] Fix query command to include framework services --- bundles/shell/shell/src/query_command.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bundles/shell/shell/src/query_command.c b/bundles/shell/shell/src/query_command.c index 490513b92..95c7360c0 100644 --- a/bundles/shell/shell/src/query_command.c +++ b/bundles/shell/shell/src/query_command.c @@ -20,13 +20,13 @@ #include #include +#include "celix_bundle.h" +#include "celix_bundle_context.h" +#include "celix_constants.h" #include "celix_framework.h" #include "celix_shell_constants.h" -#include "celix_bundle_context.h" -#include "std_commands.h" -#include "celix_bundle.h" #include "celix_utils.h" - +#include "std_commands.h" struct query_options { bool useColors; @@ -143,9 +143,13 @@ static void queryCommand_listServices(celix_bundle_context_t *ctx, const struct data.nrOfProvidedServicesFound = 0; data.nrOfRequestedServicesFound = 0; - if (opts->bndId >= 0L) { + if (opts->bndId >= CELIX_FRAMEWORK_BUNDLE_ID) { queryCommand_listServicesForBundle(ctx, opts->bndId, &data, opts, sout, serr); } else { + //query framework bundle + queryCommand_listServicesForBundle(ctx, CELIX_FRAMEWORK_BUNDLE_ID, &data, opts, sout, serr); + + //query rest of the bundles celix_array_list_t *bundleIds = celix_bundleContext_listBundles(ctx); for (int i = 0; i < celix_arrayList_size(bundleIds); ++i) { long bndId = celix_arrayList_getLong(bundleIds, i); @@ -199,7 +203,7 @@ bool queryCommand_execute(void *_ptr, const char *command_line_str, FILE *sout, //check if its a number (bundle id) errno = 0; long bndId = strtol(sub_str, NULL, 10); - if (bndId > 0 && errno == 0 /*not EINVAL*/) { + if (bndId >= CELIX_FRAMEWORK_BUNDLE_ID && errno == 0 /*not EINVAL*/) { opts.bndId = bndId; } else { //not option and not a bundle id -> query From 69fb405da10248bdee83f592c51b5e8f8a25bb2c Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Wed, 28 Jun 2023 17:22:19 +0200 Subject: [PATCH 03/33] Disable condition in framwork --- libs/framework/CMakeLists.txt | 1 - .../src/celix_framework_conditions.c | 200 ++++++++++-------- .../src/celix_framework_conditions.h | 47 ++-- libs/framework/src/framework.c | 18 +- libs/framework/src/framework_private.h | 6 - 5 files changed, 134 insertions(+), 138 deletions(-) diff --git a/libs/framework/CMakeLists.txt b/libs/framework/CMakeLists.txt index a0c71fc82..82968846b 100644 --- a/libs/framework/CMakeLists.txt +++ b/libs/framework/CMakeLists.txt @@ -34,7 +34,6 @@ set(FRAMEWORK_SRC src/framework_bundle_lifecycle_handler.c src/celix_bundle_state.c src/celix_framework_utils.c - src/celix_framework_conditions.c src/celix_module_private.h) set(FRAMEWORK_DEPS libuuid::libuuid CURL::libcurl ZLIB::ZLIB ${CMAKE_DL_LIBS}) diff --git a/libs/framework/src/celix_framework_conditions.c b/libs/framework/src/celix_framework_conditions.c index 3105baf38..2f717a7c6 100644 --- a/libs/framework/src/celix_framework_conditions.c +++ b/libs/framework/src/celix_framework_conditions.c @@ -1,107 +1,129 @@ /* -* 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. -*/ + * 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 "framework_private.h" #include "celix_condition.h" #include "celix_constants.h" +#include "framework_private.h" void celix_frameworkConditions_registerInitialConditions(celix_framework_t* framework) { - long svcId = -1L; - celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); - celixThreadMutex_lock(&framework->conditions.mutex); - celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; - opts.serviceName = CELIX_CONDITION_SERVICE_NAME; - opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; - opts.svc = &CELIX_CONDITION_INSTANCE; - opts.properties = celix_properties_create(); - if (opts.properties) { - celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); - svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); - } - celix_status_t addStatus = celix_arrayList_add(framework->conditions.initialConditionSvcIds, (void*)svcId); - if (addStatus != CELIX_SUCCESS) { - celix_bundleContext_unregisterService(ctx, svcId); - fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Error adding initial condition service id to list"); - } - celixThreadMutex_unlock(&framework->conditions.mutex); -} - -void celix_frameworkConditions_unregisterInitialConditions(celix_framework_t* framework) { - celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); - celixThreadMutex_lock(&framework->conditions.mutex); - for (int i = 0; i < celix_arrayList_size(framework->conditions.initialConditionSvcIds); ++i) { - long svcId = (long)celix_arrayList_get(framework->conditions.initialConditionSvcIds, i); - celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); - } - celix_arrayList_clear(framework->conditions.initialConditionSvcIds); - celixThreadMutex_unlock(&framework->conditions.mutex); + // long svcId = -1L; + // celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); + // celixThreadMutex_lock(&framework->conditions.mutex); + // celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + // opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + // opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + // opts.svc = &CELIX_CONDITION_INSTANCE; + // opts.properties = celix_properties_create(); + // if (opts.properties) { + // celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_TRUE); + // svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); + // } + // celix_status_t addStatus = celix_arrayList_addLong(framework->conditions.initialConditionSvcIds, svcId); + // if (addStatus != CELIX_SUCCESS) { + // celix_bundleContext_unregisterService(ctx, svcId); + // fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Error adding initial condition service id to list"); + // } + // celixThreadMutex_unlock(&framework->conditions.mutex); } static void celix_frameworkConditions_checkFrameworkReady(void* data) { - celix_framework_t* framework = data; - celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); + // celix_framework_t* framework = data; + // celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); - celixThreadMutex_lock(&framework->dispatcher.mutex); - bool isEventQueueEmpty = framework->dispatcher.eventQueueSize == 0 && celix_arrayList_size(framework->dispatcher.dynamicEventQueue) == 0; - bool isActive = framework->dispatcher.active; - celixThreadMutex_unlock(&framework->dispatcher.mutex); + // celixThreadMutex_lock(&framework->dispatcher.mutex); + // int eventQueueSize = + // framework->dispatcher.eventQueueSize + celix_arrayList_size(framework->dispatcher.dynamicEventQueue); + // bool ready = celix_framework_isCurrentThreadTheEventLoop(framework) ? + // eventQueueSize == 1: /*note 1, because celix_frameworkConditions_checkFrameworkReady is called from an event*/ + // eventQueueSize == 0; + // bool cancel = framework->conditions.cancelRegistrations; + // celixThreadMutex_unlock(&framework->dispatcher.mutex); - if (!isActive) { - fw_log(framework->logger, CELIX_LOG_LEVEL_DEBUG, "Framework is not active anymore, so no need to register the framework ready condition"); - return; - } + // if (cancel) { + // fw_log(framework->logger, + // CELIX_LOG_LEVEL_DEBUG, + // "Framework is stopping or not active anymore, so no need to register the framework ready condition"); + // return; + // } - if (isEventQueueEmpty) { - celixThreadMutex_lock(&framework->conditions.mutex); - celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; - opts.serviceName = CELIX_CONDITION_SERVICE_NAME; - opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; - opts.svc = &CELIX_CONDITION_INSTANCE; - opts.properties = celix_properties_create(); - if (opts.properties) { - celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); - long svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); - celix_status_t addStatus = celix_arrayList_add(framework->conditions.frameworkReadyConditionSvcIds, (void*)svcId); - if (addStatus != CELIX_SUCCESS) { - celix_bundleContext_unregisterService(ctx, svcId); - fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Error adding framework ready condition service id to list"); - } - } - celixThreadMutex_unlock(&framework->conditions.mutex); - } else { - //try again later on the event queue - celix_framework_fireGenericEvent(framework, -1, CELIX_FRAMEWORK_BUNDLE_ID, "Check event queue for framework ready condition service", framework, celix_frameworkConditions_checkFrameworkReady, NULL, NULL); - } + // if (ready) { + // celixThreadMutex_lock(&framework->conditions.mutex); + // celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + // opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + // opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + // opts.svc = &CELIX_CONDITION_INSTANCE; + // opts.properties = celix_properties_create(); + // if (opts.properties) { + // celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); + // long svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); + // celix_status_t addStatus = + // celix_arrayList_addLong(framework->conditions.frameworkReadyConditionSvcIds, svcId); + // if (addStatus != CELIX_SUCCESS) { + // celix_bundleContext_unregisterService(ctx, svcId); + // fw_log(framework->logger, + // CELIX_LOG_LEVEL_ERROR, + // "Error adding framework ready condition service id to list"); + // } + // } + // celixThreadMutex_unlock(&framework->conditions.mutex); + // } else { + // // try again later on the event queue + // fprintf(stderr, + // "Event queue not empty, so try again later. bundle state is %s\n", + // celix_bundleState_getName(celix_bundle_getState(framework->bundle))); // TODO remove + // celix_framework_fireGenericEvent(framework, + // -1, + // CELIX_FRAMEWORK_BUNDLE_ID, + // "Check event queue for framework ready condition service", + // framework, + // celix_frameworkConditions_checkFrameworkReady, + // NULL, + // NULL); + // } } void celix_frameworkConditions_registerFrameworkReadyConditions(celix_framework_t* framework) { - //note called when all bundles are installed and started + // note called when all bundles are installed and started celix_frameworkConditions_checkFrameworkReady(framework); } -void celix_frameworkConditions_unregisterFrameworkReadyConditions(celix_framework_t* framework) { - celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); - celixThreadMutex_lock(&framework->conditions.mutex); - for (int i = 0; i < celix_arrayList_size(framework->conditions.frameworkReadyConditionSvcIds); ++i) { - long svcId = (long)celix_arrayList_get(framework->conditions.frameworkReadyConditionSvcIds, i); - celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); - } - celix_arrayList_clear(framework->conditions.frameworkReadyConditionSvcIds); - celixThreadMutex_unlock(&framework->conditions.mutex); -} \ No newline at end of file +// TODO rename to cleanup +void celix_frameworkConditions_unregisterConditions(celix_framework_t* framework) { + // celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); + + // celixThreadMutex_lock(&framework->conditions.mutex); + // framework->conditions.cancelRegistrations = true; + // celixThreadMutex_unlock(&framework->conditions.mutex); + // celix_framework_waitUntilNoEventsForBnd(framework, CELIX_FRAMEWORK_BUNDLE_ID); //TODO maybe remove + + // celixThreadMutex_lock(&framework->conditions.mutex); + // for (int i = 0; i < celix_arrayList_size(framework->conditions.initialConditionSvcIds); ++i) { + // long svcId = (long)celix_arrayList_getLong(framework->conditions.initialConditionSvcIds, i); + // fprintf(stderr, "Unregistering svc id %li\n", svcId); + // celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); + // } + // celix_arrayList_clear(framework->conditions.initialConditionSvcIds); + // for (int i = 0; i < celix_arrayList_size(framework->conditions.frameworkReadyConditionSvcIds); ++i) { + // long svcId = (long)celix_arrayList_getLong(framework->conditions.frameworkReadyConditionSvcIds, i); + // fprintf(stderr, "Unregistering svc id %li\n", svcId); + // celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); + // } + // celix_arrayList_clear(framework->conditions.frameworkReadyConditionSvcIds); + // celixThreadMutex_unlock(&framework->conditions.mutex); +} diff --git a/libs/framework/src/celix_framework_conditions.h b/libs/framework/src/celix_framework_conditions.h index cf9c14ff8..a5ce9fdda 100644 --- a/libs/framework/src/celix_framework_conditions.h +++ b/libs/framework/src/celix_framework_conditions.h @@ -1,27 +1,27 @@ /* -* 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. -*/ + * 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_CELIX_FRAMEWORK_CONDITIONS_H_ #define CELIX_CELIX_FRAMEWORK_CONDITIONS_H_ -#include "celix_types.h" #include "celix_errno.h" +#include "celix_types.h" #ifdef __cplusplus extern "C" { @@ -40,12 +40,6 @@ extern "C" { */ void celix_frameworkConditions_registerInitialConditions(celix_framework_t* framework); -/** - * @brief Unregister the initial celix_condition services for the framework. - * @param[in] framework The framework. - */ -void celix_frameworkConditions_unregisterInitialConditions(celix_framework_t* framework); - /** * @brief Register the framework ready celix_condition services for the framework. * @@ -60,11 +54,10 @@ void celix_frameworkConditions_unregisterInitialConditions(celix_framework_t* fr void celix_frameworkConditions_registerFrameworkReadyConditions(celix_framework_t* framework); /** - * @brief Unregister the framework ready celix_condition services for the framework. + * @brief Unregister the intial and framework ready celix_condition services for the framework. * @param framework The framework. */ -void celix_frameworkConditions_unregisterFrameworkReadyConditions(celix_framework_t* framework); - +void celix_frameworkConditions_unregisterConditions(celix_framework_t* framework); #ifdef __cplusplus } diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index 8962d0f89..12ef7c5bd 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -36,7 +36,6 @@ #include "celix_libloader.h" #include "celix_log_constants.h" #include "celix_module_private.h" -#include "celix_framework_conditions.h" #include "bundle_archive_private.h" #include "bundle_context_private.h" @@ -256,10 +255,6 @@ celix_status_t framework_create(framework_pt *out, celix_properties_t* config) { framework->dispatcher.eventQueue = malloc(sizeof(celix_framework_event_t) * framework->dispatcher.eventQueueCap); framework->dispatcher.dynamicEventQueue = celix_arrayList_create(); - celixThreadMutex_create(&framework->conditions.mutex, NULL); - framework->conditions.initialConditionSvcIds = celix_arrayList_create(); - framework->conditions.frameworkReadyConditionSvcIds = celix_arrayList_create(); - //create and store framework uuid char uuid[37]; uuid_t uid; @@ -482,9 +477,6 @@ celix_status_t framework_start(celix_framework_t* framework) { CELIX_DO_IF(status, fw_fireBundleEvent(framework, OSGI_FRAMEWORK_BUNDLE_EVENT_STARTED, entry)); celix_framework_bundleEntry_decreaseUseCount(entry); - celix_frameworkConditions_registerInitialConditions(framework); - CELIX_DO_IF(status, fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, framework->bundleId)); - if (status != CELIX_SUCCESS) { status = CELIX_BUNDLE_EXCEPTION; fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not start framework"); @@ -494,7 +486,7 @@ celix_status_t framework_start(celix_framework_t* framework) { celix_status_t startStatus = framework_autoStartConfiguredBundles(framework); celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); if (startStatus == CELIX_SUCCESS && installStatus == CELIX_SUCCESS) { - celix_frameworkConditions_registerFrameworkReadyConditions(framework); + fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, framework->bundleId); //TODO maybe register framwork.ready on this event and only keep the condition true service? } else { status = CELIX_BUNDLE_EXCEPTION; //error already logged } @@ -1227,11 +1219,10 @@ static void* framework_shutdown(void *framework) { // 'stop' framework bundle if (fwEntry != NULL) { - bundle_t *bnd = fwEntry->bnd; fw_bundleEntry_waitTillUseCountIs(fwEntry, 1); //note this function has 1 use count. bundle_state_e state; - bundle_getState(bnd, &state); + bundle_getState(fwEntry->bnd, &state); if (state == CELIX_BUNDLE_STATE_ACTIVE || state == CELIX_BUNDLE_STATE_STARTING) { celix_framework_stopBundleEntry(fw, fwEntry); } @@ -1520,11 +1511,8 @@ static celix_status_t frameworkActivator_stop(void * userData, bundle_context_t celix_status_t status = CELIX_SUCCESS; framework_pt framework; - if (bundleContext_getFramework(context, &framework) == CELIX_SUCCESS) { - - celix_frameworkConditions_unregisterFrameworkReadyConditions(framework); - celix_frameworkConditions_unregisterInitialConditions(framework); + if (bundleContext_getFramework(context, &framework) == CELIX_SUCCESS) { fw_log(framework->logger, CELIX_LOG_LEVEL_TRACE, "Start shutdown thread for framework %s", celix_framework_getUUID(framework)); celixThreadMutex_lock(&framework->shutdown.mutex); bool alreadyInitialized = framework->shutdown.initialized; diff --git a/libs/framework/src/framework_private.h b/libs/framework/src/framework_private.h index a06a8c3bb..d2a36cfc0 100644 --- a/libs/framework/src/framework_private.h +++ b/libs/framework/src/framework_private.h @@ -188,12 +188,6 @@ struct celix_framework { celix_thread_mutex_t mutex; //protects below celix_array_list_t* bundleLifecycleHandlers; //entry = celix_framework_bundle_lifecycle_handler_t* } bundleLifecycleHandling; - - struct { - celix_thread_mutex_t mutex; /**< protects below*/ - celix_array_list_t* initialConditionSvcIds; /**< entry = long (service id) */ - celix_array_list_t* frameworkReadyConditionSvcIds; /**< entry = long (service id) */ - } conditions; /**< Struct to store the framework celix_conditions admin */ }; /** From b5964b6427db0f0004162b86f9230dbc90fc1498 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 29 Jun 2023 23:02:50 +0200 Subject: [PATCH 04/33] Refactor conditions to framework bundle --- documents/conditions.md | 23 --- documents/framework.md | 4 + libs/framework/CMakeLists.txt | 1 + .../src/CelixBundleContextBundlesTestSuite.cc | 10 +- .../CelixBundleContextServicesTestSuite.cc | 1 + .../gtest/src/CxxBundleContextTestSuite.cc | 8 +- libs/framework/include/celix_condition.h | 10 +- libs/framework/include/celix_framework.h | 5 + libs/framework/src/celix_framework_bundle.c | 167 ++++++++++++++++++ libs/framework/src/celix_framework_bundle.h | 54 ++++++ libs/framework/src/framework.c | 94 ++++------ libs/framework/src/framework_private.h | 6 + 12 files changed, 285 insertions(+), 98 deletions(-) delete mode 100644 documents/conditions.md create mode 100644 libs/framework/src/celix_framework_bundle.c create mode 100644 libs/framework/src/celix_framework_bundle.h diff --git a/documents/conditions.md b/documents/conditions.md deleted file mode 100644 index b2c170ee5..000000000 --- a/documents/conditions.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Apache Celix Conditions ---- - - - -# Apache Celix Conditions -TODO \ No newline at end of file diff --git a/documents/framework.md b/documents/framework.md index 4eb0b99df..d697bd4a7 100644 --- a/documents/framework.md +++ b/documents/framework.md @@ -206,6 +206,10 @@ add_executable(create_framework_with_celix_launcher src/launcher.c) target_link_libraries(create_framework_with_celix_launcher PRIVATE Celix::framework) ``` +## Framework Conditions + +TODO + ## Framework bundle cache The Apache Celix framework uses a bundle cache directory to store the installed bundles, their state and for a persistent bundle storage. The bundle cache directory is created in the directory configured in the framework diff --git a/libs/framework/CMakeLists.txt b/libs/framework/CMakeLists.txt index 3cde38606..ad6c5921a 100644 --- a/libs/framework/CMakeLists.txt +++ b/libs/framework/CMakeLists.txt @@ -35,6 +35,7 @@ set(FRAMEWORK_SRC src/celix_bundle_state.c src/celix_framework_utils.c src/celix_scheduled_event.c + src/celix_framework_bundle.c ) set(FRAMEWORK_DEPS libuuid::libuuid CURL::libcurl ZLIB::ZLIB ${CMAKE_DL_LIBS}) diff --git a/libs/framework/gtest/src/CelixBundleContextBundlesTestSuite.cc b/libs/framework/gtest/src/CelixBundleContextBundlesTestSuite.cc index 37cf9b2a7..fd5c54217 100644 --- a/libs/framework/gtest/src/CelixBundleContextBundlesTestSuite.cc +++ b/libs/framework/gtest/src/CelixBundleContextBundlesTestSuite.cc @@ -46,10 +46,12 @@ class CelixBundleContextBundlesTestSuite : public ::testing::Test { const char * const TEST_BND_UNRESOLVABLE_LOC = "" TEST_BUNDLE_UNRESOLVABLE_LOCATION ""; CelixBundleContextBundlesTestSuite() { - properties = properties_create(); - properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true"); - properties_set(properties, "org.osgi.framework.storage.clean", "true"); - properties_set(properties, "org.osgi.framework.storage", ".cacheBundleContextTestFramework"); + properties = celix_properties_create(); + celix_properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true"); + celix_properties_set(properties, "org.osgi.framework.storage.clean", "true"); + celix_properties_set(properties, "org.osgi.framework.storage", ".cacheBundleContextTestFramework"); + celix_properties_set(properties, "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED", "false"); + fw = celix_frameworkFactory_createFramework(properties); ctx = framework_getContext(fw); diff --git a/libs/framework/gtest/src/CelixBundleContextServicesTestSuite.cc b/libs/framework/gtest/src/CelixBundleContextServicesTestSuite.cc index f9115f416..8780a4b7d 100644 --- a/libs/framework/gtest/src/CelixBundleContextServicesTestSuite.cc +++ b/libs/framework/gtest/src/CelixBundleContextServicesTestSuite.cc @@ -47,6 +47,7 @@ class CelixBundleContextServicesTestSuite : public ::testing::Test { celix_properties_set(properties, "org.osgi.framework.storage.clean", "onFirstInit"); celix_properties_set(properties, "org.osgi.framework.storage", ".cacheBundleContextTestFramework"); celix_properties_set(properties, "CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"); + celix_properties_set(properties, "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED", "false"); celix_properties_setLong(properties, CELIX_FRAMEWORK_STATIC_EVENT_QUEUE_SIZE, 256); //ensure that the floodEventLoopTest overflows the static event queue size fw = celix_frameworkFactory_createFramework(properties); diff --git a/libs/framework/gtest/src/CxxBundleContextTestSuite.cc b/libs/framework/gtest/src/CxxBundleContextTestSuite.cc index 7c0e7db95..004b87bda 100644 --- a/libs/framework/gtest/src/CxxBundleContextTestSuite.cc +++ b/libs/framework/gtest/src/CxxBundleContextTestSuite.cc @@ -33,10 +33,10 @@ class CxxBundleContextTestSuite : public ::testing::Test { static constexpr const char * const TEST_BND2_LOC = "" SIMPLE_TEST_BUNDLE2_LOCATION ""; CxxBundleContextTestSuite() { - auto* properties = properties_create(); - properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true"); - properties_set(properties, "org.osgi.framework.storage.clean", "onFirstInit"); - properties_set(properties, "org.osgi.framework.storage", ".cacheCxxBundleContextTestFramework"); + auto* properties = celix_properties_create(); + celix_properties_set(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", "true"); + celix_properties_set(properties, "org.osgi.framework.storage.clean", "onFirstInit"); + celix_properties_set(properties, "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED", "false"); auto* cfw = celix_frameworkFactory_createFramework(properties); fw = std::shared_ptr{cfw, [](celix_framework_t* f){ celix_frameworkFactory_destroyFramework(f); }}; diff --git a/libs/framework/include/celix_condition.h b/libs/framework/include/celix_condition.h index 55fe37a1b..8c442e8bf 100644 --- a/libs/framework/include/celix_condition.h +++ b/libs/framework/include/celix_condition.h @@ -20,8 +20,6 @@ #ifndef CELIX_CONDITION_H_ #define CELIX_CONDITION_H_ -#include "celix_framework_export.h" - #ifdef __cplusplus extern "C" { #endif @@ -40,7 +38,7 @@ extern "C" { * @brief The property key used to specify the condition id. * A condition id can only identify a single condition. */ -#define CELIX_CONDITION_ID "celix.condition.id" +#define CELIX_CONDITION_ID "condition.id" /*! * @brief The unique identifier for the default True condition. @@ -79,12 +77,6 @@ typedef struct celix_condition { sizeof(celix_condition_t) != 0 */ } celix_condition_t; -/** - * @brief A condition service struct instance that can be used to register celix_condition services. - * This can be helpful to avoid a bundle having to implement this service to register a celix_condition service. - */ -CELIX_FRAMEWORK_EXPORT celix_condition_t CELIX_CONDITION_INSTANCE; - #ifdef __cplusplus } #endif diff --git a/libs/framework/include/celix_framework.h b/libs/framework/include/celix_framework.h index 749752f73..cd99d4037 100644 --- a/libs/framework/include/celix_framework.h +++ b/libs/framework/include/celix_framework.h @@ -417,6 +417,11 @@ CELIX_FRAMEWORK_EXPORT void celix_framework_waitForGenericEvent(celix_framework_ */ CELIX_FRAMEWORK_EXPORT void celix_framework_waitForStop(celix_framework_t *framework); +/** + * @brief Check if the event queue is empty. + */ +CELIX_FRAMEWORK_EXPORT bool celix_framework_isEventQueueEmpty(celix_framework_t* fw); + #ifdef __cplusplus } #endif diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c new file mode 100644 index 000000000..282f55114 --- /dev/null +++ b/libs/framework/src/celix_framework_bundle.c @@ -0,0 +1,167 @@ +/* + * 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 "celix_framework_bundle.h" + +#include "celix_condition.h" +#include "celix_threads.h" +#include "framework_private.h" + +#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED \ + "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED" // TODO move to constants.h +#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT true // TODO move to constants.h + +typedef struct celix_framework_bundle_activator { + celix_bundle_context_t* ctx; + celix_condition_t conditionInstance; /**< condition instance which can be used for multiple condition services.*/ + long trueConditionSvcId; /**< service id of the condition service which is always true. */ + framework_listener_t listener; /**< framework listener to check if the framework is ready. */ + + celix_thread_mutex_t mutex; /**< protects below. */ + bool frameworkStartedEventReceived; /**< true if the framework started event is received. */ + long frameworkReadyConditionSvcId; /**< service id of the condition service which is set when the framework is + ready. */ + long checkFrameworkScheduledEventId; /**< event id of the scheduled event to check if the framework is ready. */ + +} celix_framework_bundle_activator_t; + +static celix_status_t celix_frameworkBundle_frameworkEvent(void* handle, framework_event_t* event) { + framework_listener_t* listener = handle; + celix_framework_bundle_activator_t* act = listener->handle; + if (event->type == OSGI_FRAMEWORK_EVENT_STARTED) { + celixThreadMutex_lock(&act->mutex); + act->frameworkStartedEventReceived = true; + celixThreadMutex_unlock(&act->mutex); + } + return CELIX_SUCCESS; +} + +celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** userData) { + *userData = NULL; + celix_framework_bundle_activator_t* act = calloc(1, sizeof(*act)); + if (act) { + act->ctx = ctx; + act->trueConditionSvcId = -1L; + act->listener.handle = act; + act->listener.frameworkEvent = celix_frameworkBundle_frameworkEvent; + act->frameworkStartedEventReceived = false; + act->frameworkReadyConditionSvcId = -1L; + act->checkFrameworkScheduledEventId = -1L; + act->conditionInstance.handle = act; + celixThreadMutex_create(&act->mutex, NULL); + *userData = act; + } + return act != NULL ? CELIX_SUCCESS : CELIX_ENOMEM; +} + +static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_activator_t* act) { + celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + opts.svc = &act->conditionInstance; + opts.properties = celix_properties_create(); + if (opts.properties) { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_TRUE); + act->trueConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); + celix_bundleContext_log( + act->ctx, CELIX_LOG_LEVEL_INFO, "Registered true condition service with id %li", act->trueConditionSvcId); + } else { + celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for true condition service"); + } +} + +static void celix_frameworkBundle_readyCheck(void* data) { + celix_framework_bundle_activator_t* act = data; + celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_INFO, "celix_frameworkBundle_readyCheck"); + celixThreadMutex_lock(&act->mutex); + bool ready = act->frameworkStartedEventReceived && + celix_framework_isEventQueueEmpty(celix_bundleContext_getFramework(act->ctx)); + if (ready) { + celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + opts.svc = &act->conditionInstance; + opts.properties = celix_properties_create(); + if (opts.properties) { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); + act->frameworkReadyConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); + } else { + celix_bundleContext_log( + act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for framework.ready condition service"); + } + } else { + // not ready yet, schedule a new check + celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; + opts.callback = celix_frameworkBundle_readyCheck; + opts.callbackData = act; + opts.initialDelayInSeconds = 0.01; // TBD use a small delay or accept a lot of scheduled events during startup. + act->checkFrameworkScheduledEventId = celix_bundleContext_scheduleEvent(act->ctx, &opts); + } + celixThreadMutex_unlock(&act->mutex); +} + +celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_t* ctx) { + celix_framework_bundle_activator_t* act = userData; + + bool conditionsEnabled = celix_bundleContext_getPropertyAsBool( + ctx, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT); + if (conditionsEnabled) { + celix_frameworkBundle_registerTrueCondition(act); + fw_addFrameworkListener(celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); + celix_frameworkBundle_readyCheck(act); + return act->trueConditionSvcId >= 0 ? CELIX_SUCCESS : CELIX_BUNDLE_EXCEPTION; + } + + return CELIX_SUCCESS; +} + +celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t* ctx) { + celix_framework_bundle_activator_t* act = userData; + celix_status_t status = CELIX_SUCCESS; + + // remove framework listener + fw_removeFrameworkListener(celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); + + // stop ready check and remove framework ready condition service if present + celixThreadMutex_lock(&act->mutex); + celix_bundleContext_tryRemoveScheduledEventAsync(ctx, act->checkFrameworkScheduledEventId); + act->checkFrameworkScheduledEventId = -1L; + celix_bundleContext_unregisterServiceAsync(ctx, act->frameworkReadyConditionSvcId, NULL, NULL); + act->frameworkReadyConditionSvcId = -1L; + celixThreadMutex_unlock(&act->mutex); + + // remove true condition service + celix_bundleContext_unregisterServiceAsync(ctx, act->trueConditionSvcId, NULL, NULL); + act->trueConditionSvcId = -1L; + + // framework shutdown + celix_framework_t* framework = celix_bundleContext_getFramework(ctx); + celix_framework_shutdownAsync(framework); + return status; +} + +celix_status_t celix_frameworkBundle_destroy(void* userData, + celix_bundle_context_t* ctx __attribute__((unused))) { + celix_framework_bundle_activator_t* act = userData; + if (act) { + celixThreadMutex_destroy(&act->mutex); + free(userData); + } + return CELIX_SUCCESS; +} diff --git a/libs/framework/src/celix_framework_bundle.h b/libs/framework/src/celix_framework_bundle.h new file mode 100644 index 000000000..29776bc54 --- /dev/null +++ b/libs/framework/src/celix_framework_bundle.h @@ -0,0 +1,54 @@ +/* + * 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_FRAMEWORK_BUNDLE_H_ +#define CELIX_FRAMEWORK_BUNDLE_H_ + +#include "celix_errno.h" +#include "celix_bundle_context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The framework bundle bundle activator create. + */ +celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** userData); + +/** + * @brief The framework bundle bundle activator start. + */ +celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_t* ctx); + +/** + * @brief The framework bundle bundle activator stop. + */ +celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t* ctx); + +/** + * @brief The framework bundle bundle activator destroy. + */ +celix_status_t celix_frameworkBundle_destroy(void* userData, celix_bundle_context_t* ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* CELIX_FRAMEWORK_BUNDLE_H_ */ diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index c993e80a6..d9015c2dc 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -36,6 +36,7 @@ #include "celix_libloader.h" #include "celix_log_constants.h" #include "celix_module_private.h" +#include "celix_framework_bundle.h" #include "bundle_archive_private.h" #include "bundle_context_private.h" @@ -177,10 +178,6 @@ static void *fw_eventDispatcher(void *fw); celix_status_t fw_invokeBundleListener(framework_pt framework, bundle_listener_pt listener, bundle_event_pt event, bundle_pt bundle); celix_status_t fw_invokeFrameworkListener(framework_pt framework, framework_listener_pt listener, framework_event_pt event, bundle_pt bundle); -static celix_status_t frameworkActivator_start(void * userData, bundle_context_t *context); -static celix_status_t frameworkActivator_stop(void * userData, bundle_context_t *context); -static celix_status_t frameworkActivator_destroy(void * userData, bundle_context_t *context); - static celix_status_t framework_autoStartConfiguredBundles(celix_framework_t *fw); static celix_status_t framework_autoInstallConfiguredBundles(celix_framework_t *fw); static celix_status_t framework_autoInstallConfiguredBundlesForList(celix_framework_t *fw, const char *autoStart, celix_array_list_t *installedBundles); @@ -435,27 +432,19 @@ celix_status_t fw_init(framework_pt framework) { celix_status_t status = bundle_setState(framework->bundle, CELIX_BUNDLE_STATE_STARTING); if (status == CELIX_SUCCESS) { celix_bundle_activator_t *activator = calloc(1,(sizeof(*activator))); - if (activator != NULL) { + if (activator) { bundle_context_t *validateContext = NULL; - void * userData = NULL; - - //create_function_pt create = NULL; - celix_bundle_activator_start_fp start = frameworkActivator_start; - celix_bundle_activator_stop_fp stop = frameworkActivator_stop; - celix_bundle_activator_destroy_fp destroy = frameworkActivator_destroy; - activator->start = start; - activator->stop = stop; - activator->destroy = destroy; + activator->create = celix_frameworkBundle_create; + activator->start = celix_frameworkBundle_start; + activator->stop = celix_frameworkBundle_stop; + activator->destroy = celix_frameworkBundle_destroy; status = CELIX_DO_IF(status, bundle_setActivator(framework->bundle, activator)); status = CELIX_DO_IF(status, bundle_getContext(framework->bundle, &validateContext)); + status = CELIX_DO_IF(status, activator->create(validateContext, &activator->userData)); + status = CELIX_DO_IF(status, activator->start(activator->userData, validateContext)); - if (status == CELIX_SUCCESS) { - activator->userData = userData; - if (start != NULL) { - start(userData, validateContext); - } - } else { + if (status != CELIX_SUCCESS) { free(activator); } } else { @@ -500,9 +489,11 @@ celix_status_t framework_start(celix_framework_t* framework) { celix_status_t startStatus = framework_autoStartConfiguredBundles(framework); celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); if (startStatus == CELIX_SUCCESS && installStatus == CELIX_SUCCESS) { - fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, framework->bundleId); //TODO maybe register framwork.ready on this event and only keep the condition true service? + fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, framework->bundleId); } else { - status = CELIX_BUNDLE_EXCEPTION; //error already logged + //note not returning a error, because the framework is started, but not all bundles are started/installed + fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not auto start or install all configured bundles"); + fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, framework->bundleId); } if (status == CELIX_SUCCESS) { @@ -1015,8 +1006,7 @@ celix_status_t fw_removeFrameworkListener(framework_pt framework, bundle_pt bund if (frameworkListener->listener == listener && frameworkListener->bundle == bundle) { arrayList_remove(framework->frameworkListeners, i); - frameworkListener->bundle = NULL; - frameworkListener->listener = NULL; + free(frameworkListener); } } @@ -1264,6 +1254,21 @@ static void* framework_shutdown(void *framework) { return NULL; } +void celix_framework_shutdownAsync(celix_framework_t* framework) { + fw_log(framework->logger, + CELIX_LOG_LEVEL_TRACE, + "Start shutdown thread for framework %s", + celix_framework_getUUID(framework)); + celixThreadMutex_lock(&framework->shutdown.mutex); + bool alreadyInitialized = framework->shutdown.initialized; + framework->shutdown.initialized = true; + celixThreadMutex_unlock(&framework->shutdown.mutex); + + if (!alreadyInitialized) { + celixThread_create(&framework->shutdown.thread, NULL, framework_shutdown, framework); + } +} + celix_status_t framework_getFrameworkBundle(const_framework_pt framework, bundle_pt *bundle) { celix_status_t status = CELIX_SUCCESS; @@ -1563,6 +1568,13 @@ static int celix_framework_eventQueueSize(celix_framework_t* fw) { return fw->dispatcher.eventQueueSize + celix_arrayList_size(fw->dispatcher.dynamicEventQueue); } +bool celix_framework_isEventQueueEmpty(celix_framework_t* fw) { + celixThreadMutex_lock(&fw->dispatcher.mutex); + bool empty = celix_framework_eventQueueSize(fw) == 0; + celixThreadMutex_unlock(&fw->dispatcher.mutex); + return empty; +} + static bool requiresScheduledEventsProcessing(celix_framework_t* framework) { // precondition framework->dispatcher.mutex locked struct timespec currentTime = celixThreadCondition_getTime(); @@ -1647,40 +1659,6 @@ celix_status_t fw_invokeFrameworkListener(framework_pt framework, framework_list return ret; } -static celix_status_t frameworkActivator_start(void * userData, bundle_context_t *context) { - // nothing to do - return CELIX_SUCCESS; -} - -static celix_status_t frameworkActivator_stop(void * userData, bundle_context_t *context) { - celix_status_t status = CELIX_SUCCESS; - framework_pt framework; - - - if (bundleContext_getFramework(context, &framework) == CELIX_SUCCESS) { - fw_log(framework->logger, CELIX_LOG_LEVEL_TRACE, "Start shutdown thread for framework %s", celix_framework_getUUID(framework)); - celixThreadMutex_lock(&framework->shutdown.mutex); - bool alreadyInitialized = framework->shutdown.initialized; - framework->shutdown.initialized = true; - celixThreadMutex_unlock(&framework->shutdown.mutex); - - if (!alreadyInitialized) { - celixThread_create(&framework->shutdown.thread, NULL, framework_shutdown, framework); - } - } else { - status = CELIX_FRAMEWORK_EXCEPTION; - } - - framework_logIfError(framework->logger, status, NULL, "Failed to stop framework activator"); - - return status; -} - -static celix_status_t frameworkActivator_destroy(void * userData, bundle_context_t *context) { - return CELIX_SUCCESS; -} - - /********************************************************************************************************************** ********************************************************************************************************************** * Updated API diff --git a/libs/framework/src/framework_private.h b/libs/framework/src/framework_private.h index 2482b3529..0f8bcfbf2 100644 --- a/libs/framework/src/framework_private.h +++ b/libs/framework/src/framework_private.h @@ -522,6 +522,12 @@ bool celix_framework_removeScheduledEvent(celix_framework_t* fw, bool async, boo */ void celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId); + +/** + * @brief Start the celix framework shutdown sequence on a separate thread and return immediately. + */ +void celix_framework_shutdownAsync(celix_framework_t* framework); + #ifdef __cplusplus } #endif From 116f92f00092734f94b1e559963cff775f92ef6f Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 30 Jun 2023 20:26:09 +0200 Subject: [PATCH 05/33] Add FrameworkConditionsTestSuite and refactor fw conditions impl --- .../shell/shell/gtest/src/ShellTestSuite.cc | 4 + libs/framework/gtest/CMakeLists.txt | 7 +- ...undleArchiveWithErrorInjectionTestSuite.cc | 5 +- .../gtest/src/CondTestBundleActivator.cc | 36 +++++ .../gtest/src/FrameworkConditionsTestSuite.cc | 128 ++++++++++++++++++ libs/framework/include/celix/Constants.h | 8 ++ libs/framework/include/celix_condition.h | 46 ++++--- libs/framework/include/celix_constants.h | 9 ++ libs/framework/src/celix_framework_bundle.c | 101 ++++++++------ libs/framework/src/framework.c | 4 +- 10 files changed, 288 insertions(+), 60 deletions(-) create mode 100644 libs/framework/gtest/src/CondTestBundleActivator.cc create mode 100644 libs/framework/gtest/src/FrameworkConditionsTestSuite.cc diff --git a/bundles/shell/shell/gtest/src/ShellTestSuite.cc b/bundles/shell/shell/gtest/src/ShellTestSuite.cc index 4117f0adc..6f85f70d2 100644 --- a/bundles/shell/shell/gtest/src/ShellTestSuite.cc +++ b/bundles/shell/shell/gtest/src/ShellTestSuite.cc @@ -25,6 +25,7 @@ #include "celix_bundle_context.h" #include "celix_shell.h" #include "celix_framework_utils.h" +#include "celix_constants.h" class ShellTestSuite : public ::testing::Test { public: @@ -40,6 +41,9 @@ class ShellTestSuite : public ::testing::Test { celix_properties_set(properties, "org.osgi.framework.storage", ".cacheShellTestSuite"); celix_properties_set(properties, "CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"); + //to ensure "query 0" is still a test case for am empty result. + celix_properties_setBool(properties, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, false); + auto* cFw = celix_frameworkFactory_createFramework(properties); auto cCtx = celix_framework_getFrameworkContext(cFw); diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index eb3231b4c..695053abe 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -30,7 +30,8 @@ celix_bundle_headers(bundle_with_bad_export "Export-Library: $") add_celix_bundle(simple_cxx_bundle SOURCES src/HelloWorldCxxActivator.cc VERSION 1.0.0) celix_bundle_libs(simple_cxx_bundle "PRIVATE" TRUE Celix::framework) add_celix_bundle(simple_cxx_dep_man_bundle SOURCES src/HelloWorldCxxActivatorWithDepMan.cc VERSION 1.0.0) -add_celix_bundle(cmp_test_bundle SOURCES src/CmpTestBundleActivator.cc) +add_celix_bundle(cmp_test_bundle SOURCES src/CmpTestBundleActivator.cc VERSION 1.0.0) +add_celix_bundle(cond_test_bundle SOURCES src/CondTestBundleActivator.cc VERSION 1.0.0) add_subdirectory(subdir) #simple_test_bundle4, simple_test_bundle5 and sublib add_celix_bundle(unresolvable_bundle SOURCES src/nop_activator.c VERSION 1.0.0) @@ -58,6 +59,7 @@ set(CELIX_FRAMEWORK_TEST_SOURCES src/CelixLauncherTestSuite.cc src/CelixBundleCacheTestSuite.cc src/ScheduledEventTestSuite.cc + src/FrameworkConditionsTestSuite.cc ) add_executable(test_framework ${CELIX_FRAMEWORK_TEST_SOURCES}) @@ -99,6 +101,7 @@ celix_get_bundle_filename(unresolvable_bundle UNRESOLVABLE_BUNDLE) celix_get_bundle_file(simple_cxx_bundle SIMPLE_CXX_BUNDLE_LOC) celix_get_bundle_file(simple_cxx_dep_man_bundle SIMPLE_CXX_DEP_MAN_BUNDLE_LOC) celix_get_bundle_file(cmp_test_bundle CMP_TEST_BUNDLE_LOC) +celix_get_bundle_file(cond_test_bundle COND_TEST_BUNDLE_LOC) configure_file(config.properties.in config.properties @ONLY) configure_file(framework1.properties.in framework1.properties @ONLY) @@ -118,6 +121,7 @@ target_compile_definitions(test_framework PRIVATE CMP_TEST_BUNDLE_LOC="${CMP_TEST_BUNDLE_LOC}" SIMPLE_CXX_DEP_MAN_BUNDLE_LOC="${SIMPLE_CXX_DEP_MAN_BUNDLE_LOC}" CMP_TEST_BUNDLE_LOC="${CMP_TEST_BUNDLE_LOC}" + COND_TEST_BUNDLE_LOC="${COND_TEST_BUNDLE_LOC}" INSTALL_AND_START_BUNDLES_CONFIG_PROPERTIES_FILE="${CMAKE_CURRENT_BINARY_DIR}/install_and_start_bundles.properties" ) add_test(NAME test_framework COMMAND test_framework) @@ -220,6 +224,7 @@ if (ENABLE_TESTING_FOR_CXX14) CMP_TEST_BUNDLE_LOC="${CMP_TEST_BUNDLE_LOC}" SIMPLE_CXX_DEP_MAN_BUNDLE_LOC="${SIMPLE_CXX_DEP_MAN_WITH_CXX11_BUNDLE_LOC}" CMP_TEST_BUNDLE_LOC="${CMP_TEST_BUNDLE_LOC}" + COND_TEST_BUNDLE_LOC="${COND_TEST_BUNDLE_LOC}" INSTALL_AND_START_BUNDLES_CONFIG_PROPERTIES_FILE="${CMAKE_CURRENT_BINARY_DIR}/install_and_start_bundles.properties" ) add_test(NAME test_framework_with_cxx14 COMMAND test_framework_with_cxx14) diff --git a/libs/framework/gtest/src/BundleArchiveWithErrorInjectionTestSuite.cc b/libs/framework/gtest/src/BundleArchiveWithErrorInjectionTestSuite.cc index d0363d027..a5a3e70d9 100644 --- a/libs/framework/gtest/src/BundleArchiveWithErrorInjectionTestSuite.cc +++ b/libs/framework/gtest/src/BundleArchiveWithErrorInjectionTestSuite.cc @@ -40,8 +40,9 @@ class BundleArchiveWithErrorInjectionTestSuite : public ::testing::Test { public: BundleArchiveWithErrorInjectionTestSuite() { - fw = celix::createFramework( - {{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"}, {CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE, "true"}}); + fw = celix::createFramework({{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"}, + {CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE, "true"}, + {CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, "false"}}); ctx = fw->getFrameworkBundleContext(); } diff --git a/libs/framework/gtest/src/CondTestBundleActivator.cc b/libs/framework/gtest/src/CondTestBundleActivator.cc new file mode 100644 index 000000000..2f6ac9834 --- /dev/null +++ b/libs/framework/gtest/src/CondTestBundleActivator.cc @@ -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. + */ + +#include "celix/BundleActivator.h" +#include "celix_condition.h" + +class CndTestBundleActivator { + public: + explicit CndTestBundleActivator(const std::shared_ptr& ctx) { + registration = ctx->registerUnmanagedService(&condition, CELIX_CONDITION_SERVICE_NAME) + .addProperty(CELIX_CONDITION_ID, "test") + .build(); + } + + private: + std::shared_ptr registration{}; + celix_condition condition{}; +}; + +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(CndTestBundleActivator) \ No newline at end of file diff --git a/libs/framework/gtest/src/FrameworkConditionsTestSuite.cc b/libs/framework/gtest/src/FrameworkConditionsTestSuite.cc new file mode 100644 index 000000000..cb955f996 --- /dev/null +++ b/libs/framework/gtest/src/FrameworkConditionsTestSuite.cc @@ -0,0 +1,128 @@ +/* + * 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 + +#include "celix/Constants.h" +#include "celix/FrameworkFactory.h" +#include "celix_condition.h" + +class FrameworkConditionsTestSuite : public ::testing::Test { + public: + const int USE_SERVICE_TIMEOUT_IN_MS = 500; + + FrameworkConditionsTestSuite() = default; +}; + +TEST_F(FrameworkConditionsTestSuite, ConditionTrueAndFrameworkReadyTest) { + // Given a Celix framework (with conditions enabled (default)) + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // Then the condition service with id "true" is available + auto filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(filter).build(); + EXPECT_EQ(1, count); + + // And the condition service with id "framework.error" will not become available + filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(filter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(0, count); + + // But the condition service with id "framework.ready" will become available + filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(filter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(1, count); +} + +TEST_F(FrameworkConditionsTestSuite, ConditionTrueAndFrameworkErrorTest) { + // Given a Celix framework which is configured to start an invalid bundle + auto fw = celix::createFramework({{celix::AUTO_START_0, "non-existing-bundle.zip"}}); + auto ctx = fw->getFrameworkBundleContext(); + + // Then the condition service with id "true" is available + auto filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(filter).build(); + EXPECT_EQ(1, count); + + // And the condition service with id "framework.ready" does not become available (framework startup error) + filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(filter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(0, count); + + // But the condition service with id "framework.error" will become available + filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(filter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(1, count); +} + +TEST_F(FrameworkConditionsTestSuite, FrameworkReadyRegisteredLastTest) { + // Given a Celix framework which is configured to start a bundle with a condition test service + auto fw = celix::createFramework({{celix::AUTO_START_0, COND_TEST_BUNDLE_LOC}}); + auto ctx = fw->getFrameworkBundleContext(); + + // Then the condition service with id "true" is available + auto filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(filter).build(); + EXPECT_EQ(1, count); + + // And the condition service with id "framework.error" will not become available + filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(filter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(0, count); + + // But the condition service with id "framework.ready" will become available + filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(filter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(1, count); + + // And the condition service with id "test" is available + filter = std::string{"("} + CELIX_CONDITION_ID + "=test)"; + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(filter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(1, count); + + // And the service.id of the framework.ready condition is higher than the service.id of the test condition + //(white-box test, framework.ready condition is registered last) + filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + long readySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, filter); + filter = std::string{"("} + CELIX_CONDITION_ID + "=test)"; + long testySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, filter); + EXPECT_GT(readySvcId, testySvcId); +} diff --git a/libs/framework/include/celix/Constants.h b/libs/framework/include/celix/Constants.h index f2efbf8bc..13046110d 100644 --- a/libs/framework/include/celix/Constants.h +++ b/libs/framework/include/celix/Constants.h @@ -218,4 +218,12 @@ namespace celix { * */ constexpr const char * const LOAD_BUNDLES_WITH_NODELETE = CELIX_LOAD_BUNDLES_WITH_NODELETE; + + /** + * @brief Celix framework environment property (named "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED") to configure + * whether framework condition services are enabled or not. + * Default is true. + * Should be a boolean value. + */ + constexpr const char* const FRAMEWORK_CONDITION_SERVICES_ENABLED = CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED; } diff --git a/libs/framework/include/celix_condition.h b/libs/framework/include/celix_condition.h index 8c442e8bf..ee6642ce3 100644 --- a/libs/framework/include/celix_condition.h +++ b/libs/framework/include/celix_condition.h @@ -40,23 +40,6 @@ extern "C" { */ #define CELIX_CONDITION_ID "condition.id" -/*! - * @brief The unique identifier for the default True condition. - * The default True condition is registered by the framework during framework initialization and therefore - * can always be relied upon. - */ -#define CELIX_CONDITION_ID_TRUE "true" - -/*! - * @brief The unique identifier for the framework ready condition. - * The framework ready condition is registered by the framework after all install configured bundles are installed, - * all start configured bundles are started and after the Apache Celix Event Queue becomes empty. - * - * Note that after the framework ready condition is registered, the event queue can become non-empty again. - * For example if a component depends on the "framework.ready" condition. - */ -#define CELIX_CONDITION_ID_FRAMEWORK_READY "framework.ready" - /** * @brief Celix condition service struct. * @@ -77,6 +60,35 @@ typedef struct celix_condition { sizeof(celix_condition_t) != 0 */ } celix_condition_t; +/*! + * @brief The unique identifier for the default framework true condition. + * The default True condition is registered by the framework during framework initialization and therefore + * can always be relied upon. + */ +#define CELIX_CONDITION_ID_TRUE "true" + +/*! + * @brief The unique identifier for the framework.ready condition. + * The framework ready condition is registered by the framework after all configured bundles are installed - and + * if configured - started and after the Apache Celix Event Queue becomes empty. + * + * Note that after the framework ready condition is registered, the event queue can become non-empty again. + * For example if a component depends on the "framework.ready" condition. + * + * Either a framework.ready or framework.error condition is registered. + */ +#define CELIX_CONDITION_ID_FRAMEWORK_READY "framework.ready" + +/*! + * @brief The unique identifier for the framework.error condition. + + * The framework error condition is registered by the framework after all configured bundles are processed, + * but an error occurred while installing or starting a bundle. + * + * Either a framework.ready or framework.error condition is registered. + */ +#define CELIX_CONDITION_ID_FRAMEWORK_ERROR "framework.error" + #ifdef __cplusplus } #endif diff --git a/libs/framework/include/celix_constants.h b/libs/framework/include/celix_constants.h index c2ad3a8c6..76920c5bf 100644 --- a/libs/framework/include/celix_constants.h +++ b/libs/framework/include/celix_constants.h @@ -337,6 +337,15 @@ extern "C" { */ #define CELIX_DEFAULT_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS 2.0 +/** + * @brief Celix framework environment property (named "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED") to configure + * whether framework condition services are enabled or not. + * Default is true. + * Should be a boolean value. + */ +#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED" + + #ifdef __cplusplus } #endif diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c index 282f55114..00e2aecb6 100644 --- a/libs/framework/src/celix_framework_bundle.c +++ b/libs/framework/src/celix_framework_bundle.c @@ -20,33 +20,40 @@ #include "celix_framework_bundle.h" #include "celix_condition.h" +#include "celix_constants.h" #include "celix_threads.h" #include "framework_private.h" -#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED \ - "CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED" // TODO move to constants.h -#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT true // TODO move to constants.h +#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT true +/** + * @brief Celix framework bundle activator struct. + */ typedef struct celix_framework_bundle_activator { celix_bundle_context_t* ctx; celix_condition_t conditionInstance; /**< condition instance which can be used for multiple condition services.*/ - long trueConditionSvcId; /**< service id of the condition service which is always true. */ framework_listener_t listener; /**< framework listener to check if the framework is ready. */ - celix_thread_mutex_t mutex; /**< protects below. */ - bool frameworkStartedEventReceived; /**< true if the framework started event is received. */ - long frameworkReadyConditionSvcId; /**< service id of the condition service which is set when the framework is - ready. */ + celix_thread_mutex_t mutex; /**< protects below. */ + long trueConditionSvcId; /**< service id of the condition service which is always true. */ + bool frameworkStartedEventReceived; /**< true if the framework started event is received. */ + bool frameworkErrorEventReceived; /**< true if the framework error event is received. */ + long frameworkReadyOrErrorConditionSvcId; /**< service id of the condition service which is set when the framework + is ready or started up with an error */ long checkFrameworkScheduledEventId; /**< event id of the scheduled event to check if the framework is ready. */ - } celix_framework_bundle_activator_t; static celix_status_t celix_frameworkBundle_frameworkEvent(void* handle, framework_event_t* event) { framework_listener_t* listener = handle; celix_framework_bundle_activator_t* act = listener->handle; - if (event->type == OSGI_FRAMEWORK_EVENT_STARTED) { + if (event->type == OSGI_FRAMEWORK_EVENT_STARTED || event->type == OSGI_FRAMEWORK_EVENT_ERROR) { celixThreadMutex_lock(&act->mutex); - act->frameworkStartedEventReceived = true; + if (event->type == OSGI_FRAMEWORK_EVENT_STARTED) { + act->frameworkStartedEventReceived = true; + } else { + act->frameworkErrorEventReceived = true; + } + celix_bundleContext_wakeupScheduledEvent(act->ctx, act->checkFrameworkScheduledEventId); celixThreadMutex_unlock(&act->mutex); } return CELIX_SUCCESS; @@ -61,7 +68,8 @@ celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** act->listener.handle = act; act->listener.frameworkEvent = celix_frameworkBundle_frameworkEvent; act->frameworkStartedEventReceived = false; - act->frameworkReadyConditionSvcId = -1L; + act->frameworkErrorEventReceived = false; + act->frameworkReadyOrErrorConditionSvcId = -1L; act->checkFrameworkScheduledEventId = -1L; act->conditionInstance.handle = act; celixThreadMutex_create(&act->mutex, NULL); @@ -79,8 +87,6 @@ static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_a if (opts.properties) { celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_TRUE); act->trueConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); - celix_bundleContext_log( - act->ctx, CELIX_LOG_LEVEL_INFO, "Registered true condition service with id %li", act->trueConditionSvcId); } else { celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for true condition service"); } @@ -88,34 +94,48 @@ static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_a static void celix_frameworkBundle_readyCheck(void* data) { celix_framework_bundle_activator_t* act = data; - celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_INFO, "celix_frameworkBundle_readyCheck"); celixThreadMutex_lock(&act->mutex); + bool ready = act->frameworkStartedEventReceived && celix_framework_isEventQueueEmpty(celix_bundleContext_getFramework(act->ctx)); - if (ready) { + bool error = act->frameworkErrorEventReceived; + + if (ready || error) { celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; opts.serviceName = CELIX_CONDITION_SERVICE_NAME; opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; opts.svc = &act->conditionInstance; opts.properties = celix_properties_create(); if (opts.properties) { - celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); - act->frameworkReadyConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); + if (ready) { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); + } else /*error*/ { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_ERROR); + celix_bundleContext_log(act->ctx, + CELIX_LOG_LEVEL_INFO, + "Framework error received -> no framework.ready condition service will be registered"); + } + celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_INFO, "Registering framework.ready/framework.error condition service %s", celix_properties_get(opts.properties, CELIX_CONDITION_ID, "!ERROR!")); + act->frameworkReadyOrErrorConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); } else { celix_bundleContext_log( - act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for framework.ready condition service"); + act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for framework.ready/framework.error condition service"); } - } else { - // not ready yet, schedule a new check - celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; - opts.callback = celix_frameworkBundle_readyCheck; - opts.callbackData = act; - opts.initialDelayInSeconds = 0.01; // TBD use a small delay or accept a lot of scheduled events during startup. - act->checkFrameworkScheduledEventId = celix_bundleContext_scheduleEvent(act->ctx, &opts); + celix_bundleContext_removeScheduledEventAsync(act->ctx, act->checkFrameworkScheduledEventId); + act->checkFrameworkScheduledEventId = -1L; } celixThreadMutex_unlock(&act->mutex); } +static void celix_frameworkBundle_startReadyCheck(celix_framework_bundle_activator_t* act) { + celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; + opts.name = "celix_frameworkBundle_readyCheck"; + opts.callback = celix_frameworkBundle_readyCheck; + opts.callbackData = act; + opts.intervalInSeconds = 0.001; + act->checkFrameworkScheduledEventId = celix_bundleContext_scheduleEvent(act->ctx, &opts); +} + celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_t* ctx) { celix_framework_bundle_activator_t* act = userData; @@ -123,8 +143,9 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ ctx, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT); if (conditionsEnabled) { celix_frameworkBundle_registerTrueCondition(act); - fw_addFrameworkListener(celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); - celix_frameworkBundle_readyCheck(act); + fw_addFrameworkListener( + celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); + celix_frameworkBundle_startReadyCheck(act); return act->trueConditionSvcId >= 0 ? CELIX_SUCCESS : CELIX_BUNDLE_EXCEPTION; } @@ -133,31 +154,35 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t* ctx) { celix_framework_bundle_activator_t* act = userData; + celix_framework_t* framework = celix_bundleContext_getFramework(ctx); celix_status_t status = CELIX_SUCCESS; // remove framework listener - fw_removeFrameworkListener(celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); + fw_removeFrameworkListener(framework, celix_bundleContext_getBundle(ctx), &act->listener); - // stop ready check and remove framework ready condition service if present + // stop ready check celixThreadMutex_lock(&act->mutex); - celix_bundleContext_tryRemoveScheduledEventAsync(ctx, act->checkFrameworkScheduledEventId); + long checkEventId = act->checkFrameworkScheduledEventId; act->checkFrameworkScheduledEventId = -1L; - celix_bundleContext_unregisterServiceAsync(ctx, act->frameworkReadyConditionSvcId, NULL, NULL); - act->frameworkReadyConditionSvcId = -1L; celixThreadMutex_unlock(&act->mutex); + celix_bundleContext_removeScheduledEvent(ctx, checkEventId); - // remove true condition service - celix_bundleContext_unregisterServiceAsync(ctx, act->trueConditionSvcId, NULL, NULL); + // remove framework true condition service and - if present - framework.ready condition service, + celixThreadMutex_lock(&act->mutex); + long trueSvcId = act->trueConditionSvcId; + long readyOrErrorSvcId = act->frameworkReadyOrErrorConditionSvcId; act->trueConditionSvcId = -1L; + act->frameworkReadyOrErrorConditionSvcId = -1L; + celixThreadMutex_unlock(&act->mutex); + celix_bundleContext_unregisterService(ctx, readyOrErrorSvcId); + celix_bundleContext_unregisterService(ctx, trueSvcId); // framework shutdown - celix_framework_t* framework = celix_bundleContext_getFramework(ctx); celix_framework_shutdownAsync(framework); return status; } -celix_status_t celix_frameworkBundle_destroy(void* userData, - celix_bundle_context_t* ctx __attribute__((unused))) { +celix_status_t celix_frameworkBundle_destroy(void* userData, celix_bundle_context_t* ctx __attribute__((unused))) { celix_framework_bundle_activator_t* act = userData; if (act) { celixThreadMutex_destroy(&act->mutex); diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index d9015c2dc..d84b2a229 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -489,11 +489,11 @@ celix_status_t framework_start(celix_framework_t* framework) { celix_status_t startStatus = framework_autoStartConfiguredBundles(framework); celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); if (startStatus == CELIX_SUCCESS && installStatus == CELIX_SUCCESS) { - fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, framework->bundleId); + fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, CELIX_SUCCESS); } else { //note not returning a error, because the framework is started, but not all bundles are started/installed fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not auto start or install all configured bundles"); - fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, framework->bundleId); + fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, CELIX_BUNDLE_EXCEPTION); } if (status == CELIX_SUCCESS) { From 908f932b8d8b3e232d783434e6e89cd6a5ff2e77 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 1 Jul 2023 13:41:26 +0200 Subject: [PATCH 06/33] Add error injection tests for fw bundle and factory --- libs/framework/gtest/CMakeLists.txt | 7 +- ...stSuite.cc => FrameworkBundleTestSuite.cc} | 52 +++++------- ...meworkBundleWithErrorInjectionTestSuite.cc | 84 +++++++++++++++++++ ...tSuite.cc => FrameworkFactoryTestSuite.cc} | 18 ++-- ...eworkFactoryWithErrorInjectionTestSuite.cc | 61 ++++++++++++++ libs/framework/include/celix/Framework.h | 3 +- .../include/celix/FrameworkFactory.h | 39 +++++---- .../include/celix_framework_factory.h | 5 +- libs/framework/src/celix_framework_bundle.c | 56 ++++++++----- libs/framework/src/celix_framework_bundle.h | 7 ++ libs/framework/src/celix_framework_factory.c | 22 +++-- libs/framework/src/framework.c | 42 +++++++--- 12 files changed, 297 insertions(+), 99 deletions(-) rename libs/framework/gtest/src/{FrameworkConditionsTestSuite.cc => FrameworkBundleTestSuite.cc} (70%) create mode 100644 libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc rename libs/framework/gtest/src/{CxxFrameworkFactoryTestSuite.cc => FrameworkFactoryTestSuite.cc} (76%) create mode 100644 libs/framework/gtest/src/FrameworkFactoryWithErrorInjectionTestSuite.cc diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index 695053abe..ceac5a3f1 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -53,13 +53,13 @@ set(CELIX_FRAMEWORK_TEST_SOURCES src/DependencyManagerTestSuite.cc src/CxxBundleContextTestSuite.cc src/HelloWorldCxxActivator.cc - src/CxxFrameworkFactoryTestSuite.cc + src/FrameworkFactoryTestSuite.cc src/CxxBundleActivatorTestSuite.cc src/BundleArchiveTestSuite.cc src/CelixLauncherTestSuite.cc src/CelixBundleCacheTestSuite.cc src/ScheduledEventTestSuite.cc - src/FrameworkConditionsTestSuite.cc + src/FrameworkBundleTestSuite.cc ) add_executable(test_framework ${CELIX_FRAMEWORK_TEST_SOURCES}) @@ -134,6 +134,8 @@ if (LINKER_WRAP_SUPPORTED) src/CelixBundleContextBundlesWithErrorTestSuite.cc src/CelixBundleCacheErrorInjectionTestSuite.cc src/ScheduledEventWithErrorInjectionTestSuite.cc + src/FrameworkBundleWithErrorInjectionTestSuite.cc + src/FrameworkFactoryWithErrorInjectionTestSuite.cc ) target_compile_definitions(test_framework_with_ei PRIVATE SIMPLE_TEST_BUNDLE1_LOCATION="${SIMPLE_TEST_BUNDLE1}" @@ -152,6 +154,7 @@ if (LINKER_WRAP_SUPPORTED) Celix::unistd_ei Celix::hash_map_ei Celix::properties_ei + Celix::threads_ei GTest::gtest GTest::gtest_main ) diff --git a/libs/framework/gtest/src/FrameworkConditionsTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc similarity index 70% rename from libs/framework/gtest/src/FrameworkConditionsTestSuite.cc rename to libs/framework/gtest/src/FrameworkBundleTestSuite.cc index cb955f996..1eb7c6261 100644 --- a/libs/framework/gtest/src/FrameworkConditionsTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc @@ -23,106 +23,100 @@ #include "celix/FrameworkFactory.h" #include "celix_condition.h" -class FrameworkConditionsTestSuite : public ::testing::Test { +class FrameworkBundleTestSuite : public ::testing::Test { public: const int USE_SERVICE_TIMEOUT_IN_MS = 500; + const std::string trueFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; + const std::string readyFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + const std::string errorFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; - FrameworkConditionsTestSuite() = default; + FrameworkBundleTestSuite() = default; }; -TEST_F(FrameworkConditionsTestSuite, ConditionTrueAndFrameworkReadyTest) { +TEST_F(FrameworkBundleTestSuite, ConditionTrueAndFrameworkReadyTest) { // Given a Celix framework (with conditions enabled (default)) auto fw = celix::createFramework(); auto ctx = fw->getFrameworkBundleContext(); // Then the condition service with id "true" is available - auto filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; - auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(filter).build(); + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(trueFilter).build(); EXPECT_EQ(1, count); // And the condition service with id "framework.error" will not become available - filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(filter) + .setFilter(errorFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(0, count); // But the condition service with id "framework.ready" will become available - filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(filter) + .setFilter(readyFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); } -TEST_F(FrameworkConditionsTestSuite, ConditionTrueAndFrameworkErrorTest) { +TEST_F(FrameworkBundleTestSuite, ConditionTrueAndFrameworkErrorTest) { // Given a Celix framework which is configured to start an invalid bundle auto fw = celix::createFramework({{celix::AUTO_START_0, "non-existing-bundle.zip"}}); auto ctx = fw->getFrameworkBundleContext(); // Then the condition service with id "true" is available - auto filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; - auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(filter).build(); + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(trueFilter).build(); EXPECT_EQ(1, count); // And the condition service with id "framework.ready" does not become available (framework startup error) - filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(filter) + .setFilter(readyFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(0, count); // But the condition service with id "framework.error" will become available - filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(filter) + .setFilter(errorFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); } -TEST_F(FrameworkConditionsTestSuite, FrameworkReadyRegisteredLastTest) { +TEST_F(FrameworkBundleTestSuite, FrameworkReadyRegisteredLastTest) { // Given a Celix framework which is configured to start a bundle with a condition test service auto fw = celix::createFramework({{celix::AUTO_START_0, COND_TEST_BUNDLE_LOC}}); auto ctx = fw->getFrameworkBundleContext(); // Then the condition service with id "true" is available - auto filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; - auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(filter).build(); + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME).setFilter(trueFilter).build(); EXPECT_EQ(1, count); // And the condition service with id "framework.error" will not become available - filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(filter) + .setFilter(errorFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(0, count); // But the condition service with id "framework.ready" will become available - filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(filter) + .setFilter(readyFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); // And the condition service with id "test" is available - filter = std::string{"("} + CELIX_CONDITION_ID + "=test)"; + auto testFilter = std::string{"("} + CELIX_CONDITION_ID + "=test)"; count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(filter) + .setFilter(testFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); // And the service.id of the framework.ready condition is higher than the service.id of the test condition //(white-box test, framework.ready condition is registered last) - filter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; - long readySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, filter); - filter = std::string{"("} + CELIX_CONDITION_ID + "=test)"; - long testySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, filter); + long readySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, readyFilter); + long testySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, testFilter); EXPECT_GT(readySvcId, testySvcId); } diff --git a/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc new file mode 100644 index 000000000..3f9f4938f --- /dev/null +++ b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc @@ -0,0 +1,84 @@ +/* + * 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 + +#include "celix/FrameworkFactory.h" +#include "celix_condition.h" +#include "celix_framework_bundle.h" + +#include "celix_properties_ei.h" +#include "celix_threads_ei.h" +#include "malloc_ei.h" + +class FrameworkBundleWithErrorInjectionTestSuite : public ::testing::Test { + public: + const int USE_SERVICE_TIMEOUT_IN_MS = 500; + const std::string readyFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + + FrameworkBundleWithErrorInjectionTestSuite() = default; + + ~FrameworkBundleWithErrorInjectionTestSuite() noexcept override { + // reset error injections + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_celixThreadMutex_create(nullptr, 0, CELIX_SUCCESS); + celix_ei_expect_celix_properties_create(nullptr, 0, nullptr); + } +}; + +TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErroCreatingFrameworkBundleTest) { + // Wen an error injection for calloc is primed when called from celix_frameworkBundle_create + celix_ei_expect_calloc((void*)celix_frameworkBundle_create, 0, nullptr); + ; + + // Then an exception is expected when creating a framework instance + EXPECT_ANY_THROW(celix::createFramework()); + + // When an error injection for celixThreadMutex_create is primed when called from celix_frameworkBundle_create + celix_ei_expect_celixThreadMutex_create((void*)celix_frameworkBundle_create, 0, ENOMEM); + + // Then an exception is expected when creating a framework instance + EXPECT_ANY_THROW(celix::createFramework()); +} + +TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorStartingFrameworkBundleTest) { + // Wen an error injection for celix_properties_create is primed when called (indirectly) from + // celix_frameworkBundle_start + celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_start, 1, nullptr); + + // Then an exception is expected when creating a framework instance + EXPECT_ANY_THROW(celix::createFramework()); +} + +TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringFrameworkReadyConditionTest) { + // When an error injection for celix_properties_create is primed when called from celix_frameworkBundle_readyCheck + celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_readyCheck, 0, nullptr); + + // And a framework instance is created + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // Then the framework.ready condition will not become available, due to an error creating properties for the service + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(readyFilter) + .setTimeout(std::chrono::milliseconds {USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(count, 0); +} diff --git a/libs/framework/gtest/src/CxxFrameworkFactoryTestSuite.cc b/libs/framework/gtest/src/FrameworkFactoryTestSuite.cc similarity index 76% rename from libs/framework/gtest/src/CxxFrameworkFactoryTestSuite.cc rename to libs/framework/gtest/src/FrameworkFactoryTestSuite.cc index 04ce44c8a..9f7c02047 100644 --- a/libs/framework/gtest/src/CxxFrameworkFactoryTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkFactoryTestSuite.cc @@ -23,18 +23,17 @@ #include "celix/FrameworkFactory.h" -class CxxFrameworkFactoryTestSuite : public ::testing::Test { +class FrameworkFactoryTestSuite : public ::testing::Test { public: - std::shared_ptr framework{}; - std::vector> registrations{}; + FrameworkFactoryTestSuite() = default; }; -TEST_F(CxxFrameworkFactoryTestSuite, CreateDestroy) { - auto fw = celix::createFramework(); - EXPECT_TRUE(fw->getFrameworkBundleContext()->getBundle().isSystemBundle()); +TEST_F(FrameworkFactoryTestSuite, CreateDestroy) { + auto framework = celix::createFramework(); + EXPECT_TRUE(framework->getFrameworkBundleContext()->getBundle().isSystemBundle()); } -TEST_F(CxxFrameworkFactoryTestSuite, WaitForStop) { +TEST_F(FrameworkFactoryTestSuite, WaitForStop) { auto fw = celix::createFramework(); std::future sync; std::thread wait{[fw] { @@ -46,9 +45,10 @@ TEST_F(CxxFrameworkFactoryTestSuite, WaitForStop) { struct cService{}; -TEST_F(CxxFrameworkFactoryTestSuite, DestroyFrameworkAndUnregisterServices) { - framework = celix::createFramework(); +TEST_F(FrameworkFactoryTestSuite, DestroyFrameworkAndUnregisterServices) { + auto framework = celix::createFramework(); auto ctx = framework->getFrameworkBundleContext(); + std::vector> registrations{}; for (int i = 0; i < 10; ++i) { auto reg = ctx->registerService(std::make_shared()) .build(); diff --git a/libs/framework/gtest/src/FrameworkFactoryWithErrorInjectionTestSuite.cc b/libs/framework/gtest/src/FrameworkFactoryWithErrorInjectionTestSuite.cc new file mode 100644 index 000000000..d8b4241cb --- /dev/null +++ b/libs/framework/gtest/src/FrameworkFactoryWithErrorInjectionTestSuite.cc @@ -0,0 +1,61 @@ +/* + * 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 + +#include "celix/FrameworkFactory.h" +#include "celix_framework_factory.h" + +#include "celix_properties_ei.h" +#include "malloc_ei.h" + +class FrameworkFactoryWithErrorInjectionTestSuite : public ::testing::Test { +public: + FrameworkFactoryWithErrorInjectionTestSuite() = default; + + ~FrameworkFactoryWithErrorInjectionTestSuite() noexcept override { + //reset the error injection + celix_ei_expect_malloc(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_create(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_copy(nullptr, 0, nullptr); + } +}; + +TEST_F(FrameworkFactoryWithErrorInjectionTestSuite, ErrorCreatingFrameworkTest) { + //When an error injection for celix_properties_copy is primed when called from anywhere + celix_ei_expect_celix_properties_copy(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + //Then an exception is expected when creating a framework instance + EXPECT_ANY_THROW(celix::createFramework()); + + //When an error injection for celix_properties_create is primed when called from + //celix_frameworkFactory_createFramework + celix_ei_expect_celix_properties_create((void*)celix_frameworkFactory_createFramework, 0, nullptr); + + //Then a nullptr is returned when calling celix_frameworkFactory_createFramework with a nullptr properties + //(note celix::createFramework will always call celix_frameworkFactory_createFramework with a non-null properties) + EXPECT_EQ(nullptr, celix_frameworkFactory_createFramework(nullptr)); + + //When an error injection for calloc is primed when called (indirectly) from celix_frameworkFactory_createFramework + celix_ei_expect_calloc((void*)celix_frameworkFactory_createFramework, 1, nullptr); + + //Then an exception is expected when creating a framework instance + EXPECT_ANY_THROW(celix::createFramework()); +} + diff --git a/libs/framework/include/celix/Framework.h b/libs/framework/include/celix/Framework.h index e0e62eeb6..ae86923bc 100644 --- a/libs/framework/include/celix/Framework.h +++ b/libs/framework/include/celix/Framework.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include "celix_framework.h" @@ -105,7 +106,7 @@ namespace celix { * @warning Try not the depend on the C API from a C++ bundle. If features are missing these should be added to * the C++ API. */ - celix_framework_t * getCFramework() const { + celix_framework_t* getCFramework() const { return cFw.get(); } private: diff --git a/libs/framework/include/celix/FrameworkFactory.h b/libs/framework/include/celix/FrameworkFactory.h index 4f1f3a2e9..4872a8b03 100644 --- a/libs/framework/include/celix/FrameworkFactory.h +++ b/libs/framework/include/celix/FrameworkFactory.h @@ -22,24 +22,31 @@ #include #include "celix/Properties.h" -#include "celix/BundleContext.h" #include "celix/Framework.h" +#include "celix/BundleContext.h" #include "celix_framework_factory.h" namespace celix { - /** - * @brief Create a new celix Framework instance. - */ - inline std::shared_ptr createFramework(const celix::Properties& properties = {}) { - auto* copy = celix_properties_copy(properties.getCProperties()); - auto* cFw= celix_frameworkFactory_createFramework(copy); - auto fwCtx = std::make_shared(celix_framework_getFrameworkContext(cFw)); - std::shared_ptr framework{new celix::Framework{std::move(fwCtx), cFw}, [](celix::Framework* fw) { - auto* cFw = fw->getCFramework(); - delete fw; - celix_framework_waitForEmptyEventQueue(cFw); - celix_frameworkFactory_destroyFramework(cFw); - }}; - return framework; +/** + * @brief Create a new celix Framework instance. + * @throws celix::Exception if the framework could not be created. + */ +inline std::shared_ptr createFramework(const celix::Properties& properties = {}) { + auto* copy = celix_properties_copy(properties.getCProperties()); + if (!copy) { + throw celix::Exception{"Could not copy properties"}; } -} \ No newline at end of file + + auto* cFw = celix_frameworkFactory_createFramework(copy); + if (!cFw) { + throw celix::Exception{"Could not create framework"}; + } + + auto fwCtx = std::make_shared(celix_framework_getFrameworkContext(cFw)); + return std::shared_ptr{new celix::Framework{std::move(fwCtx), cFw}, [](celix::Framework* fw) { + auto* cFw = fw->getCFramework(); + delete fw; + celix_frameworkFactory_destroyFramework(cFw); + }}; +} +} // namespace celix \ No newline at end of file diff --git a/libs/framework/include/celix_framework_factory.h b/libs/framework/include/celix_framework_factory.h index 73b97ae09..bbb69c3d7 100644 --- a/libs/framework/include/celix_framework_factory.h +++ b/libs/framework/include/celix_framework_factory.h @@ -29,8 +29,9 @@ extern "C" { /** * Creates a new framework. The framework will be in the started state. - * @param config The framework configuration. Can be NULL. - * @return a started framework or NULL + * @param[in] config The framework configuration. Can be NULL. This call will take ownership of the config and + * also destroy it when the call fails. + * @return a started framework or NULL. If NULL is returned the framework is not started. */ CELIX_FRAMEWORK_EXPORT celix_framework_t* celix_frameworkFactory_createFramework(celix_properties_t *config); diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c index 00e2aecb6..65c91b8e5 100644 --- a/libs/framework/src/celix_framework_bundle.c +++ b/libs/framework/src/celix_framework_bundle.c @@ -62,20 +62,28 @@ static celix_status_t celix_frameworkBundle_frameworkEvent(void* handle, framewo celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** userData) { *userData = NULL; celix_framework_bundle_activator_t* act = calloc(1, sizeof(*act)); - if (act) { - act->ctx = ctx; - act->trueConditionSvcId = -1L; - act->listener.handle = act; - act->listener.frameworkEvent = celix_frameworkBundle_frameworkEvent; - act->frameworkStartedEventReceived = false; - act->frameworkErrorEventReceived = false; - act->frameworkReadyOrErrorConditionSvcId = -1L; - act->checkFrameworkScheduledEventId = -1L; - act->conditionInstance.handle = act; - celixThreadMutex_create(&act->mutex, NULL); - *userData = act; + if (!act) { + return ENOMEM; + } + + celix_status_t status = celixThreadMutex_create(&act->mutex, NULL); + if (status != CELIX_SUCCESS) { + free(act); + return status; } - return act != NULL ? CELIX_SUCCESS : CELIX_ENOMEM; + + act->ctx = ctx; + act->trueConditionSvcId = -1L; + act->listener.handle = act; + act->listener.frameworkEvent = celix_frameworkBundle_frameworkEvent; + act->frameworkStartedEventReceived = false; + act->frameworkErrorEventReceived = false; + act->frameworkReadyOrErrorConditionSvcId = -1L; + act->checkFrameworkScheduledEventId = -1L; + act->conditionInstance.handle = act; + *userData = act; + + return CELIX_SUCCESS; } static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_activator_t* act) { @@ -92,7 +100,7 @@ static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_a } } -static void celix_frameworkBundle_readyCheck(void* data) { +void celix_frameworkBundle_readyCheck(void* data) { celix_framework_bundle_activator_t* act = data; celixThreadMutex_lock(&act->mutex); @@ -141,21 +149,25 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ bool conditionsEnabled = celix_bundleContext_getPropertyAsBool( ctx, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT); - if (conditionsEnabled) { - celix_frameworkBundle_registerTrueCondition(act); - fw_addFrameworkListener( - celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); - celix_frameworkBundle_startReadyCheck(act); - return act->trueConditionSvcId >= 0 ? CELIX_SUCCESS : CELIX_BUNDLE_EXCEPTION; + if (!conditionsEnabled) { + return CELIX_SUCCESS; } + celix_frameworkBundle_registerTrueCondition(act); + if (act->trueConditionSvcId < 0) { + return CELIX_BUNDLE_EXCEPTION; + } + + fw_addFrameworkListener( + celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); + celix_frameworkBundle_startReadyCheck(act); + return CELIX_SUCCESS; } celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t* ctx) { celix_framework_bundle_activator_t* act = userData; celix_framework_t* framework = celix_bundleContext_getFramework(ctx); - celix_status_t status = CELIX_SUCCESS; // remove framework listener fw_removeFrameworkListener(framework, celix_bundleContext_getBundle(ctx), &act->listener); @@ -179,7 +191,7 @@ celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t // framework shutdown celix_framework_shutdownAsync(framework); - return status; + return CELIX_SUCCESS; } celix_status_t celix_frameworkBundle_destroy(void* userData, celix_bundle_context_t* ctx __attribute__((unused))) { diff --git a/libs/framework/src/celix_framework_bundle.h b/libs/framework/src/celix_framework_bundle.h index 29776bc54..5a43cd032 100644 --- a/libs/framework/src/celix_framework_bundle.h +++ b/libs/framework/src/celix_framework_bundle.h @@ -47,6 +47,13 @@ celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t */ celix_status_t celix_frameworkBundle_destroy(void* userData, celix_bundle_context_t* ctx); +/** + * @brief The scheduled event callback for the framework bundle bundle ready check. + * @note Part of the header for testing purposes. + * @param[in] data The framework bundle bundle activator. + */ +void celix_frameworkBundle_readyCheck(void* data); + #ifdef __cplusplus } #endif diff --git a/libs/framework/src/celix_framework_factory.c b/libs/framework/src/celix_framework_factory.c index f97b8c54c..c3b8f95a1 100644 --- a/libs/framework/src/celix_framework_factory.c +++ b/libs/framework/src/celix_framework_factory.c @@ -27,16 +27,22 @@ framework_t* celix_frameworkFactory_createFramework(celix_properties_t *config) config = celix_properties_create(); } - if (config != NULL) { - framework_create(&fw, config); + if (config == NULL) { + return NULL; } - if (fw != NULL) { - celix_status_t rc = framework_start(fw); - if (rc != CELIX_SUCCESS) { - framework_destroy(fw); - fw = NULL; - } + + celix_status_t status = framework_create(&fw, config); + if (status != CELIX_SUCCESS) { + celix_properties_destroy(config); + return NULL; } + + status = framework_start(fw); + if (status != CELIX_SUCCESS) { + framework_destroy(fw); + return NULL; + } + return fw; } diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index d84b2a229..88633507b 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -183,6 +183,7 @@ static celix_status_t framework_autoInstallConfiguredBundles(celix_framework_t * static celix_status_t framework_autoInstallConfiguredBundlesForList(celix_framework_t *fw, const char *autoStart, celix_array_list_t *installedBundles); static celix_status_t framework_autoStartConfiguredBundlesForList(celix_framework_t* fw, const celix_array_list_t *installedBundles); static void celix_framework_addToEventQueue(celix_framework_t *fw, const celix_framework_event_t* event); +static void celix_framework_stopAndJoinEventQueue(celix_framework_t* fw); struct fw_bundleListener { bundle_pt bundle; @@ -237,6 +238,9 @@ typedef struct fw_frameworkListener * fw_framework_listener_pt; celix_status_t framework_create(framework_pt *out, celix_properties_t* config) { celix_framework_t* framework = calloc(1, sizeof(*framework)); + if (!framework) { + return ENOMEM; + } celixThreadCondition_init(&framework->shutdown.cond, NULL); celixThreadMutex_create(&framework->shutdown.mutex, NULL); @@ -442,9 +446,14 @@ celix_status_t fw_init(framework_pt framework) { status = CELIX_DO_IF(status, bundle_setActivator(framework->bundle, activator)); status = CELIX_DO_IF(status, bundle_getContext(framework->bundle, &validateContext)); status = CELIX_DO_IF(status, activator->create(validateContext, &activator->userData)); + bool fwBundleCreated = status == CELIX_SUCCESS; status = CELIX_DO_IF(status, activator->start(activator->userData, validateContext)); + if (status != CELIX_SUCCESS) { + if (fwBundleCreated) { + activator->destroy(activator->userData, validateContext); + } free(activator); } } else { @@ -453,7 +462,8 @@ celix_status_t fw_init(framework_pt framework) { } if (status != CELIX_SUCCESS) { - fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not init framework"); + fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not init framework"); + celix_framework_stopAndJoinEventQueue(framework); } return status; @@ -475,6 +485,11 @@ celix_status_t framework_start(celix_framework_t* framework) { bundle_setState(framework->bundle, CELIX_BUNDLE_STATE_ACTIVE); } + if (status != CELIX_SUCCESS) { + fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Could not initialize framework"); + return status; + } + celix_framework_bundle_entry_t* entry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(framework, framework->bundleId); CELIX_DO_IF(status, fw_fireBundleEvent(framework, OSGI_FRAMEWORK_BUNDLE_EVENT_STARTED, entry)); @@ -482,8 +497,9 @@ celix_status_t framework_start(celix_framework_t* framework) { if (status != CELIX_SUCCESS) { status = CELIX_BUNDLE_EXCEPTION; - fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not start framework"); + fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not start framework bundle"); fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, status); + return status; } celix_status_t startStatus = framework_autoStartConfiguredBundles(framework); @@ -1181,6 +1197,19 @@ celix_status_t framework_waitForStop(framework_pt framework) { return CELIX_SUCCESS; } +static void celix_framework_stopAndJoinEventQueue(celix_framework_t* fw) { + fw_log(fw->logger, + CELIX_LOG_LEVEL_TRACE, + "Stop and joining event loop thread for framework %s", + celix_framework_getUUID(fw)); + celixThreadMutex_lock(&fw->dispatcher.mutex); + fw->dispatcher.active = false; + celixThreadCondition_broadcast(&fw->dispatcher.cond); + celixThreadMutex_unlock(&fw->dispatcher.mutex); + celixThread_join(fw->dispatcher.thread, NULL); + fw_log(fw->logger, CELIX_LOG_LEVEL_DEBUG, "Joined event loop thread for framework %s", celix_framework_getUUID(fw)); +} + static void* framework_shutdown(void *framework) { framework_pt fw = (framework_pt) framework; @@ -1236,14 +1265,7 @@ static void* framework_shutdown(void *framework) { celix_framework_bundleEntry_decreaseUseCount(fwEntry); } - //join dispatcher thread - celixThreadMutex_lock(&fw->dispatcher.mutex); - fw->dispatcher.active = false; - celixThreadCondition_broadcast(&fw->dispatcher.cond); - celixThreadMutex_unlock(&fw->dispatcher.mutex); - celixThread_join(fw->dispatcher.thread, NULL); - fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "Joined event loop thread for framework %s", celix_framework_getUUID(framework)); - + celix_framework_stopAndJoinEventQueue(fw); celixThreadMutex_lock(&fw->shutdown.mutex); fw->shutdown.done = true; From 1ace083c3e753fac2b133c7705f7440bd28ce74c Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 1 Jul 2023 14:13:23 +0200 Subject: [PATCH 07/33] Add framework celix_condition services documentation --- documents/framework.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/documents/framework.md b/documents/framework.md index d697bd4a7..91d9dbb4f 100644 --- a/documents/framework.md +++ b/documents/framework.md @@ -206,9 +206,32 @@ add_executable(create_framework_with_celix_launcher src/launcher.c) target_link_libraries(create_framework_with_celix_launcher PRIVATE Celix::framework) ``` -## Framework Conditions - -TODO +## Framework `celix_condition` Services +In a dynamic framework such as Apache Celix, it can sometimes be challenging to ascertain when the framework or +certain parts of a dynamic service-based application are ready for use. To address this issue, Apache Celix provides +services known as `celix_condition` services. + +A `celix_condition` service is a registered marker interface with a "condition.id" service property. +The service's availability signifies that the condition identified by the "condition.id" has been met. + +The `celix_condition` service is an adaptation of the +[OSGi 8 Condition Service Specification](https://docs.osgi.org/specification/osgi.core/8.0.0/service.condition.html). + +The Apache Celix framework will provide the following `celix_condition` services for the respective states: + +- Celix condition "true", which is always available. +- Celix condition "framework.ready". This service will be registered when the framework has successfully and fully + started, which includes installing and starting all configured bundles and services, and ensuring the event queue is + empty after all configured bundles have been started. Note that the "framework.ready" condition is not part of the + OSGi condition specification. +- Celix condition "framework.error". This service will be registered when the framework has not started successfully. + This can occur if any of the configured bundles fail to start or install. Note that the "framework.error" condition + is not part of the OSGi condition specification. + +Contrary to the OSGi specification, the Apache Celix framework does not provide a public API for adding or removing +framework listeners. Instead, framework condition services can be used. This has the advantage of ensuring no +framework state changes can be missed, because the state indicating condition services will be available +until the framework is stopped. ## Framework bundle cache The Apache Celix framework uses a bundle cache directory to store the installed bundles, their state and for a From f07f6e2d9905f7213606617132fdd59806e6d5b2 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 6 Jul 2023 22:50:49 +0200 Subject: [PATCH 08/33] Add component.ready framework condition --- .../gtest/src/CondTestBundleActivator.cc | 21 ++- .../gtest/src/FrameworkBundleTestSuite.cc | 40 ++++-- ...meworkBundleWithErrorInjectionTestSuite.cc | 28 +++- libs/framework/include/celix_condition.h | 8 ++ libs/framework/src/celix_framework_bundle.c | 121 ++++++++++-------- libs/framework/src/celix_framework_bundle.h | 12 +- libs/framework/src/framework.c | 2 + 7 files changed, 159 insertions(+), 73 deletions(-) diff --git a/libs/framework/gtest/src/CondTestBundleActivator.cc b/libs/framework/gtest/src/CondTestBundleActivator.cc index 2f6ac9834..e349cbcc8 100644 --- a/libs/framework/gtest/src/CondTestBundleActivator.cc +++ b/libs/framework/gtest/src/CondTestBundleActivator.cc @@ -20,12 +20,27 @@ #include "celix/BundleActivator.h" #include "celix_condition.h" -class CndTestBundleActivator { +/** + * @brief Empty Test Component for testing the condition service + */ +class CondComponent { +public: + CondComponent() = default; +}; + +class CondTestBundleActivator { public: - explicit CndTestBundleActivator(const std::shared_ptr& ctx) { + explicit CondTestBundleActivator(const std::shared_ptr& ctx) { registration = ctx->registerUnmanagedService(&condition, CELIX_CONDITION_SERVICE_NAME) .addProperty(CELIX_CONDITION_ID, "test") .build(); + + auto& cmp = ctx->getDependencyManager()->createComponent(); + cmp.createServiceDependency(CELIX_CONDITION_SERVICE_NAME) + .setFilter("(condition.id=does-not-exists)") + .setRequired(true); + + cmp.build(); } private: @@ -33,4 +48,4 @@ class CndTestBundleActivator { celix_condition condition{}; }; -CELIX_GEN_CXX_BUNDLE_ACTIVATOR(CndTestBundleActivator) \ No newline at end of file +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(CondTestBundleActivator) \ No newline at end of file diff --git a/libs/framework/gtest/src/FrameworkBundleTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc index 1eb7c6261..bd5d6985e 100644 --- a/libs/framework/gtest/src/FrameworkBundleTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc @@ -27,15 +27,17 @@ class FrameworkBundleTestSuite : public ::testing::Test { public: const int USE_SERVICE_TIMEOUT_IN_MS = 500; const std::string trueFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_TRUE + ")"; - const std::string readyFilter = + const std::string frameworkReadyFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; - const std::string errorFilter = + const std::string frameworkErrorFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; + const std::string componentsReadyFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; FrameworkBundleTestSuite() = default; }; -TEST_F(FrameworkBundleTestSuite, ConditionTrueAndFrameworkReadyTest) { +TEST_F(FrameworkBundleTestSuite, ConditionTrueFrameworkReadyAndComponentsReadyTest) { // Given a Celix framework (with conditions enabled (default)) auto fw = celix::createFramework(); auto ctx = fw->getFrameworkBundleContext(); @@ -46,14 +48,21 @@ TEST_F(FrameworkBundleTestSuite, ConditionTrueAndFrameworkReadyTest) { // And the condition service with id "framework.error" will not become available count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(errorFilter) + .setFilter(frameworkErrorFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(0, count); // But the condition service with id "framework.ready" will become available count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(readyFilter) + .setFilter(frameworkReadyFilter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(1, count); + + // And the condition service with id "components.ready" will become available + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); @@ -70,20 +79,20 @@ TEST_F(FrameworkBundleTestSuite, ConditionTrueAndFrameworkErrorTest) { // And the condition service with id "framework.ready" does not become available (framework startup error) count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(readyFilter) + .setFilter(frameworkReadyFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(0, count); // But the condition service with id "framework.error" will become available count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(errorFilter) + .setFilter(frameworkErrorFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); } -TEST_F(FrameworkBundleTestSuite, FrameworkReadyRegisteredLastTest) { +TEST_F(FrameworkBundleTestSuite, FrameworkReadyRegisteredLaterTest) { // Given a Celix framework which is configured to start a bundle with a condition test service auto fw = celix::createFramework({{celix::AUTO_START_0, COND_TEST_BUNDLE_LOC}}); auto ctx = fw->getFrameworkBundleContext(); @@ -94,14 +103,14 @@ TEST_F(FrameworkBundleTestSuite, FrameworkReadyRegisteredLastTest) { // And the condition service with id "framework.error" will not become available count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(errorFilter) + .setFilter(frameworkErrorFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(0, count); // But the condition service with id "framework.ready" will become available count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(readyFilter) + .setFilter(frameworkReadyFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); @@ -116,7 +125,16 @@ TEST_F(FrameworkBundleTestSuite, FrameworkReadyRegisteredLastTest) { // And the service.id of the framework.ready condition is higher than the service.id of the test condition //(white-box test, framework.ready condition is registered last) - long readySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, readyFilter); + long readySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, frameworkReadyFilter); long testySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, testFilter); EXPECT_GT(readySvcId, testySvcId); + + // And the "components.ready" condition is not available, because the test bundle contains a component which will + // not become active + count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(0, count); } + diff --git a/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc index 3f9f4938f..1feef016f 100644 --- a/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc @@ -30,8 +30,10 @@ class FrameworkBundleWithErrorInjectionTestSuite : public ::testing::Test { public: const int USE_SERVICE_TIMEOUT_IN_MS = 500; - const std::string readyFilter = + const std::string frameworkReadyFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + const std::string componentsReadyFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; FrameworkBundleWithErrorInjectionTestSuite() = default; @@ -59,7 +61,7 @@ TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErroCreatingFrameworkBundleTe } TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorStartingFrameworkBundleTest) { - // Wen an error injection for celix_properties_create is primed when called (indirectly) from + // When an error injection for celix_properties_create is primed when called (indirectly) from // celix_frameworkBundle_start celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_start, 1, nullptr); @@ -68,8 +70,8 @@ TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorStartingFrameworkBundleT } TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringFrameworkReadyConditionTest) { - // When an error injection for celix_properties_create is primed when called from celix_frameworkBundle_readyCheck - celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_readyCheck, 0, nullptr); + // When an error injection for celix_properties_create is primed when called from celix_frameworkBundle_handleFrameworkEvent + celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_handleFrameworkEvent, 0, nullptr); // And a framework instance is created auto fw = celix::createFramework(); @@ -77,7 +79,23 @@ TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringFrameworkRead // Then the framework.ready condition will not become available, due to an error creating properties for the service auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(readyFilter) + .setFilter(frameworkReadyFilter) + .setTimeout(std::chrono::milliseconds {USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(count, 0); +} + +TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringComponentsReadyConditionTest) { + // When an error injection for celix_properties_create is primed when called from celix_frameworkBundle_componentsCheck + celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_componentsCheck, 0, nullptr); + + // And a framework instance is created + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // Then the components.ready condition will not become available, due to an error creating properties for the service + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) .setTimeout(std::chrono::milliseconds {USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(count, 0); diff --git a/libs/framework/include/celix_condition.h b/libs/framework/include/celix_condition.h index ee6642ce3..f901e61c5 100644 --- a/libs/framework/include/celix_condition.h +++ b/libs/framework/include/celix_condition.h @@ -89,6 +89,14 @@ typedef struct celix_condition { */ #define CELIX_CONDITION_ID_FRAMEWORK_ERROR "framework.error" +/*! + * @brief The unique identifier for the components.ready condition. + * + * The components ready condition is registered by the framework if the framework.ready condition is registered + * and all components active. + */ +#define CELIX_CONDITION_ID_COMPONENTS_READY "components.ready" + #ifdef __cplusplus } #endif diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c index 65c91b8e5..883d5c507 100644 --- a/libs/framework/src/celix_framework_bundle.c +++ b/libs/framework/src/celix_framework_bundle.c @@ -22,6 +22,7 @@ #include "celix_condition.h" #include "celix_constants.h" #include "celix_threads.h" +#include "celix_dependency_manager.h" #include "framework_private.h" #define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT true @@ -36,29 +37,13 @@ typedef struct celix_framework_bundle_activator { celix_thread_mutex_t mutex; /**< protects below. */ long trueConditionSvcId; /**< service id of the condition service which is always true. */ - bool frameworkStartedEventReceived; /**< true if the framework started event is received. */ - bool frameworkErrorEventReceived; /**< true if the framework error event is received. */ long frameworkReadyOrErrorConditionSvcId; /**< service id of the condition service which is set when the framework is ready or started up with an error */ - long checkFrameworkScheduledEventId; /**< event id of the scheduled event to check if the framework is ready. */ + long checkComponentsScheduledEventId; /**< event id of the scheduled event to check if the framework is ready. */ + long componentsReadyConditionSvcId; /**< service id of the condition service which is set when all components are + ready. */ } celix_framework_bundle_activator_t; -static celix_status_t celix_frameworkBundle_frameworkEvent(void* handle, framework_event_t* event) { - framework_listener_t* listener = handle; - celix_framework_bundle_activator_t* act = listener->handle; - if (event->type == OSGI_FRAMEWORK_EVENT_STARTED || event->type == OSGI_FRAMEWORK_EVENT_ERROR) { - celixThreadMutex_lock(&act->mutex); - if (event->type == OSGI_FRAMEWORK_EVENT_STARTED) { - act->frameworkStartedEventReceived = true; - } else { - act->frameworkErrorEventReceived = true; - } - celix_bundleContext_wakeupScheduledEvent(act->ctx, act->checkFrameworkScheduledEventId); - celixThreadMutex_unlock(&act->mutex); - } - return CELIX_SUCCESS; -} - celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** userData) { *userData = NULL; celix_framework_bundle_activator_t* act = calloc(1, sizeof(*act)); @@ -75,11 +60,10 @@ celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** act->ctx = ctx; act->trueConditionSvcId = -1L; act->listener.handle = act; - act->listener.frameworkEvent = celix_frameworkBundle_frameworkEvent; - act->frameworkStartedEventReceived = false; - act->frameworkErrorEventReceived = false; + act->listener.frameworkEvent = celix_frameworkBundle_handleFrameworkEvent; act->frameworkReadyOrErrorConditionSvcId = -1L; - act->checkFrameworkScheduledEventId = -1L; + act->checkComponentsScheduledEventId = -1L; + act->componentsReadyConditionSvcId = -1L; act->conditionInstance.handle = act; *userData = act; @@ -100,48 +84,79 @@ static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_a } } -void celix_frameworkBundle_readyCheck(void* data) { - celix_framework_bundle_activator_t* act = data; - celixThreadMutex_lock(&act->mutex); - - bool ready = act->frameworkStartedEventReceived && - celix_framework_isEventQueueEmpty(celix_bundleContext_getFramework(act->ctx)); - bool error = act->frameworkErrorEventReceived; +celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framework_event_t* event) { + framework_listener_t* listener = handle; + celix_framework_bundle_activator_t* act = listener->handle; + if (event->type == OSGI_FRAMEWORK_EVENT_STARTED || event->type == OSGI_FRAMEWORK_EVENT_ERROR) { + celixThreadMutex_lock(&act->mutex); - if (ready || error) { celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; opts.serviceName = CELIX_CONDITION_SERVICE_NAME; opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; opts.svc = &act->conditionInstance; opts.properties = celix_properties_create(); if (opts.properties) { - if (ready) { + if (event->type == OSGI_FRAMEWORK_EVENT_STARTED) { celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); + celix_bundleContext_log( + act->ctx, + CELIX_LOG_LEVEL_DEBUG, + "Framework started event received -> registering framework.ready condition service"); } else /*error*/ { celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_ERROR); - celix_bundleContext_log(act->ctx, - CELIX_LOG_LEVEL_INFO, - "Framework error received -> no framework.ready condition service will be registered"); + celix_bundleContext_log( + act->ctx, + CELIX_LOG_LEVEL_INFO, + "Framework error event received -> registering framework.error condition service"); } - celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_INFO, "Registering framework.ready/framework.error condition service %s", celix_properties_get(opts.properties, CELIX_CONDITION_ID, "!ERROR!")); - act->frameworkReadyOrErrorConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); + act->frameworkReadyOrErrorConditionSvcId = + celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); + celix_bundleContext_wakeupScheduledEvent(act->ctx, act->checkComponentsScheduledEventId); + } else { + celix_bundleContext_log(act->ctx, + CELIX_LOG_LEVEL_ERROR, + "Cannot create properties for framework.ready/framework.error condition service"); + } + celixThreadMutex_unlock(&act->mutex); + } + return CELIX_SUCCESS; +} + +void celix_frameworkBundle_componentsCheck(void* data) { + celix_framework_bundle_activator_t* act = data; + celix_dependency_manager_t* mng = celix_bundleContext_getDependencyManager(act->ctx); + + celixThreadMutex_lock(&act->mutex); + bool allComponentsActive = celix_dependencyManager_allComponentsActive(mng); + bool ready = allComponentsActive && act->frameworkReadyOrErrorConditionSvcId >= 0; + if (ready) { + celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + opts.svc = &act->conditionInstance; + opts.properties = celix_properties_create(); + if (opts.properties) { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_COMPONENTS_READY); + celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_DEBUG, "Registering components.ready condition service"); + act->componentsReadyConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); } else { celix_bundleContext_log( - act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for framework.ready/framework.error condition service"); + act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for components.ready condition service"); } - celix_bundleContext_removeScheduledEventAsync(act->ctx, act->checkFrameworkScheduledEventId); - act->checkFrameworkScheduledEventId = -1L; + celix_bundleContext_removeScheduledEventAsync(act->ctx, act->checkComponentsScheduledEventId); + act->checkComponentsScheduledEventId = -1L; } celixThreadMutex_unlock(&act->mutex); } -static void celix_frameworkBundle_startReadyCheck(celix_framework_bundle_activator_t* act) { +static void celix_frameworkBundle_startComponentsCheck(celix_framework_bundle_activator_t* act) { celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; - opts.name = "celix_frameworkBundle_readyCheck"; - opts.callback = celix_frameworkBundle_readyCheck; + opts.name = "celix_frameworkBundle_componentsCheck"; + opts.callback = celix_frameworkBundle_componentsCheck; opts.callbackData = act; + opts.initialDelayInSeconds = 1; //note will be wakeup by framework event opts.intervalInSeconds = 0.001; - act->checkFrameworkScheduledEventId = celix_bundleContext_scheduleEvent(act->ctx, &opts); + act->checkComponentsScheduledEventId = celix_bundleContext_scheduleEvent(act->ctx, &opts); } celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_t* ctx) { @@ -160,7 +175,7 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ fw_addFrameworkListener( celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); - celix_frameworkBundle_startReadyCheck(act); + celix_frameworkBundle_startComponentsCheck(act); return CELIX_SUCCESS; } @@ -174,20 +189,24 @@ celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t // stop ready check celixThreadMutex_lock(&act->mutex); - long checkEventId = act->checkFrameworkScheduledEventId; - act->checkFrameworkScheduledEventId = -1L; + long checkEventId = act->checkComponentsScheduledEventId; + act->checkComponentsScheduledEventId = -1L; celixThreadMutex_unlock(&act->mutex); celix_bundleContext_removeScheduledEvent(ctx, checkEventId); // remove framework true condition service and - if present - framework.ready condition service, celixThreadMutex_lock(&act->mutex); - long trueSvcId = act->trueConditionSvcId; - long readyOrErrorSvcId = act->frameworkReadyOrErrorConditionSvcId; + long trueConditionSvcId = act->trueConditionSvcId; + long frameworkReadyOrErrorConditionSvcId = act->frameworkReadyOrErrorConditionSvcId; + long componentsReadyConditionSvcId = act->componentsReadyConditionSvcId; act->trueConditionSvcId = -1L; act->frameworkReadyOrErrorConditionSvcId = -1L; + act->componentsReadyConditionSvcId = -1L; celixThreadMutex_unlock(&act->mutex); - celix_bundleContext_unregisterService(ctx, readyOrErrorSvcId); - celix_bundleContext_unregisterService(ctx, trueSvcId); + + celix_bundleContext_unregisterService(ctx, componentsReadyConditionSvcId); + celix_bundleContext_unregisterService(ctx, frameworkReadyOrErrorConditionSvcId); + celix_bundleContext_unregisterService(ctx, trueConditionSvcId); // framework shutdown celix_framework_shutdownAsync(framework); diff --git a/libs/framework/src/celix_framework_bundle.h b/libs/framework/src/celix_framework_bundle.h index 5a43cd032..850cf9686 100644 --- a/libs/framework/src/celix_framework_bundle.h +++ b/libs/framework/src/celix_framework_bundle.h @@ -22,6 +22,7 @@ #include "celix_errno.h" #include "celix_bundle_context.h" +#include "framework_event.h" #ifdef __cplusplus extern "C" { @@ -48,11 +49,16 @@ celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t celix_status_t celix_frameworkBundle_destroy(void* userData, celix_bundle_context_t* ctx); /** - * @brief The scheduled event callback for the framework bundle bundle ready check. + * @brief The framework bundle framework event handler. * @note Part of the header for testing purposes. - * @param[in] data The framework bundle bundle activator. */ -void celix_frameworkBundle_readyCheck(void* data); +celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framework_event_t* event); + +/** + * @brief The scheduled event callback for the framework components.ready check. + * @note Part of the header for testing purposes. + */ +void celix_frameworkBundle_componentsCheck(void* data); #ifdef __cplusplus } diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index 745c4f519..a33530343 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -480,6 +480,8 @@ celix_status_t framework_start(celix_framework_t* framework) { celix_status_t startStatus = framework_autoStartConfiguredBundles(framework); celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); if (startStatus == CELIX_SUCCESS && installStatus == CELIX_SUCCESS) { + //fire started event if all bundles are started/installed and the event queue is empty + celix_framework_waitForEmptyEventQueue(framework); fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, CELIX_SUCCESS); } else { //note not returning a error, because the framework is started, but not all bundles are started/installed From ba48303b694cd540ea6d21707be4f914679069bc Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Mon, 10 Jul 2023 23:04:01 +0200 Subject: [PATCH 09/33] Cleanup framework_start --- libs/framework/include_deprecated/framework.h | 12 +++++++ libs/framework/src/celix_framework_bundle.c | 20 ++++++------ libs/framework/src/framework.c | 31 +++++++------------ 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/libs/framework/include_deprecated/framework.h b/libs/framework/include_deprecated/framework.h index d184eb2f2..e3c45ada3 100644 --- a/libs/framework/include_deprecated/framework.h +++ b/libs/framework/include_deprecated/framework.h @@ -32,8 +32,20 @@ extern "C" { CELIX_FRAMEWORK_DEPRECATED_EXPORT celix_status_t framework_create(celix_framework_t **framework, celix_properties_t *config); +/** + * @brief Start the framework. + * @note Not thread safe. + * @param[in] framework The framework to start. + * @return CELIX_SUCCESS if the framework is started. + */ CELIX_FRAMEWORK_DEPRECATED_EXPORT celix_status_t framework_start(celix_framework_t *framework); +/** + * @brief Stop the framework. + * @note Not thread safe. + * @param[in] framework The framework to stop. + * @return CELIX_SUCCESS if the framework is stopped. + */ CELIX_FRAMEWORK_DEPRECATED_EXPORT celix_status_t framework_stop(celix_framework_t *framework); CELIX_FRAMEWORK_DEPRECATED_EXPORT celix_status_t framework_destroy(celix_framework_t *framework); diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c index 883d5c507..8eb2c06c1 100644 --- a/libs/framework/src/celix_framework_bundle.c +++ b/libs/framework/src/celix_framework_bundle.c @@ -30,7 +30,7 @@ /** * @brief Celix framework bundle activator struct. */ -typedef struct celix_framework_bundle_activator { +typedef struct celix_framework_bundle { celix_bundle_context_t* ctx; celix_condition_t conditionInstance; /**< condition instance which can be used for multiple condition services.*/ framework_listener_t listener; /**< framework listener to check if the framework is ready. */ @@ -42,11 +42,11 @@ typedef struct celix_framework_bundle_activator { long checkComponentsScheduledEventId; /**< event id of the scheduled event to check if the framework is ready. */ long componentsReadyConditionSvcId; /**< service id of the condition service which is set when all components are ready. */ -} celix_framework_bundle_activator_t; +} celix_framework_bundle_t; celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** userData) { *userData = NULL; - celix_framework_bundle_activator_t* act = calloc(1, sizeof(*act)); + celix_framework_bundle_t* act = calloc(1, sizeof(*act)); if (!act) { return ENOMEM; } @@ -70,7 +70,7 @@ celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** return CELIX_SUCCESS; } -static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_activator_t* act) { +static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_t* act) { celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; opts.serviceName = CELIX_CONDITION_SERVICE_NAME; opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; @@ -86,7 +86,7 @@ static void celix_frameworkBundle_registerTrueCondition(celix_framework_bundle_a celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framework_event_t* event) { framework_listener_t* listener = handle; - celix_framework_bundle_activator_t* act = listener->handle; + celix_framework_bundle_t* act = listener->handle; if (event->type == OSGI_FRAMEWORK_EVENT_STARTED || event->type == OSGI_FRAMEWORK_EVENT_ERROR) { celixThreadMutex_lock(&act->mutex); @@ -123,7 +123,7 @@ celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framewor } void celix_frameworkBundle_componentsCheck(void* data) { - celix_framework_bundle_activator_t* act = data; + celix_framework_bundle_t* act = data; celix_dependency_manager_t* mng = celix_bundleContext_getDependencyManager(act->ctx); celixThreadMutex_lock(&act->mutex); @@ -149,7 +149,7 @@ void celix_frameworkBundle_componentsCheck(void* data) { celixThreadMutex_unlock(&act->mutex); } -static void celix_frameworkBundle_startComponentsCheck(celix_framework_bundle_activator_t* act) { +static void celix_frameworkBundle_startComponentsCheck(celix_framework_bundle_t* act) { celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; opts.name = "celix_frameworkBundle_componentsCheck"; opts.callback = celix_frameworkBundle_componentsCheck; @@ -160,7 +160,7 @@ static void celix_frameworkBundle_startComponentsCheck(celix_framework_bundle_ac } celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_t* ctx) { - celix_framework_bundle_activator_t* act = userData; + celix_framework_bundle_t* act = userData; bool conditionsEnabled = celix_bundleContext_getPropertyAsBool( ctx, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT); @@ -181,7 +181,7 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ } celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t* ctx) { - celix_framework_bundle_activator_t* act = userData; + celix_framework_bundle_t* act = userData; celix_framework_t* framework = celix_bundleContext_getFramework(ctx); // remove framework listener @@ -214,7 +214,7 @@ celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t } celix_status_t celix_frameworkBundle_destroy(void* userData, celix_bundle_context_t* ctx __attribute__((unused))) { - celix_framework_bundle_activator_t* act = userData; + celix_framework_bundle_t* act = userData; if (act) { celixThreadMutex_destroy(&act->mutex); free(userData); diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index a3295e1d6..7fa890b0d 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -446,20 +446,19 @@ celix_status_t fw_init(framework_pt framework) { celix_status_t framework_start(celix_framework_t* framework) { celix_status_t status = CELIX_SUCCESS; - bundle_state_e state = CELIX_BUNDLE_STATE_UNKNOWN; + bundle_state_e state = celix_bundle_getState(framework->bundle); - status = CELIX_DO_IF(status, bundle_getState(framework->bundle, &state)); - if (status == CELIX_SUCCESS) { - if ((state == CELIX_BUNDLE_STATE_INSTALLED) || (state == CELIX_BUNDLE_STATE_RESOLVED)) { - status = CELIX_DO_IF(status, fw_init(framework)); - } - } + //framework_start should be called when state is INSTALLED or RESOLVED + bool expectedState = state == CELIX_BUNDLE_STATE_INSTALLED || state == CELIX_BUNDLE_STATE_RESOLVED; - status = CELIX_DO_IF(status, bundle_getState(framework->bundle, &state)); - if (status == CELIX_SUCCESS && state == CELIX_BUNDLE_STATE_STARTING) { - bundle_setState(framework->bundle, CELIX_BUNDLE_STATE_ACTIVE); + if (!expectedState) { + fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Could not start framework, unexpected state %i", state); + return CELIX_ILLEGAL_STATE; } + status = CELIX_DO_IF(status, fw_init(framework)); + status = CELIX_DO_IF(status, bundle_setState(framework->bundle, CELIX_BUNDLE_STATE_ACTIVE)); + if (status != CELIX_SUCCESS) { fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Could not initialize framework"); return status; @@ -467,24 +466,18 @@ celix_status_t framework_start(celix_framework_t* framework) { celix_framework_bundle_entry_t* entry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(framework, framework->bundleId); - CELIX_DO_IF(status, fw_fireBundleEvent(framework, OSGI_FRAMEWORK_BUNDLE_EVENT_STARTED, entry)); + fw_fireBundleEvent(framework, OSGI_FRAMEWORK_BUNDLE_EVENT_STARTED, entry); celix_framework_bundleEntry_decreaseUseCount(entry); - if (status != CELIX_SUCCESS) { - status = CELIX_BUNDLE_EXCEPTION; - fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not start framework bundle"); - fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, status); - return status; - } - celix_status_t startStatus = framework_autoStartConfiguredBundles(framework); celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); + if (startStatus == CELIX_SUCCESS && installStatus == CELIX_SUCCESS) { //fire started event if all bundles are started/installed and the event queue is empty celix_framework_waitForEmptyEventQueue(framework); fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_STARTED, CELIX_SUCCESS); } else { - //note not returning a error, because the framework is started, but not all bundles are started/installed + //note not returning an error, because the framework is started, but not all bundles are started/installed fw_logCode(framework->logger, CELIX_LOG_LEVEL_ERROR, status, "Could not auto start or install all configured bundles"); fw_fireFrameworkEvent(framework, OSGI_FRAMEWORK_EVENT_ERROR, CELIX_BUNDLE_EXCEPTION); } From c329f649fcd9b1985ffe9f65c85614ed4e4618c1 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 11 Jul 2023 23:30:05 +0200 Subject: [PATCH 10/33] Remove unused source files --- .../src/celix_framework_conditions.c | 129 ------------------ .../src/celix_framework_conditions.h | 66 --------- 2 files changed, 195 deletions(-) delete mode 100644 libs/framework/src/celix_framework_conditions.c delete mode 100644 libs/framework/src/celix_framework_conditions.h diff --git a/libs/framework/src/celix_framework_conditions.c b/libs/framework/src/celix_framework_conditions.c deleted file mode 100644 index 2f717a7c6..000000000 --- a/libs/framework/src/celix_framework_conditions.c +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 "celix_condition.h" -#include "celix_constants.h" -#include "framework_private.h" - -void celix_frameworkConditions_registerInitialConditions(celix_framework_t* framework) { - // long svcId = -1L; - // celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); - // celixThreadMutex_lock(&framework->conditions.mutex); - // celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; - // opts.serviceName = CELIX_CONDITION_SERVICE_NAME; - // opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; - // opts.svc = &CELIX_CONDITION_INSTANCE; - // opts.properties = celix_properties_create(); - // if (opts.properties) { - // celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_TRUE); - // svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); - // } - // celix_status_t addStatus = celix_arrayList_addLong(framework->conditions.initialConditionSvcIds, svcId); - // if (addStatus != CELIX_SUCCESS) { - // celix_bundleContext_unregisterService(ctx, svcId); - // fw_log(framework->logger, CELIX_LOG_LEVEL_ERROR, "Error adding initial condition service id to list"); - // } - // celixThreadMutex_unlock(&framework->conditions.mutex); -} - -static void celix_frameworkConditions_checkFrameworkReady(void* data) { - // celix_framework_t* framework = data; - // celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); - - // celixThreadMutex_lock(&framework->dispatcher.mutex); - // int eventQueueSize = - // framework->dispatcher.eventQueueSize + celix_arrayList_size(framework->dispatcher.dynamicEventQueue); - // bool ready = celix_framework_isCurrentThreadTheEventLoop(framework) ? - // eventQueueSize == 1: /*note 1, because celix_frameworkConditions_checkFrameworkReady is called from an event*/ - // eventQueueSize == 0; - // bool cancel = framework->conditions.cancelRegistrations; - // celixThreadMutex_unlock(&framework->dispatcher.mutex); - - // if (cancel) { - // fw_log(framework->logger, - // CELIX_LOG_LEVEL_DEBUG, - // "Framework is stopping or not active anymore, so no need to register the framework ready condition"); - // return; - // } - - // if (ready) { - // celixThreadMutex_lock(&framework->conditions.mutex); - // celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; - // opts.serviceName = CELIX_CONDITION_SERVICE_NAME; - // opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; - // opts.svc = &CELIX_CONDITION_INSTANCE; - // opts.properties = celix_properties_create(); - // if (opts.properties) { - // celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); - // long svcId = celix_bundleContext_registerServiceWithOptionsAsync(ctx, &opts); - // celix_status_t addStatus = - // celix_arrayList_addLong(framework->conditions.frameworkReadyConditionSvcIds, svcId); - // if (addStatus != CELIX_SUCCESS) { - // celix_bundleContext_unregisterService(ctx, svcId); - // fw_log(framework->logger, - // CELIX_LOG_LEVEL_ERROR, - // "Error adding framework ready condition service id to list"); - // } - // } - // celixThreadMutex_unlock(&framework->conditions.mutex); - // } else { - // // try again later on the event queue - // fprintf(stderr, - // "Event queue not empty, so try again later. bundle state is %s\n", - // celix_bundleState_getName(celix_bundle_getState(framework->bundle))); // TODO remove - // celix_framework_fireGenericEvent(framework, - // -1, - // CELIX_FRAMEWORK_BUNDLE_ID, - // "Check event queue for framework ready condition service", - // framework, - // celix_frameworkConditions_checkFrameworkReady, - // NULL, - // NULL); - // } -} - -void celix_frameworkConditions_registerFrameworkReadyConditions(celix_framework_t* framework) { - // note called when all bundles are installed and started - celix_frameworkConditions_checkFrameworkReady(framework); -} - -// TODO rename to cleanup -void celix_frameworkConditions_unregisterConditions(celix_framework_t* framework) { - // celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(framework); - - // celixThreadMutex_lock(&framework->conditions.mutex); - // framework->conditions.cancelRegistrations = true; - // celixThreadMutex_unlock(&framework->conditions.mutex); - // celix_framework_waitUntilNoEventsForBnd(framework, CELIX_FRAMEWORK_BUNDLE_ID); //TODO maybe remove - - // celixThreadMutex_lock(&framework->conditions.mutex); - // for (int i = 0; i < celix_arrayList_size(framework->conditions.initialConditionSvcIds); ++i) { - // long svcId = (long)celix_arrayList_getLong(framework->conditions.initialConditionSvcIds, i); - // fprintf(stderr, "Unregistering svc id %li\n", svcId); - // celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); - // } - // celix_arrayList_clear(framework->conditions.initialConditionSvcIds); - // for (int i = 0; i < celix_arrayList_size(framework->conditions.frameworkReadyConditionSvcIds); ++i) { - // long svcId = (long)celix_arrayList_getLong(framework->conditions.frameworkReadyConditionSvcIds, i); - // fprintf(stderr, "Unregistering svc id %li\n", svcId); - // celix_bundleContext_unregisterServiceAsync(ctx, svcId, NULL, NULL); - // } - // celix_arrayList_clear(framework->conditions.frameworkReadyConditionSvcIds); - // celixThreadMutex_unlock(&framework->conditions.mutex); -} diff --git a/libs/framework/src/celix_framework_conditions.h b/libs/framework/src/celix_framework_conditions.h deleted file mode 100644 index a5ce9fdda..000000000 --- a/libs/framework/src/celix_framework_conditions.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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_CELIX_FRAMEWORK_CONDITIONS_H_ -#define CELIX_CELIX_FRAMEWORK_CONDITIONS_H_ - -#include "celix_errno.h" -#include "celix_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Register the initial celix_condition services for the framework. - * - * The initial celix_condition services are: - * - The true celix_condition service - * - * Will log an error if the initial celix_condition services administration objects or services registrations - * fail. - * - * @param[in] framework The framework. - */ -void celix_frameworkConditions_registerInitialConditions(celix_framework_t* framework); - -/** - * @brief Register the framework ready celix_condition services for the framework. - * - * The framework ready celix_condition services are: - * - The framework.ready celix_condition service - * - * Will log an error if the framework read celix_condition services administration objects or services registrations - * fail. - * - * @param framework The framework. - */ -void celix_frameworkConditions_registerFrameworkReadyConditions(celix_framework_t* framework); - -/** - * @brief Unregister the intial and framework ready celix_condition services for the framework. - * @param framework The framework. - */ -void celix_frameworkConditions_unregisterConditions(celix_framework_t* framework); - -#ifdef __cplusplus -} -#endif - -#endif // CELIX_CELIX_FRAMEWORK_CONDITIONS_H_ From 5cc100dc26c50e199ba25110632809e57e667e10 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 11 Jul 2023 23:30:39 +0200 Subject: [PATCH 11/33] Fix some minor documentation / comments --- documents/framework.md | 3 +++ libs/framework/gtest/src/FrameworkBundleTestSuite.cc | 6 +++--- libs/framework/include/celix/FrameworkFactory.h | 2 +- libs/framework/include/celix_condition.h | 10 +++------- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/documents/framework.md b/documents/framework.md index 91d9dbb4f..c773335cf 100644 --- a/documents/framework.md +++ b/documents/framework.md @@ -227,6 +227,9 @@ The Apache Celix framework will provide the following `celix_condition` services - Celix condition "framework.error". This service will be registered when the framework has not started successfully. This can occur if any of the configured bundles fail to start or install. Note that the "framework.error" condition is not part of the OSGi condition specification. +- Celix condition "components.ready". This service will be registered when the "framework.ready" or "framework.error" + service is registered, all components have become active and the event queue is empty. + Note that the "components.ready" condition is not part of the OSGi condition specification. Contrary to the OSGi specification, the Apache Celix framework does not provide a public API for adding or removing framework listeners. Instead, framework condition services can be used. This has the advantage of ensuring no diff --git a/libs/framework/gtest/src/FrameworkBundleTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc index bd5d6985e..c3067efa0 100644 --- a/libs/framework/gtest/src/FrameworkBundleTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc @@ -124,13 +124,13 @@ TEST_F(FrameworkBundleTestSuite, FrameworkReadyRegisteredLaterTest) { EXPECT_EQ(1, count); // And the service.id of the framework.ready condition is higher than the service.id of the test condition - //(white-box test, framework.ready condition is registered last) + //(white-box test, framework.ready condition is registered later) long readySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, frameworkReadyFilter); long testySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, testFilter); EXPECT_GT(readySvcId, testySvcId); - // And the "components.ready" condition is not available, because the test bundle contains a component which will - // not become active + // And the "components.ready" condition does not become available, because the test bundle contains a component + // which will not become active count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) .setFilter(componentsReadyFilter) .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) diff --git a/libs/framework/include/celix/FrameworkFactory.h b/libs/framework/include/celix/FrameworkFactory.h index 4872a8b03..520a2c567 100644 --- a/libs/framework/include/celix/FrameworkFactory.h +++ b/libs/framework/include/celix/FrameworkFactory.h @@ -49,4 +49,4 @@ inline std::shared_ptr createFramework(const celix::Properties celix_frameworkFactory_destroyFramework(cFw); }}; } -} // namespace celix \ No newline at end of file +} // namespace celix diff --git a/libs/framework/include/celix_condition.h b/libs/framework/include/celix_condition.h index f901e61c5..bd86b4bab 100644 --- a/libs/framework/include/celix_condition.h +++ b/libs/framework/include/celix_condition.h @@ -44,16 +44,12 @@ extern "C" { * @brief Celix condition service struct. * * In dynamic systems, such as OSGi and Apache Celix, one of the more challenging problems can be to define when - * a system or part of it is ready to do work. The answer can change depending on the individual perspective. - * The developer of a web server might say, the system is ready when the server starts listening on port 80. - * An application developer however would define the system as ready when the database connection is up and all - * servlets are registered. Taking the application developers view, the web server should start listening on - * port 80 when the application is ready and not beforehand. + * a system or part of it is ready to do work. * - * The Condition service interface is a marker interface designed to address this issue. + * The celix_condition service interface is a marker interface designed to address this issue. * Its role is to provide a dependency that can be tracked. It acts as a defined signal to other services. * - * A Condition service must be registered with the "celix.condition.id" service property. + * A celix_condition service must be registered with the "condition.id" service property. */ typedef struct celix_condition { void* handle; /**< private dummy handle, note not used in marker service struct, but added to ensure From cc323ef82b766cac7d5afec9d3ee5beecaa73309 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Wed, 12 Jul 2023 18:27:29 +0800 Subject: [PATCH 12/33] Remove dead code. --- libs/framework/src/framework.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index 7fa890b0d..b17e047ba 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -295,16 +295,6 @@ celix_status_t framework_destroy(framework_pt framework) { framework_waitForStop(framework); } - //Note the shutdown thread can not be joined on the framework_shutdown (which is normally more logical), - //because a shutdown can be initiated from a bundle. - //A bundle cannot be stopped when it is waiting for a framework shutdown -> hence a shutdown thread which - //has not been joined yet. - if (shutdownInitialized && !framework->shutdown.joined) { - celixThread_join(framework->shutdown.thread, NULL); - framework->shutdown.joined = true; - } - - celix_serviceRegistry_destroy(framework->registry); celixThreadMutex_lock(&framework->installedBundles.mutex); From 9998dd988476aab22c0f40ba9efbf9657955c523 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Mon, 24 Jul 2023 20:31:54 +0200 Subject: [PATCH 13/33] Move the components.ready check from fw bundle to a separate bundle --- bundles/CMakeLists.txt | 1 + bundles/components_ready_check/CMakeLists.txt | 53 ++++++ bundles/components_ready_check/README.md | 45 +++++ .../api/celix_components_ready_constants.h | 39 +++++ .../gtest/CMakeLists.txt | 54 ++++++ .../gtest/src/ActiveComponentBundle.cc | 42 +++++ .../gtest/src/ComponentsReadyTestSuite.cc | 90 ++++++++++ ...ponentsReadyWithErrorInjectionTestSuite.cc | 123 ++++++++++++++ .../gtest/src/InactiveComponentBundle.cc | 46 +++++ .../src/celix_components_ready_check.c | 160 ++++++++++++++++++ .../src/celix_components_ready_check.h | 58 +++++++ .../celix_components_ready_check_activator.c | 39 +++++ bundles/shell/README.md | 18 +- .../celix_bundle_ctx/CMakeLists.txt | 1 + .../include/celix_bundle_context_ei.h | 1 + .../src/celix_bundle_context_ei.cc | 7 + libs/framework/gtest/CMakeLists.txt | 1 - .../gtest/src/CondTestBundleActivator.cc | 7 - .../gtest/src/FrameworkBundleTestSuite.cc | 18 -- ...meworkBundleWithErrorInjectionTestSuite.cc | 17 -- libs/framework/include/celix_condition.h | 8 - libs/framework/src/celix_framework_bundle.c | 54 ------ libs/framework/src/celix_framework_bundle.h | 6 - libs/framework/src/framework.c | 2 +- 24 files changed, 770 insertions(+), 120 deletions(-) create mode 100644 bundles/components_ready_check/CMakeLists.txt create mode 100644 bundles/components_ready_check/README.md create mode 100644 bundles/components_ready_check/api/celix_components_ready_constants.h create mode 100644 bundles/components_ready_check/gtest/CMakeLists.txt create mode 100644 bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc create mode 100644 bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc create mode 100644 bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc create mode 100644 bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc create mode 100644 bundles/components_ready_check/src/celix_components_ready_check.c create mode 100644 bundles/components_ready_check/src/celix_components_ready_check.h create mode 100644 bundles/components_ready_check/src/celix_components_ready_check_activator.c diff --git a/bundles/CMakeLists.txt b/bundles/CMakeLists.txt index 78ecdfd53..a65cbe728 100644 --- a/bundles/CMakeLists.txt +++ b/bundles/CMakeLists.txt @@ -23,3 +23,4 @@ add_subdirectory(deployment_admin) add_subdirectory(remote_services) add_subdirectory(pubsub) add_subdirectory(cxx_remote_services) +add_subdirectory(components_ready_check) diff --git a/bundles/components_ready_check/CMakeLists.txt b/bundles/components_ready_check/CMakeLists.txt new file mode 100644 index 000000000..34eb8a8dc --- /dev/null +++ b/bundles/components_ready_check/CMakeLists.txt @@ -0,0 +1,53 @@ +# 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. + +#TODO add conanfile options for COMPONENTS_READY_CHECK +celix_subproject(COMPONENTS_READY_CHECK "Bundle that checks if all components becomes active and reports this using a condition service" ON) +if (COMPONENTS_READY_CHECK) + + add_library(components_ready_api INTERFACE) + target_include_directories(components_ready_api INTERFACE $) + add_library(Celix::components_ready_api ALIAS components_ready_api) + + + set(COMPONENTS_READY_CHECK_SRC src/celix_components_ready_check_activator.c src/celix_components_ready_check.c) + set(COMPONENTS_READY_CHECK_DEPS Celix::framework Celix::components_ready_api) + + add_celix_bundle(components_ready_check + SOURCES ${COMPONENTS_READY_CHECK_SRC} + VERSION 1.0.0 + SYMBOLIC_NAME "apache_celix_components_ready_check" + GROUP "Celix/Conditions" + NAME "Apache Celix Components Ready Check" + FILENAME celix_components_ready_check + ) + target_include_directories(components_ready_check PRIVATE src) + target_link_libraries(components_ready_check PRIVATE ${COMPONENTS_READY_CHECK_DEPS}) + add_library(Celix::components_ready_check ALIAS components_ready_check) + + #TODO check if include files are installed + install(TARGETS components_ready_api EXPORT celix DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT components_ready + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/celix/components_ready_api) + install_celix_bundle(components_ready_check EXPORT celix COMPONENT components_ready) + + if (ENABLE_TESTING) + add_library(components_ready_cut STATIC ${COMPONENTS_READY_CHECK_SRC}) + target_include_directories(components_ready_cut PUBLIC src) + target_link_libraries(components_ready_cut PUBLIC ${COMPONENTS_READY_CHECK_DEPS}) + add_subdirectory(gtest) + endif() +endif () diff --git a/bundles/components_ready_check/README.md b/bundles/components_ready_check/README.md new file mode 100644 index 000000000..8090077dc --- /dev/null +++ b/bundles/components_ready_check/README.md @@ -0,0 +1,45 @@ +--- +title: Shell +--- + + + +# Apache Celix Component Ready + +## Intro +The Apache Celix Component Ready provides an api and bundle which can be used to check if all components are ready. + +## API library +The Apache Celix Component Ready Check provides a single api library `Celix::component_ready_api` which contains +the condition id constant used to register the "components.ready" condition service. + +## Bundle +The Apache Celix Component Ready Check provides the `Celix::components_ready_check` bundle which registers the +"components.ready" condition service. + +TODO explain when the "components.ready" condition service is registered. + +## CMake options + +- COMPONENTS_READY_CHECK=ON + +## Using info + +If the Apache Celix Component Ready is installed, `find_package(Celix)` will set: +- The `Celix::component_ready_api` interface (i.e. header only) library target +- The `Celix::components_ready_check` bundle target diff --git a/bundles/components_ready_check/api/celix_components_ready_constants.h b/bundles/components_ready_check/api/celix_components_ready_constants.h new file mode 100644 index 000000000..09804ee82 --- /dev/null +++ b/bundles/components_ready_check/api/celix_components_ready_constants.h @@ -0,0 +1,39 @@ +/* + * 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_COMPONENTS_READY_CONSTANTS_H_ +#define CELIX_COMPONENTS_READY_CONSTANTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * @brief The unique identifier for the components.ready condition. + * + * The components ready condition is registered by the framework if the framework.ready condition is registered + * and all components active. + */ +#define CELIX_CONDITION_ID_COMPONENTS_READY "components.ready" + +#ifdef __cplusplus +} +#endif + +#endif /* CELIX_COMPONENTS_READY_CONSTANTS_H_ */ diff --git a/bundles/components_ready_check/gtest/CMakeLists.txt b/bundles/components_ready_check/gtest/CMakeLists.txt new file mode 100644 index 000000000..a98fff208 --- /dev/null +++ b/bundles/components_ready_check/gtest/CMakeLists.txt @@ -0,0 +1,54 @@ +# 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. + +set(CMAKE_CXX_STANDARD 17) + +#Add active/inactive test component bundles +add_celix_bundle(ActiveComponentBundle SOURCES src/ActiveComponentBundle.cc VERSION 1.0.0) +add_celix_bundle(InactiveComponentBundle SOURCES src/InactiveComponentBundle.cc VERSION 1.0.0) + +add_executable(test_components_ready src/ComponentsReadyTestSuite.cc) +target_link_libraries(test_components_ready PRIVATE + Celix::framework + Celix::components_ready_api + GTest::gtest + GTest::gtest_main +) +celix_target_bundle_set_definition(test_components_ready NAME COMPONENTS_READY_CHECK_BUNDLE_SET Celix::components_ready_check) +celix_target_bundle_set_definition(test_components_ready NAME ACTIVE_CMP_TEST_BUNDLE_SET ActiveComponentBundle) +celix_target_bundle_set_definition(test_components_ready NAME INACTIVE_CMP_TEST_BUNDLE_SET InactiveComponentBundle) + +add_test(NAME test_components_ready COMMAND test_components_ready) +setup_target_for_coverage(test_components_ready SCAN_DIR ..) + +if (LINKER_WRAP_SUPPORTED) + add_executable(test_components_ready_with_ei src/ComponentsReadyWithErrorInjectionTestSuite.cc) + target_link_libraries(test_components_ready_with_ei PRIVATE + framework_cut + components_ready_cut + Celix::components_ready_api + Celix::malloc_ei + Celix::threads_ei + Celix::bundle_ctx_ei + Celix::properties_ei + GTest::gtest + GTest::gtest_main + ) + + add_test(NAME test_components_ready_with_ei COMMAND test_components_ready_with_ei) + setup_target_for_coverage(test_components_ready_with_ei SCAN_DIR ..) +endif () diff --git a/bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc b/bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc new file mode 100644 index 000000000..7bc3549dd --- /dev/null +++ b/bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc @@ -0,0 +1,42 @@ +/* + * 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 "celix/BundleActivator.h" +#include "celix_condition.h" + +/** + * @brief Empty Test Component for testing the condition service + */ +class CondComponent { +public: + CondComponent() = default; +}; + +class ActiveComponentBundle { +public: + explicit ActiveComponentBundle(const std::shared_ptr& ctx) { + ctx->getDependencyManager()->createComponent().build(); + } + +private: + std::shared_ptr registration{}; + celix_condition condition{}; +}; + +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ActiveComponentBundle) \ No newline at end of file diff --git a/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc new file mode 100644 index 000000000..39eb531ef --- /dev/null +++ b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc @@ -0,0 +1,90 @@ +/* + * 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 + +#include "celix/FrameworkFactory.h" +#include "celix/FrameworkUtils.h" +#include "celix_condition.h" +#include "celix_components_ready_constants.h" + +class ComponentsReadyTestSuite : public ::testing::Test { +public: + const int USE_SERVICE_TIMEOUT_IN_MS = 2000; // TODO improve test time to a lower value + const std::string componentsReadyFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; + + ComponentsReadyTestSuite() = default; +}; + +TEST_F(ComponentsReadyTestSuite, ComponentsReadyTest) { + // Given a Celix framework + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // When the components ready check bundle is installed + celix::installBundleSet(*fw, COMPONENTS_READY_CHECK_BUNDLE_SET); + + // Then the condition service with id "components.ready" will become available + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(1, count); +} + +TEST_F(ComponentsReadyTestSuite, ComponentsReadyWithActiveComponentTest) { + // Given a Celix framework + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // When a test bundle with an active-able component is installed + celix::installBundleSet(*fw, ACTIVE_CMP_TEST_BUNDLE_SET); + + // And the components ready bundle check is installed + celix::installBundleSet(*fw, COMPONENTS_READY_CHECK_BUNDLE_SET); + + // Then the "components.ready" condition will become available + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(1, count); +} + + +TEST_F(ComponentsReadyTestSuite, ComponentsReadyWithInactiveComponentTest) { + // Given a Celix framework + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // When a test bundle with a not active-able component is installed + celix::installBundleSet(*fw, INACTIVE_CMP_TEST_BUNDLE_SET); + + // WAnd the components ready check bundle is installed + celix::installBundleSet(*fw, COMPONENTS_READY_CHECK_BUNDLE_SET); + + // Then the "components.ready" condition will not become available, because the test bundle contains a component + // which cannot become active + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) + .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(0, count); +} diff --git a/bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc b/bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc new file mode 100644 index 000000000..52dce4378 --- /dev/null +++ b/bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc @@ -0,0 +1,123 @@ +/* + * 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 + +#include "celix/FrameworkFactory.h" +#include "celix/FrameworkUtils.h" +#include "celix_condition.h" +#include "celix_components_ready_constants.h" +#include "celix_components_ready_check.h" + +#include "malloc_ei.h" +#include "celix_threads_ei.h" +#include "celix_bundle_context_ei.h" +#include "celix_properties_ei.h" + +class ComponentsReadyWithErrorInjectionTestSuite : public ::testing::Test { +public: + const int USE_SERVICE_TIMEOUT_IN_MS = 2000; // TODO improve test time to a lower value + const std::string frameworkReadyFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; + const std::string componentsReadyFilter = + std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; + + ComponentsReadyWithErrorInjectionTestSuite() = default; + + ~ComponentsReadyWithErrorInjectionTestSuite() noexcept override { + // reset error injections + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_celixThreadMutex_create(nullptr, 0, CELIX_SUCCESS); + celix_ei_expect_celix_bundleContext_trackServicesWithOptionsAsync(nullptr, 0, 0); + celix_ei_expect_celix_properties_create(nullptr, 0, nullptr); + celix_ei_expect_celix_bundleContext_scheduleEvent(nullptr, 0, 0); + } +}; + +TEST_F(ComponentsReadyWithErrorInjectionTestSuite, ErrorCreatingComponentsReadyCheck) { + // Given a Celix framework + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // When an error injection for calloc is primed when called from celix_componentsReadyCheck_create + celix_ei_expect_calloc((void*)celix_componentsReadyCheck_create, 0, nullptr); + + // Then the components ready check will cannot be created + auto* rdy = celix_componentsReadyCheck_create(ctx->getCBundleContext()); + EXPECT_EQ(rdy, nullptr); + + // When an error injection for mutex create is primed when called from celix_componentsReadyCheck_create + celix_ei_expect_celixThreadMutex_create((void*)celix_componentsReadyCheck_create, 0, CELIX_BUNDLE_EXCEPTION); + + // Then the components ready check will cannot be created + rdy = celix_componentsReadyCheck_create(ctx->getCBundleContext()); + EXPECT_EQ(rdy, nullptr); + + // When an error injection for celix_bundleContext_trackServicesWithOptionsAsync is primed when called from + // celix_componentsReadyCheck_create + celix_ei_expect_celix_bundleContext_trackServicesWithOptionsAsync((void*)celix_componentsReadyCheck_create, 0, -1); + + // Then the components ready check will cannot be created + rdy = celix_componentsReadyCheck_create(ctx->getCBundleContext()); + EXPECT_EQ(rdy, nullptr); +} + +TEST_F(ComponentsReadyWithErrorInjectionTestSuite, ErrorRegisteringComponentsReadyConditionTest) { + // Given a Celix framework + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // When an error injection for celix_properties_create is primed when called from celix_frameworkBundle_componentsCheck + celix_ei_expect_celix_properties_create((void*)celix_componentReadyCheck_registerCondition, 0, nullptr); + + // And the components ready check is created + auto* rdy = celix_componentsReadyCheck_create(ctx->getCBundleContext()); + EXPECT_NE(rdy, nullptr); + + // But the components.ready condition will not become available, due to an error creating properties for the service + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) + .setTimeout(std::chrono::milliseconds {USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(count, 0); + + celix_componentsReadyCheck_destroy(rdy); +} + +TEST_F(ComponentsReadyWithErrorInjectionTestSuite, ErrorSchedulingReadyCheckEventTest) { + // Given a Celix framework + auto fw = celix::createFramework(); + auto ctx = fw->getFrameworkBundleContext(); + + // When an error injection for celix_bundleContext_scheduleEvent is primed when called from celix_componentReadyCheck_setFrameworkReadySvc + celix_ei_expect_celix_bundleContext_scheduleEvent((void*)celix_componentReadyCheck_setFrameworkReadySvc, 0, -1); + + // And the components ready check is created + auto* rdy = celix_componentsReadyCheck_create(ctx->getCBundleContext()); + EXPECT_NE(rdy, nullptr); + + // But the components.ready condition will not become available, due to an error creating a check scheduled event + auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) + .setFilter(componentsReadyFilter) + .setTimeout(std::chrono::milliseconds {USE_SERVICE_TIMEOUT_IN_MS}) + .build(); + EXPECT_EQ(count, 0); + + celix_componentsReadyCheck_destroy(rdy); +} diff --git a/bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc b/bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc new file mode 100644 index 000000000..19b1d0857 --- /dev/null +++ b/bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc @@ -0,0 +1,46 @@ +/* + * 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 "celix/BundleActivator.h" +#include "celix_condition.h" + +/** + * @brief Empty Test Component for testing the condition service + */ +class CondComponent { +public: + CondComponent() = default; +}; + +class InactiveComponentBundleActivator { +public: + explicit InactiveComponentBundleActivator(const std::shared_ptr& ctx) { + auto& cmp = ctx->getDependencyManager()->createComponent(); + cmp.createServiceDependency(CELIX_CONDITION_SERVICE_NAME) + .setFilter("(condition.id=does-not-exists)") + .setRequired(true); + cmp.build(); + } + +private: + std::shared_ptr registration{}; + celix_condition condition{}; +}; + +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(InactiveComponentBundleActivator) \ No newline at end of file diff --git a/bundles/components_ready_check/src/celix_components_ready_check.c b/bundles/components_ready_check/src/celix_components_ready_check.c new file mode 100644 index 000000000..4eebd348b --- /dev/null +++ b/bundles/components_ready_check/src/celix_components_ready_check.c @@ -0,0 +1,160 @@ +/* + * 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 "celix_components_ready_check.h" + +#include +#include + +#include "celix_components_ready_constants.h" +#include "celix_condition.h" +#include "celix_dependency_manager.h" +#include "celix_framework.h" +#include "celix_threads.h" + +struct celix_components_ready_check { + celix_bundle_context_t* ctx; + celix_condition_t conditionInstance; /**< condition instance which can be used for multiple condition services.*/ + celix_thread_mutex_t mutex; /**< mutex to protect the fields below. */ + long frameworkReadyTrackerId; /**< tracker id for the framework ready condition service. */ + long checkComponentsScheduledEventId; /**< event id of the scheduled event to check if the framework is ready. */ + long componentsReadyConditionSvcId; /**< service id of the condition service which is set when all components are + ready. */ +}; + +celix_components_ready_check_t* celix_componentsReadyCheck_create(celix_bundle_context_t* ctx) { + celix_status_t status; + celix_components_ready_check_t* rdy = calloc(1, sizeof(*rdy)); + if (rdy) { + rdy->ctx = ctx; + rdy->frameworkReadyTrackerId = -1L; + rdy->checkComponentsScheduledEventId = -1L; + rdy->componentsReadyConditionSvcId = -1L; + + status = celixThreadMutex_create(&rdy->mutex, NULL); + if (status != CELIX_SUCCESS) { + goto mutex_init_err; + } + + char filter[32]; + int written = + snprintf(filter, sizeof(filter), "(%s=%s)", CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); + assert(written < sizeof(filter)); + + celix_service_tracking_options_t opts = CELIX_EMPTY_SERVICE_TRACKING_OPTIONS; + opts.filter.serviceName = CELIX_CONDITION_SERVICE_NAME; + opts.filter.filter = filter; + opts.set = celix_componentReadyCheck_setFrameworkReadySvc; + opts.callbackHandle = rdy; + rdy->frameworkReadyTrackerId = celix_bundleContext_trackServicesWithOptionsAsync(ctx, &opts); + if (rdy->frameworkReadyTrackerId < 0) { + goto tracker_err; + } + } else { + celix_bundleContext_log(ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create components ready check. ENOMEM"); + } + return rdy; +mutex_init_err: + celix_bundleContext_log( + ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create components ready check. Cannot create mutex. Got status %d", status); + free(rdy); + return NULL; +tracker_err: + celix_bundleContext_log(ctx, + CELIX_LOG_LEVEL_ERROR, + "Cannot create components ready check. Cannot track framework ready condition " + "service. Got tracker id %ld", + rdy->frameworkReadyTrackerId); + celixThreadMutex_destroy(&rdy->mutex); + free(rdy); + return NULL; +} + +void celix_componentsReadyCheck_destroy(celix_components_ready_check_t* rdy) { + if (rdy) { + celix_bundleContext_stopTracker(rdy->ctx, rdy->frameworkReadyTrackerId); + + celixThreadMutex_lock(&rdy->mutex); + long schedId = rdy->checkComponentsScheduledEventId; + rdy->checkComponentsScheduledEventId = -1L; + celixThreadMutex_unlock(&rdy->mutex); + celix_bundleContext_removeScheduledEvent(rdy->ctx, schedId); + + celixThreadMutex_lock(&rdy->mutex); + long svcId = rdy->componentsReadyConditionSvcId; + rdy->componentsReadyConditionSvcId = -1L; + celixThreadMutex_unlock(&rdy->mutex); + celix_bundleContext_unregisterService(rdy->ctx, svcId); + + free(rdy); + } +} + +void celix_componentReadyCheck_registerCondition(celix_components_ready_check_t* rdy) { + //precondition rdy->mutex is locked + celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; + opts.serviceName = CELIX_CONDITION_SERVICE_NAME; + opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; + opts.svc = &rdy->conditionInstance; + opts.properties = celix_properties_create(); + if (opts.properties) { + celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_COMPONENTS_READY); + celix_bundleContext_log(rdy->ctx, CELIX_LOG_LEVEL_DEBUG, "Registering components.ready condition service"); + rdy->componentsReadyConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(rdy->ctx, &opts); + } else { + celix_bundleContext_log( + rdy->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for components.ready condition service"); + } +} + +static void celix_componentReadyCheck_check(void* data) { + celix_components_ready_check_t* rdy = data; + celix_dependency_manager_t* mng = celix_bundleContext_getDependencyManager(rdy->ctx); + celix_framework_t* fw = celix_bundleContext_getFramework(rdy->ctx); + bool ready = celix_dependencyManager_allComponentsActive(mng) && celix_framework_isEventQueueEmpty(fw); + if (ready) { + celixThreadMutex_lock(&rdy->mutex); + celix_bundleContext_removeScheduledEventAsync(rdy->ctx, rdy->checkComponentsScheduledEventId); + rdy->checkComponentsScheduledEventId = -1L; + celix_componentReadyCheck_registerCondition(rdy); + celixThreadMutex_unlock(&rdy->mutex); + } +} + +void celix_componentReadyCheck_setFrameworkReadySvc(void* handle, void* svc) { + celix_components_ready_check_t* rdy = handle; + celixThreadMutex_lock(&rdy->mutex); + if (svc && rdy->checkComponentsScheduledEventId < 0) { + // framework ready, now periodically check if all components are ready + celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; + opts.name = "celix_componentReady_check"; + opts.callback = celix_componentReadyCheck_check; + opts.callbackData = rdy; + opts.initialDelayInSeconds = 0.1; + opts.intervalInSeconds = 0.1; + rdy->checkComponentsScheduledEventId = celix_bundleContext_scheduleEvent(rdy->ctx, &opts); + if (rdy->checkComponentsScheduledEventId < 0) { + celix_bundleContext_log(rdy->ctx, + CELIX_LOG_LEVEL_ERROR, + "Cannot schedule components ready check. Got event id %ld", + rdy->checkComponentsScheduledEventId); + } + } + celixThreadMutex_unlock(&rdy->mutex); +} diff --git a/bundles/components_ready_check/src/celix_components_ready_check.h b/bundles/components_ready_check/src/celix_components_ready_check.h new file mode 100644 index 000000000..c6ee38503 --- /dev/null +++ b/bundles/components_ready_check/src/celix_components_ready_check.h @@ -0,0 +1,58 @@ +/* + * 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_COMPONENTS_READY_H_ +#define CELIX_COMPONENTS_READY_H_ + +#include "celix_bundle_context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct celix_components_ready_check celix_components_ready_check_t; + +/** + * @brief Creates a new components ready check. + * @param ctx The bundle context. + */ +celix_components_ready_check_t* celix_componentsReadyCheck_create(celix_bundle_context_t * ctx); + +/** + * @brief Destroys the components ready check. + */ +void celix_componentsReadyCheck_destroy(celix_components_ready_check_t* rdy); + +/** + * @brief Sets the framework ready condition. + * @note Part of the header for testing purposes. + */ +void celix_componentReadyCheck_setFrameworkReadySvc(void* handle, void* svc); + +/** + * @brief The scheduled event callback for the components.ready check. + * @note Part of the header for testing purposes. + */ +void celix_componentReadyCheck_registerCondition(celix_components_ready_check_t* rdy); + +#ifdef __cplusplus +} +#endif + +#endif /* CELIX_COMPONENTS_READY_H_ */ \ No newline at end of file diff --git a/bundles/components_ready_check/src/celix_components_ready_check_activator.c b/bundles/components_ready_check/src/celix_components_ready_check_activator.c new file mode 100644 index 000000000..74a76786d --- /dev/null +++ b/bundles/components_ready_check/src/celix_components_ready_check_activator.c @@ -0,0 +1,39 @@ +/* + * 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 "celix_components_ready_check.h" +#include "celix_bundle_activator.h" + +typedef struct celix_components_ready_check_activator { + celix_components_ready_check_t* rdy; +} celix_components_ready_check_activator_t; + +static int celix_componentsReadyCheckActivator_start(celix_components_ready_check_activator_t* act, celix_bundle_context_t* ctx) { + act->rdy = celix_componentsReadyCheck_create(ctx); + return act->rdy? CELIX_SUCCESS : CELIX_ENOMEM; +} + +static int celix_componentsCheckActivator_stop(celix_components_ready_check_activator_t* act, celix_bundle_context_t* ctx) { + celix_componentsReadyCheck_destroy(act->rdy); + return CELIX_SUCCESS; +} + +CELIX_GEN_BUNDLE_ACTIVATOR(celix_components_ready_check_activator_t, + celix_componentsReadyCheckActivator_start, + celix_componentsCheckActivator_stop); diff --git a/bundles/shell/README.md b/bundles/shell/README.md index 4ad8b7f1d..1bc0fdf73 100644 --- a/bundles/shell/README.md +++ b/bundles/shell/README.md @@ -19,7 +19,9 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Intro +# Apache Celix Shell + +## Intro The Celix Shell provides a service interface which can be used to interact with the Celix framework. It uses a modular approach to enable multiple frontends, e.g. textual or graphical. @@ -34,7 +36,7 @@ for example: Further information about a command can be retrieved by using `help` combined with the command. -# Service interfaces +## Service interfaces The Celix Shell functionality is achieved by 3 service interfaces. These interfaces are available through the `Celix::shel_api` CMake INTERFACE library target. @@ -43,7 +45,7 @@ The Celix Shell functionality is achieved by 3 service interfaces. These interfa - `celix_shell_command_t`: A C service interface to provide an additional shell command to the shell. - `celix::IShellCommand`: A C++ service interface to provide an additional shell command to the shell. -# Bundles +## Bundles The complete Celix shell functionality is provided by serveral bundles: - `Celix::shell` : The core shell which offer a `celix_shell_t` service and uses `celix_shell_command_t` services. - `Celix::CxxShell` : The core C++ shell which offers a `celix_shell_t` service and uses both `celix_shell_command_t` @@ -55,21 +57,21 @@ The complete Celix shell functionality is provided by serveral bundles: - `Celix::bonjour_shell`: Chat interface to the shell service using bonjour. Warning not mature and still unstable. When used it should be possible to chat to a Celix shell using Linux's pidgin application or OSX's Messages application. -## Logical Design Celix Shell +### Logical Design Celix Shell |![Celix Shell Logical Design](diagrams/celix_shell.png) -## Logical Design Celix Shell for C++ +### Logical Design Celix Shell for C++ ![Celix C++ Shell Logical Design](diagrams/celix_cxx_shell.png) -# CMake options +## CMake options - BUILD_SHELL=ON - BUILD_SHELL_TUI=ON - BUILD_SHELL_WUI=ON - BUILD_REMOTE_SHELL=ON -# Shell Config Options +## Shell Config Options - SHELL_USE_ANSI_COLORS - Configures whether shell commands are allowed to use ANSI colors when printing info for `Celix::shell`. Default is true. @@ -79,7 +81,7 @@ ANSI colors when printing info for `Celix::shell`. Default is true. - "remote.shell.telnet.port": Configures port used in `Celix::remote_shell`. Default is 6666. - "remote.shell.telnet.maxconn": Configures max nr of concurrent connections in `Celix::remote_shell`. Default is 2. -# Using info +## Using info If the Celix Shell is installed, `find_package(Celix)` will set: - The `Celix::shell_api` interface (i.e. header only) library target diff --git a/libs/error_injector/celix_bundle_ctx/CMakeLists.txt b/libs/error_injector/celix_bundle_ctx/CMakeLists.txt index fed75fbee..325eecefb 100644 --- a/libs/error_injector/celix_bundle_ctx/CMakeLists.txt +++ b/libs/error_injector/celix_bundle_ctx/CMakeLists.txt @@ -30,5 +30,6 @@ target_link_options(bundle_ctx_ei INTERFACE LINKER:--wrap,bundleContext_retainServiceReference LINKER:--wrap,celix_bundleContext_registerServiceAsync LINKER:--wrap,celix_bundleContext_registerServiceFactoryAsync + LINKER:--wrap,celix_bundleContext_scheduleEvent ) add_library(Celix::bundle_ctx_ei ALIAS bundle_ctx_ei) diff --git a/libs/error_injector/celix_bundle_ctx/include/celix_bundle_context_ei.h b/libs/error_injector/celix_bundle_ctx/include/celix_bundle_context_ei.h index 2a80e14a5..96be32d12 100644 --- a/libs/error_injector/celix_bundle_ctx/include/celix_bundle_context_ei.h +++ b/libs/error_injector/celix_bundle_ctx/include/celix_bundle_context_ei.h @@ -33,6 +33,7 @@ CELIX_EI_DECLARE(bundleContext_getServiceReferences, celix_status_t); CELIX_EI_DECLARE(bundleContext_retainServiceReference, celix_status_t); CELIX_EI_DECLARE(celix_bundleContext_registerServiceAsync, long); CELIX_EI_DECLARE(celix_bundleContext_registerServiceFactoryAsync, long); +CELIX_EI_DECLARE(celix_bundleContext_scheduleEvent, long); #ifdef __cplusplus } diff --git a/libs/error_injector/celix_bundle_ctx/src/celix_bundle_context_ei.cc b/libs/error_injector/celix_bundle_ctx/src/celix_bundle_context_ei.cc index 9b793ac85..6c465e327 100644 --- a/libs/error_injector/celix_bundle_ctx/src/celix_bundle_context_ei.cc +++ b/libs/error_injector/celix_bundle_ctx/src/celix_bundle_context_ei.cc @@ -95,4 +95,11 @@ long __wrap_celix_bundleContext_registerServiceFactoryAsync(celix_bundle_context return __real_celix_bundleContext_registerServiceFactoryAsync(__ctx, __serviceName, __factory, __properties); } +long __real_celix_bundleContext_scheduleEvent(celix_bundle_context_t *__ctx, const celix_scheduled_event_options_t* __options); +CELIX_EI_DEFINE(celix_bundleContext_scheduleEvent, long) +long __wrap_celix_bundleContext_scheduleEvent(celix_bundle_context_t *__ctx, const celix_scheduled_event_options_t* __options) { + CELIX_EI_IMPL(celix_bundleContext_scheduleEvent); + return __real_celix_bundleContext_scheduleEvent(__ctx, __options); +} + } \ No newline at end of file diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index 225de605b..a073a4d34 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -146,7 +146,6 @@ if (LINKER_WRAP_SUPPORTED) celix_deprecated_utils_headers(test_framework_with_ei) target_link_libraries(test_framework_with_ei PRIVATE framework_cut - Celix::framework Celix::malloc_ei Celix::utils_ei Celix::asprintf_ei diff --git a/libs/framework/gtest/src/CondTestBundleActivator.cc b/libs/framework/gtest/src/CondTestBundleActivator.cc index e349cbcc8..cfb8f2bcd 100644 --- a/libs/framework/gtest/src/CondTestBundleActivator.cc +++ b/libs/framework/gtest/src/CondTestBundleActivator.cc @@ -34,13 +34,6 @@ class CondTestBundleActivator { registration = ctx->registerUnmanagedService(&condition, CELIX_CONDITION_SERVICE_NAME) .addProperty(CELIX_CONDITION_ID, "test") .build(); - - auto& cmp = ctx->getDependencyManager()->createComponent(); - cmp.createServiceDependency(CELIX_CONDITION_SERVICE_NAME) - .setFilter("(condition.id=does-not-exists)") - .setRequired(true); - - cmp.build(); } private: diff --git a/libs/framework/gtest/src/FrameworkBundleTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc index c3067efa0..2f369496c 100644 --- a/libs/framework/gtest/src/FrameworkBundleTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkBundleTestSuite.cc @@ -31,8 +31,6 @@ class FrameworkBundleTestSuite : public ::testing::Test { std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; const std::string frameworkErrorFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_ERROR + ")"; - const std::string componentsReadyFilter = - std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; FrameworkBundleTestSuite() = default; }; @@ -59,13 +57,6 @@ TEST_F(FrameworkBundleTestSuite, ConditionTrueFrameworkReadyAndComponentsReadyTe .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) .build(); EXPECT_EQ(1, count); - - // And the condition service with id "components.ready" will become available - count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(componentsReadyFilter) - .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) - .build(); - EXPECT_EQ(1, count); } TEST_F(FrameworkBundleTestSuite, ConditionTrueAndFrameworkErrorTest) { @@ -128,13 +119,4 @@ TEST_F(FrameworkBundleTestSuite, FrameworkReadyRegisteredLaterTest) { long readySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, frameworkReadyFilter); long testySvcId = ctx->findServiceWithName(CELIX_CONDITION_SERVICE_NAME, testFilter); EXPECT_GT(readySvcId, testySvcId); - - // And the "components.ready" condition does not become available, because the test bundle contains a component - // which will not become active - count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(componentsReadyFilter) - .setTimeout(std::chrono::milliseconds{USE_SERVICE_TIMEOUT_IN_MS}) - .build(); - EXPECT_EQ(0, count); } - diff --git a/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc index 1feef016f..79a16f465 100644 --- a/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc @@ -32,8 +32,6 @@ class FrameworkBundleWithErrorInjectionTestSuite : public ::testing::Test { const int USE_SERVICE_TIMEOUT_IN_MS = 500; const std::string frameworkReadyFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; - const std::string componentsReadyFilter = - std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; FrameworkBundleWithErrorInjectionTestSuite() = default; @@ -85,18 +83,3 @@ TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringFrameworkRead EXPECT_EQ(count, 0); } -TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringComponentsReadyConditionTest) { - // When an error injection for celix_properties_create is primed when called from celix_frameworkBundle_componentsCheck - celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_componentsCheck, 0, nullptr); - - // And a framework instance is created - auto fw = celix::createFramework(); - auto ctx = fw->getFrameworkBundleContext(); - - // Then the components.ready condition will not become available, due to an error creating properties for the service - auto count = ctx->useService(CELIX_CONDITION_SERVICE_NAME) - .setFilter(componentsReadyFilter) - .setTimeout(std::chrono::milliseconds {USE_SERVICE_TIMEOUT_IN_MS}) - .build(); - EXPECT_EQ(count, 0); -} diff --git a/libs/framework/include/celix_condition.h b/libs/framework/include/celix_condition.h index bd86b4bab..bfb2a84ac 100644 --- a/libs/framework/include/celix_condition.h +++ b/libs/framework/include/celix_condition.h @@ -85,14 +85,6 @@ typedef struct celix_condition { */ #define CELIX_CONDITION_ID_FRAMEWORK_ERROR "framework.error" -/*! - * @brief The unique identifier for the components.ready condition. - * - * The components ready condition is registered by the framework if the framework.ready condition is registered - * and all components active. - */ -#define CELIX_CONDITION_ID_COMPONENTS_READY "components.ready" - #ifdef __cplusplus } #endif diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c index 8eb2c06c1..89feff348 100644 --- a/libs/framework/src/celix_framework_bundle.c +++ b/libs/framework/src/celix_framework_bundle.c @@ -39,9 +39,6 @@ typedef struct celix_framework_bundle { long trueConditionSvcId; /**< service id of the condition service which is always true. */ long frameworkReadyOrErrorConditionSvcId; /**< service id of the condition service which is set when the framework is ready or started up with an error */ - long checkComponentsScheduledEventId; /**< event id of the scheduled event to check if the framework is ready. */ - long componentsReadyConditionSvcId; /**< service id of the condition service which is set when all components are - ready. */ } celix_framework_bundle_t; celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** userData) { @@ -62,8 +59,6 @@ celix_status_t celix_frameworkBundle_create(celix_bundle_context_t* ctx, void** act->listener.handle = act; act->listener.frameworkEvent = celix_frameworkBundle_handleFrameworkEvent; act->frameworkReadyOrErrorConditionSvcId = -1L; - act->checkComponentsScheduledEventId = -1L; - act->componentsReadyConditionSvcId = -1L; act->conditionInstance.handle = act; *userData = act; @@ -111,7 +106,6 @@ celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framewor } act->frameworkReadyOrErrorConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); - celix_bundleContext_wakeupScheduledEvent(act->ctx, act->checkComponentsScheduledEventId); } else { celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_ERROR, @@ -122,43 +116,6 @@ celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framewor return CELIX_SUCCESS; } -void celix_frameworkBundle_componentsCheck(void* data) { - celix_framework_bundle_t* act = data; - celix_dependency_manager_t* mng = celix_bundleContext_getDependencyManager(act->ctx); - - celixThreadMutex_lock(&act->mutex); - bool allComponentsActive = celix_dependencyManager_allComponentsActive(mng); - bool ready = allComponentsActive && act->frameworkReadyOrErrorConditionSvcId >= 0; - if (ready) { - celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; - opts.serviceName = CELIX_CONDITION_SERVICE_NAME; - opts.serviceVersion = CELIX_CONDITION_SERVICE_VERSION; - opts.svc = &act->conditionInstance; - opts.properties = celix_properties_create(); - if (opts.properties) { - celix_properties_set(opts.properties, CELIX_CONDITION_ID, CELIX_CONDITION_ID_COMPONENTS_READY); - celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_DEBUG, "Registering components.ready condition service"); - act->componentsReadyConditionSvcId = celix_bundleContext_registerServiceWithOptionsAsync(act->ctx, &opts); - } else { - celix_bundleContext_log( - act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for components.ready condition service"); - } - celix_bundleContext_removeScheduledEventAsync(act->ctx, act->checkComponentsScheduledEventId); - act->checkComponentsScheduledEventId = -1L; - } - celixThreadMutex_unlock(&act->mutex); -} - -static void celix_frameworkBundle_startComponentsCheck(celix_framework_bundle_t* act) { - celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; - opts.name = "celix_frameworkBundle_componentsCheck"; - opts.callback = celix_frameworkBundle_componentsCheck; - opts.callbackData = act; - opts.initialDelayInSeconds = 1; //note will be wakeup by framework event - opts.intervalInSeconds = 0.001; - act->checkComponentsScheduledEventId = celix_bundleContext_scheduleEvent(act->ctx, &opts); -} - celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_t* ctx) { celix_framework_bundle_t* act = userData; @@ -175,7 +132,6 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ fw_addFrameworkListener( celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); - celix_frameworkBundle_startComponentsCheck(act); return CELIX_SUCCESS; } @@ -187,24 +143,14 @@ celix_status_t celix_frameworkBundle_stop(void* userData, celix_bundle_context_t // remove framework listener fw_removeFrameworkListener(framework, celix_bundleContext_getBundle(ctx), &act->listener); - // stop ready check - celixThreadMutex_lock(&act->mutex); - long checkEventId = act->checkComponentsScheduledEventId; - act->checkComponentsScheduledEventId = -1L; - celixThreadMutex_unlock(&act->mutex); - celix_bundleContext_removeScheduledEvent(ctx, checkEventId); - // remove framework true condition service and - if present - framework.ready condition service, celixThreadMutex_lock(&act->mutex); long trueConditionSvcId = act->trueConditionSvcId; long frameworkReadyOrErrorConditionSvcId = act->frameworkReadyOrErrorConditionSvcId; - long componentsReadyConditionSvcId = act->componentsReadyConditionSvcId; act->trueConditionSvcId = -1L; act->frameworkReadyOrErrorConditionSvcId = -1L; - act->componentsReadyConditionSvcId = -1L; celixThreadMutex_unlock(&act->mutex); - celix_bundleContext_unregisterService(ctx, componentsReadyConditionSvcId); celix_bundleContext_unregisterService(ctx, frameworkReadyOrErrorConditionSvcId); celix_bundleContext_unregisterService(ctx, trueConditionSvcId); diff --git a/libs/framework/src/celix_framework_bundle.h b/libs/framework/src/celix_framework_bundle.h index 850cf9686..6d1bb8cd7 100644 --- a/libs/framework/src/celix_framework_bundle.h +++ b/libs/framework/src/celix_framework_bundle.h @@ -54,12 +54,6 @@ celix_status_t celix_frameworkBundle_destroy(void* userData, celix_bundle_contex */ celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framework_event_t* event); -/** - * @brief The scheduled event callback for the framework components.ready check. - * @note Part of the header for testing purposes. - */ -void celix_frameworkBundle_componentsCheck(void* data); - #ifdef __cplusplus } #endif diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index b17e047ba..3b555addb 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -1538,7 +1538,7 @@ void celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId) { } static int celix_framework_eventQueueSize(celix_framework_t* fw) { - //precondition fw->dispatcher.mutex lockedx); + //precondition fw->dispatcher.mutex locked); return fw->dispatcher.eventQueueSize + celix_arrayList_size(fw->dispatcher.dynamicEventQueue); } From 23001993f59fe16bef1a3323b365c2625712debf Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 25 Jul 2023 12:56:42 +0200 Subject: [PATCH 14/33] Add install of components_ready_api headers --- bundles/components_ready_check/CMakeLists.txt | 12 +++++------- .../gtest/src/ActiveComponentBundle.cc | 3 +-- .../gtest/src/InactiveComponentBundle.cc | 3 +-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bundles/components_ready_check/CMakeLists.txt b/bundles/components_ready_check/CMakeLists.txt index 34eb8a8dc..a23d12e17 100644 --- a/bundles/components_ready_check/CMakeLists.txt +++ b/bundles/components_ready_check/CMakeLists.txt @@ -22,13 +22,15 @@ if (COMPONENTS_READY_CHECK) add_library(components_ready_api INTERFACE) target_include_directories(components_ready_api INTERFACE $) add_library(Celix::components_ready_api ALIAS components_ready_api) + install(TARGETS components_ready_api EXPORT celix DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT components_ready + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/celix_components_ready_api) + install(DIRECTORY api/ DESTINATION include/celix_components_ready_api COMPONENT components_ready) - set(COMPONENTS_READY_CHECK_SRC src/celix_components_ready_check_activator.c src/celix_components_ready_check.c) + set(COMPONENTS_READY_CHECK_SRC src/celix_components_ready_check.c) set(COMPONENTS_READY_CHECK_DEPS Celix::framework Celix::components_ready_api) - add_celix_bundle(components_ready_check - SOURCES ${COMPONENTS_READY_CHECK_SRC} + SOURCES src/celix_components_ready_check_activator.c ${COMPONENTS_READY_CHECK_SRC} VERSION 1.0.0 SYMBOLIC_NAME "apache_celix_components_ready_check" GROUP "Celix/Conditions" @@ -38,10 +40,6 @@ if (COMPONENTS_READY_CHECK) target_include_directories(components_ready_check PRIVATE src) target_link_libraries(components_ready_check PRIVATE ${COMPONENTS_READY_CHECK_DEPS}) add_library(Celix::components_ready_check ALIAS components_ready_check) - - #TODO check if include files are installed - install(TARGETS components_ready_api EXPORT celix DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT components_ready - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/celix/components_ready_api) install_celix_bundle(components_ready_check EXPORT celix COMPONENT components_ready) if (ENABLE_TESTING) diff --git a/bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc b/bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc index 7bc3549dd..43537e480 100644 --- a/bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc +++ b/bundles/components_ready_check/gtest/src/ActiveComponentBundle.cc @@ -36,7 +36,6 @@ class ActiveComponentBundle { private: std::shared_ptr registration{}; - celix_condition condition{}; }; -CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ActiveComponentBundle) \ No newline at end of file +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ActiveComponentBundle) diff --git a/bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc b/bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc index 19b1d0857..af4109ed0 100644 --- a/bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc +++ b/bundles/components_ready_check/gtest/src/InactiveComponentBundle.cc @@ -40,7 +40,6 @@ class InactiveComponentBundleActivator { private: std::shared_ptr registration{}; - celix_condition condition{}; }; -CELIX_GEN_CXX_BUNDLE_ACTIVATOR(InactiveComponentBundleActivator) \ No newline at end of file +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(InactiveComponentBundleActivator) From 390903a5c20cabc132a502fc80ba5ad02533c917 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 25 Jul 2023 12:58:01 +0200 Subject: [PATCH 15/33] Add components ready check to conanfile --- bundles/components_ready_check/CMakeLists.txt | 1 - conanfile.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/components_ready_check/CMakeLists.txt b/bundles/components_ready_check/CMakeLists.txt index a23d12e17..237e5b0e7 100644 --- a/bundles/components_ready_check/CMakeLists.txt +++ b/bundles/components_ready_check/CMakeLists.txt @@ -15,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -#TODO add conanfile options for COMPONENTS_READY_CHECK celix_subproject(COMPONENTS_READY_CHECK "Bundle that checks if all components becomes active and reports this using a condition service" ON) if (COMPONENTS_READY_CHECK) diff --git a/conanfile.py b/conanfile.py index 604fafd46..de06a2a69 100644 --- a/conanfile.py +++ b/conanfile.py @@ -120,6 +120,7 @@ class CelixConan(ConanFile): "build_shell_bonjour": False, "build_shell_tui": False, "build_shell_wui": False, + "build_components_ready_check": False, "build_examples": False, "build_celix_etcdlib": False, "build_launcher": False, From e20b5621f9036057bc3c160f417d6b32237b14f0 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 25 Jul 2023 13:04:34 +0200 Subject: [PATCH 16/33] Update documentation for components ready check --- bundles/components_ready_check/README.md | 7 ++++++- documents/framework.md | 3 --- documents/subprojects.md | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bundles/components_ready_check/README.md b/bundles/components_ready_check/README.md index 8090077dc..80ded70bc 100644 --- a/bundles/components_ready_check/README.md +++ b/bundles/components_ready_check/README.md @@ -32,7 +32,12 @@ the condition id constant used to register the "components.ready" condition serv The Apache Celix Component Ready Check provides the `Celix::components_ready_check` bundle which registers the "components.ready" condition service. -TODO explain when the "components.ready" condition service is registered. +The "components.ready" condition service will be registered when the "framework.ready" service is registered, +all components have become active and the event queue is empty. + +If the "components.ready" condition service is registered and some components become inactive or the event queue is +not empty, the "components.ready" condition is **not** removed. The "components.ready" condition is meant to indicate +that the components in the initial framework startup phase are ready. ## CMake options diff --git a/documents/framework.md b/documents/framework.md index c773335cf..91d9dbb4f 100644 --- a/documents/framework.md +++ b/documents/framework.md @@ -227,9 +227,6 @@ The Apache Celix framework will provide the following `celix_condition` services - Celix condition "framework.error". This service will be registered when the framework has not started successfully. This can occur if any of the configured bundles fail to start or install. Note that the "framework.error" condition is not part of the OSGi condition specification. -- Celix condition "components.ready". This service will be registered when the "framework.ready" or "framework.error" - service is registered, all components have become active and the event queue is empty. - Note that the "components.ready" condition is not part of the OSGi condition specification. Contrary to the OSGi specification, the Apache Celix framework does not provide a public API for adding or removing framework listeners. Instead, framework condition services can be used. This has the advantage of ensuring no diff --git a/documents/subprojects.md b/documents/subprojects.md index 8529a67b9..cef54d7b5 100644 --- a/documents/subprojects.md +++ b/documents/subprojects.md @@ -32,6 +32,7 @@ Apache Celix is organized into several subprojects. The following subproject are * [Pubsub](../bundles/pubsub/README.md) - An implementation for a publish-subscribe remote message communication system. * [HTTP Admin](../bundles/http_admin/README.md) - An implementation for the OSGi HTTP whiteboard adapted to C and based on civetweb. * [Remote Services](../bundles/cxx_remote_services) - A C++17 adaption and implementation of the OSGi Remote Service Admin specification. +* [Components Ready Check](../bundles/components_ready_check/README.md) - A bundle which checks if all components are ready. Standalone libraries: From aebbf5845f07137a5474b0d8bb385c5fe94258af Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 25 Jul 2023 14:41:59 +0200 Subject: [PATCH 17/33] Fix invalid integer division to floating-point division in C++ use services --- libs/framework/include/celix/UseServiceBuilder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/framework/include/celix/UseServiceBuilder.h b/libs/framework/include/celix/UseServiceBuilder.h index 69648f343..1e86cee6c 100644 --- a/libs/framework/include/celix/UseServiceBuilder.h +++ b/libs/framework/include/celix/UseServiceBuilder.h @@ -93,7 +93,7 @@ namespace celix { template UseServiceBuilder& setTimeout(std::chrono::duration duration) { auto micro = std::chrono::duration_cast(duration).count(); - timeoutInSeconds = micro / 1000000; + timeoutInSeconds = micro / 1000000.0; return *this; } From 228d567f03e60621c54a431791c9dcde5717f817 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 25 Jul 2023 14:43:01 +0200 Subject: [PATCH 18/33] Refactor nextDeadline fw function --- libs/framework/src/framework.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index 3b555addb..88ffccee5 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -1486,22 +1486,27 @@ static void celix_framework_processScheduledEvents(celix_framework_t* fw) { /** * @brief Calculate the next deadline for scheduled events. - * @return The next deadline or a time 0 second and 0 nanoseconds if no scheduled events are available. + * @return The next deadline or 1 second delayed timespec if no events are scheduled. */ -static struct timespec celix_framework_nextDeadlineForScheduledEvents(celix_framework_t* framework) { +static struct timespec celix_framework_nextDeadlineForEventsWait(celix_framework_t* framework) { + bool closestDeadlineSet = false; struct timespec closestDeadline = {0,0}; + celixThreadMutex_lock(&framework->dispatcher.mutex); CELIX_LONG_HASH_MAP_ITERATE(framework->dispatcher.scheduledEvents, entry) { celix_scheduled_event_t *visit = entry.value.ptrValue; struct timespec eventDeadline = celix_scheduledEvent_getNextDeadline(visit); - if (closestDeadline.tv_sec == 0 && closestDeadline.tv_nsec == 0) { + if (!closestDeadlineSet) { closestDeadline = eventDeadline; + closestDeadlineSet = true; } else if (celix_compareTime(&eventDeadline, &closestDeadline) < 0) { closestDeadline = eventDeadline; } } celixThreadMutex_unlock(&framework->dispatcher.mutex); - return closestDeadline; + + struct timespec fallbackDeadline = celixThreadCondition_getDelayedTime(1); //max 1 second wait + return closestDeadlineSet ? closestDeadline : fallbackDeadline; } void celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId) { @@ -1564,9 +1569,6 @@ static bool requiresScheduledEventsProcessing(celix_framework_t* framework) { } static void celix_framework_waitForNextEvent(celix_framework_t* fw, struct timespec nextDeadline) { - if (nextDeadline.tv_sec == 0 && nextDeadline.tv_nsec == 0) { - nextDeadline = celixThreadCondition_getDelayedTime(1); //no next deadline, wait max 1s - } celixThreadMutex_lock(&fw->dispatcher.mutex); if (celix_framework_eventQueueSize(fw) == 0 && !requiresScheduledEventsProcessing(fw) && fw->dispatcher.active) { celixThreadCondition_waitUntil(&fw->dispatcher.cond, &fw->dispatcher.mutex, &nextDeadline); @@ -1586,7 +1588,7 @@ static void *fw_eventDispatcher(void *fw) { while (active) { fw_handleEvents(framework); celix_framework_processScheduledEvents(framework); - struct timespec nextDeadline = celix_framework_nextDeadlineForScheduledEvents(framework); + struct timespec nextDeadline = celix_framework_nextDeadlineForEventsWait(framework); celix_framework_waitForNextEvent(framework, nextDeadline); celixThreadMutex_lock(&framework->dispatcher.mutex); From aa4d438046af51d28e1dd9c13c10636e5ee983e5 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 25 Jul 2023 14:43:23 +0200 Subject: [PATCH 19/33] Update timing in components ready test suite --- .../gtest/src/ComponentsReadyTestSuite.cc | 2 +- .../gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc index 39eb531ef..57c2c016e 100644 --- a/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc +++ b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc @@ -26,7 +26,7 @@ class ComponentsReadyTestSuite : public ::testing::Test { public: - const int USE_SERVICE_TIMEOUT_IN_MS = 2000; // TODO improve test time to a lower value + const int USE_SERVICE_TIMEOUT_IN_MS = 250; const std::string componentsReadyFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; diff --git a/bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc b/bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc index 52dce4378..0b80ff311 100644 --- a/bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc +++ b/bundles/components_ready_check/gtest/src/ComponentsReadyWithErrorInjectionTestSuite.cc @@ -32,7 +32,7 @@ class ComponentsReadyWithErrorInjectionTestSuite : public ::testing::Test { public: - const int USE_SERVICE_TIMEOUT_IN_MS = 2000; // TODO improve test time to a lower value + const int USE_SERVICE_TIMEOUT_IN_MS = 250; const std::string frameworkReadyFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_FRAMEWORK_READY + ")"; const std::string componentsReadyFilter = From 5a44d8228cba7508f0a2caecf281cb0599e9ba1f Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 25 Jul 2023 20:47:16 +0200 Subject: [PATCH 20/33] Add missing build option to conanfile.py --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index de06a2a69..5a98d56b7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -72,6 +72,7 @@ class CelixConan(ConanFile): "build_shell_bonjour": [True, False], "build_shell_tui": [True, False], "build_shell_wui": [True, False], + "build_components_ready_check": [True, False], "build_examples": [True, False], "build_celix_etcdlib": [True, False], "build_launcher": [True, False], From aab28be1979a8467bc0490d38dfd26d0a16a104c Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 27 Jul 2023 20:28:42 +0200 Subject: [PATCH 21/33] Refactor WaitForScheduledEventTest into 2 tests --- .../gtest/src/ScheduledEventTestSuite.cc | 65 +++++++++++++++---- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/libs/framework/gtest/src/ScheduledEventTestSuite.cc b/libs/framework/gtest/src/ScheduledEventTestSuite.cc index 90646a4bd..1e0d07f35 100644 --- a/libs/framework/gtest/src/ScheduledEventTestSuite.cc +++ b/libs/framework/gtest/src/ScheduledEventTestSuite.cc @@ -537,7 +537,7 @@ TEST_F(ScheduledEventTestSuite, CxxCancelOneShotEventBeforeFiredTest) { // Then the event is not fired and does not leak } -TEST_F(ScheduledEventTestSuite, RemoveScheduledEventAsync) { +TEST_F(ScheduledEventTestSuite, RemoveScheduledEventAsyncTest) { std::atomic count{0}; auto callback = [](void* data) { auto* count = static_cast*>(data); @@ -562,14 +562,14 @@ TEST_F(ScheduledEventTestSuite, RemoveScheduledEventAsync) { EXPECT_EQ(0, count.load()); } -TEST_F(ScheduledEventTestSuite, WaitForScheduledEvent) { +TEST_F(ScheduledEventTestSuite, WaitForScheduledEventTest) { std::atomic count{0}; auto callback = [](void* data) { auto* count = static_cast*>(data); count->fetch_add(1); }; - // Given a scheduled event with an initial delay of 1ms and an interval of 1ms + // Given a scheduled event with an initial delay of 10ms and an interval of 10ms celix_scheduled_event_options_t opts{}; opts.initialDelayInSeconds = 0.01; opts.intervalInSeconds = 0.01; @@ -580,35 +580,74 @@ TEST_F(ScheduledEventTestSuite, WaitForScheduledEvent) { // When waiting for the event with a timeout longer than the initial delay auto status = - celix_bundleContext_waitForScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 1); + celix_bundleContext_waitForScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 2); // Then the return status is success - EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_EQ(CELIX_SUCCESS, status) << "Unexpected status" << celix_strerror(status) << std::endl; // And the event is fired EXPECT_EQ(1, count.load()); + // When waiting for the event with a timeout longer than the interval + status = + celix_bundleContext_waitForScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 2); + + // Then the return status is success + EXPECT_EQ(CELIX_SUCCESS, status); + + // And the event is fired again + EXPECT_EQ(2, count.load()); + + celix_bundleContext_removeScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); +} + +TEST_F(ScheduledEventTestSuite, WaitTooShortForScheduledEventTest) { + std::atomic count{0}; + auto callback = [](void *data) { + auto *count = static_cast *>(data); + count->fetch_add(1); + }; + + // Given a scheduled event with an initial delay of 1s and an interval of 1s + celix_scheduled_event_options_t opts{}; + opts.initialDelayInSeconds = 1; + opts.intervalInSeconds = 1; + opts.callbackData = &count; + opts.callback = callback; + long eventId = celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(), &opts); + EXPECT_GE(eventId, 0); + // When waiting too short for the event - status = celix_bundleContext_waitForScheduledEvent( - fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 0.0001); + celix_status_t status = celix_bundleContext_waitForScheduledEvent( + fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 0.0001); // Then the return status is timeout EXPECT_EQ(ETIMEDOUT, status); - // When waiting for the event with a timeout longer than the interval - status = - celix_bundleContext_waitForScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 1); + // And th event is not fired + EXPECT_EQ(0, count.load()); + + // When event is woken up + status = celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); // Then the return status is success EXPECT_EQ(CELIX_SUCCESS, status); - // And the event is fired again - EXPECT_EQ(2, count.load()); + // And the event will be fired + waitFor([&count]() { return count.load() == 1; }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); + EXPECT_EQ(1, count.load()); + + // When waiting too short for the next (interval based) event + status = celix_bundleContext_waitForScheduledEvent( + fw->getFrameworkBundleContext()->getCBundleContext(), eventId, 0.0001); + + // Then the return status is timeout + EXPECT_EQ(ETIMEDOUT, status); celix_bundleContext_removeScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(), eventId); } -TEST_F(ScheduledEventTestSuite, CxxWaitForScheduledEvent) { +TEST_F(ScheduledEventTestSuite, CxxWaitForScheduledEventTest) { std::atomic count{0}; auto callback = [&count]() { count.fetch_add(1); }; From 46e98e963f6b286021db66fde9a08c55f16dff69 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 27 Jul 2023 20:29:17 +0200 Subject: [PATCH 22/33] Remove unused (when build type is not debug) variable. --- .../components_ready_check/src/celix_components_ready_check.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bundles/components_ready_check/src/celix_components_ready_check.c b/bundles/components_ready_check/src/celix_components_ready_check.c index 4eebd348b..f157ef875 100644 --- a/bundles/components_ready_check/src/celix_components_ready_check.c +++ b/bundles/components_ready_check/src/celix_components_ready_check.c @@ -53,9 +53,7 @@ celix_components_ready_check_t* celix_componentsReadyCheck_create(celix_bundle_c } char filter[32]; - int written = - snprintf(filter, sizeof(filter), "(%s=%s)", CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); - assert(written < sizeof(filter)); + snprintf(filter, sizeof(filter), "(%s=%s)", CELIX_CONDITION_ID, CELIX_CONDITION_ID_FRAMEWORK_READY); celix_service_tracking_options_t opts = CELIX_EMPTY_SERVICE_TRACKING_OPTIONS; opts.filter.serviceName = CELIX_CONDITION_SERVICE_NAME; From db93e5aadc7a510aacc03d65e36b9d80d67f4fef Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 10:48:51 +0200 Subject: [PATCH 23/33] Disable fw services for some ei test suites --- .../src/CelixBundleContextBundlesWithErrorTestSuite.cc | 7 ++++--- .../src/CelixFrameworkUtilsErrorInjectionTestSuite.cc | 5 ++++- .../gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc | 5 ++++- libs/framework/src/celix_libloader.h | 6 ++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/libs/framework/gtest/src/CelixBundleContextBundlesWithErrorTestSuite.cc b/libs/framework/gtest/src/CelixBundleContextBundlesWithErrorTestSuite.cc index 89fa33265..b82e21ac1 100644 --- a/libs/framework/gtest/src/CelixBundleContextBundlesWithErrorTestSuite.cc +++ b/libs/framework/gtest/src/CelixBundleContextBundlesWithErrorTestSuite.cc @@ -18,12 +18,11 @@ */ #include + #include "celix_bundle_context.h" #include "celix_framework.h" #include "celix_framework_factory.h" -extern "C" { -#include "celix_libloader.h" -} +#include "celix_constants.h" #include "celix_module_private.h" #include "celix_properties.h" #include "celix_utils_ei.h" @@ -40,6 +39,8 @@ class CelixBundleContextBundlesWithErrorTestSuite : public ::testing::Test { celix_properties_setBool(properties, "LOGHELPER_ENABLE_STDOUT_FALLBACK", true); celix_properties_setBool(properties, "org.osgi.framework.storage.clean", true); celix_properties_set(properties, "org.osgi.framework.storage", ".cacheBundleContextTestFramework"); + celix_properties_setBool(properties, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, false); + celix_properties_set(properties, CELIX_FRAMEWORK_STATIC_EVENT_QUEUE_SIZE, "10"); fw = celix_frameworkFactory_createFramework(properties); ctx = celix_framework_getFrameworkContext(fw); diff --git a/libs/framework/gtest/src/CelixFrameworkUtilsErrorInjectionTestSuite.cc b/libs/framework/gtest/src/CelixFrameworkUtilsErrorInjectionTestSuite.cc index cee8be4bf..d485c06fe 100644 --- a/libs/framework/gtest/src/CelixFrameworkUtilsErrorInjectionTestSuite.cc +++ b/libs/framework/gtest/src/CelixFrameworkUtilsErrorInjectionTestSuite.cc @@ -37,7 +37,10 @@ class CelixFrameworkUtilsErrorInjectionTestSuite : public ::testing::Test { public: CelixFrameworkUtilsErrorInjectionTestSuite () { - framework = celix::createFramework({{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"}}); + framework = celix::createFramework({ + {"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"}, + {CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, "false"} + }); } ~CelixFrameworkUtilsErrorInjectionTestSuite () override { diff --git a/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc b/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc index ea3e96ac0..12d2c965a 100644 --- a/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc +++ b/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc @@ -29,7 +29,10 @@ class ScheduledEventWithErrorInjectionTestSuite : public ::testing::Test { public: ScheduledEventWithErrorInjectionTestSuite() { - fw = celix::createFramework({{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "info"}}); + fw = celix::createFramework({ + {"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "info"}, + {CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, "false"} + }); } ~ScheduledEventWithErrorInjectionTestSuite() noexcept override { diff --git a/libs/framework/src/celix_libloader.h b/libs/framework/src/celix_libloader.h index b6ec0ce93..73bf26bbe 100644 --- a/libs/framework/src/celix_libloader.h +++ b/libs/framework/src/celix_libloader.h @@ -19,6 +19,9 @@ #ifndef CELIX_CELIX_LIBLOADER_H #define CELIX_CELIX_LIBLOADER_H +#ifdef __cplusplus +extern "C" { +#endif #include "celix_bundle_context.h" @@ -91,4 +94,7 @@ celix_bundle_activator_destroy_fp celix_libloader_findBundleActivatorDestroy(cel */ void* celix_libloader_findBundleActivatorSymbolFromAddr(void *addr, const char* name); +#ifdef __cplusplus +} +#endif #endif //CELIX_CELIX_LIBLOADER_H From c5092834c6cff73701c9706c3783d5fc39f59393 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 11:42:35 +0200 Subject: [PATCH 24/33] Add macos and linux prefix to ci build names --- .github/workflows/macos.yml | 4 ++-- .github/workflows/ubuntu.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index b43de2f22..e05bea917 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -8,7 +8,7 @@ on: jobs: - build-conan: + macos-build-conan: runs-on: macOS-11 timeout-minutes: 120 steps: @@ -48,7 +48,7 @@ jobs: run: | conan create -pr:b default -pr:h default -tf examples/conan_test_package -tbf test-build -o celix:celix_cxx17=True -o celix:celix_install_deprecated_api=True --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s . - build-brew: + macos-build-brew: runs-on: macOS-latest timeout-minutes: 120 steps: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 86f6dddb1..0be655a61 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -8,7 +8,7 @@ on: jobs: - build-conan: + linux-build-conan: runs-on: ubuntu-20.04 strategy: fail-fast: false @@ -72,7 +72,7 @@ jobs: run: | conan create -pr:b release -pr:h default -tf examples/conan_test_package -tbf test-build -o celix:celix_cxx17=True -o celix:celix_install_deprecated_api=True --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s . - build-apt: + linux-build-apt: runs-on: ubuntu-22.04 timeout-minutes: 120 steps: From aa3e7d51377161b817fb8139925ac15e4375c88d Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 12:02:14 +0200 Subject: [PATCH 25/33] Refactor ScheduledEventTestSuite to work with a "build on ci" option --- CMakeLists.txt | 2 ++ conanfile.py | 2 ++ libs/framework/gtest/CMakeLists.txt | 3 +++ .../gtest/src/ScheduledEventTestSuite.cc | 16 ++++++++++------ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6654e4ee4..36d5fa245 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,6 +183,8 @@ endif () option(ENABLE_TESTING_DEPENDENCY_MANAGER_FOR_CXX11 "Test the Dependency Manager for C++11 support" OFF) option(ENABLE_TESTING_FOR_CXX14 "Test celix utils and framework C++ header for C++14 support" OFF) +option(CELIX_BUILD_ON_CI "Whether the build is done on a CI server" OFF) + if (CELIX_INSTALL_DEPRECATED_API) #ignore deprecated warnings set(CMAKE_C_FLAGS "-Wno-deprecated-declarations ${CMAKE_C_FLAGS}") diff --git a/conanfile.py b/conanfile.py index 5a98d56b7..53fb5cb47 100644 --- a/conanfile.py +++ b/conanfile.py @@ -85,6 +85,7 @@ class CelixConan(ConanFile): "celix_install_deprecated_api": [True, False], "celix_use_compression_for_bundle_zips": [True, False], "celix_err_buffer_size": "ANY", + "celix_build_on_ci": [True, False], } default_options = { "enable_testing": False, @@ -134,6 +135,7 @@ class CelixConan(ConanFile): "celix_install_deprecated_api": False, "celix_use_compression_for_bundle_zips": True, "celix_err_buffer_size": 512, + "celix_build_on_ci": False, } _cmake = None diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index a073a4d34..a0f1a125a 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -124,6 +124,9 @@ target_compile_definitions(test_framework PRIVATE COND_TEST_BUNDLE_LOC="${COND_TEST_BUNDLE_LOC}" INSTALL_AND_START_BUNDLES_CONFIG_PROPERTIES_FILE="${CMAKE_CURRENT_BINARY_DIR}/install_and_start_bundles.properties" ) +if (CELIX_BUILD_ON_CI) + target_compile_definitions(test_framework PRIVATE CELIX_BUILD_ON_CI=1) +endif (CELIX_BUILD_ON_CI) add_test(NAME test_framework COMMAND test_framework) setup_target_for_coverage(test_framework SCAN_DIR ..) diff --git a/libs/framework/gtest/src/ScheduledEventTestSuite.cc b/libs/framework/gtest/src/ScheduledEventTestSuite.cc index 1e0d07f35..e8291b71f 100644 --- a/libs/framework/gtest/src/ScheduledEventTestSuite.cc +++ b/libs/framework/gtest/src/ScheduledEventTestSuite.cc @@ -25,10 +25,10 @@ class ScheduledEventTestSuite : public ::testing::Test { public: -#ifdef __APPLE__ - const int ALLOWED_ERROR_MARGIN_IN_MS = 500; +#ifdef CELIX_BUILD_ON_CI + const int ALLOWED_ERROR_MARGIN_IN_MS = 1000; #else - const int ALLOWED_ERROR_MARGIN_IN_MS = 200; + const int ALLOWED_ERROR_MARGIN_IN_MS = 100; #endif const double ALLOWED_PROCESSING_TIME_IN_SECONDS = 0.1; @@ -483,11 +483,12 @@ TEST_F(ScheduledEventTestSuite, CxxOneShotScheduledEventRAIITest) { std::atomic count{0}; auto callback = [&count]() { count.fetch_add(1); }; + // And a remove boolean and a remove callback to set the boolean std::atomic removed{false}; auto removeCallback = [&removed]() { removed.store(true); }; { - // And a scoped scheduled event with a initial delay of 50ms + // And a scoped scheduled event with an initial delay of 50ms auto event = fw->getFrameworkBundleContext() ->scheduledEvent() .withName("test cxx one-shot") @@ -498,10 +499,13 @@ TEST_F(ScheduledEventTestSuite, CxxOneShotScheduledEventRAIITest) { // Then the count is not yet increased ASSERT_EQ(0, count.load()); + + // And the remove callback is not yet called + EXPECT_FALSE(removed.load()); } // When the event is out of scope - // Then the remove callback is not yet called, because a one-shot event is not canceled when out of scope + // Then the remove callback is not yet called, because a one-shot event is not canceled when it goes out of scope EXPECT_FALSE(removed.load()); // And count will be increased within the initial delay (including some error margin) @@ -509,7 +513,7 @@ TEST_F(ScheduledEventTestSuite, CxxOneShotScheduledEventRAIITest) { EXPECT_EQ(1, count.load()); // And the remove callback is called shortly after the initial delay - waitFor([&] { return removed.load(); }, std::chrono::milliseconds{10}); + waitFor([&] { return removed.load(); }, std::chrono::milliseconds{ALLOWED_ERROR_MARGIN_IN_MS}); EXPECT_TRUE(removed.load()); } From f34f845db1b1215a20dd3d40c4709bcdc227c1df Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 12:20:12 +0200 Subject: [PATCH 26/33] Configure "build on ci" for github ci actions --- .github/workflows/coverage.yml | 1 + .github/workflows/macos.yml | 2 ++ .github/workflows/ubuntu.yml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9dddec18e..5fb707650 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,6 +27,7 @@ jobs: -o celix:enable_code_coverage=True -o celix:enable_testing_for_cxx14=True -o celix:enable_testing_dependency_manager_for_cxx11=True + -o celix:celix_build_on_ci=True run: | #force reequire libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 conan install . celix/ci -pr:b default -pr:h default -if build ${CONAN_BUILD_OPTIONS} -b missing -b cpputest --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e05bea917..d5706bf51 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -30,6 +30,7 @@ jobs: -o celix:build_all=True -o celix:enable_testing_for_cxx14=True -o celix:enable_testing_dependency_manager_for_cxx11=True + -o celix:celix_build_on_ci=True run: | #force reequire libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 conan install . celix/ci -pr:b default -pr:h default -if build ${CONAN_BUILD_OPTIONS} -b missing -b cpputest --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s @@ -65,6 +66,7 @@ jobs: -DENABLE_TESTING_DEPENDENCY_MANAGER_FOR_CXX11=ON -DENABLE_TESTING_FOR_CXX14=ON -DENABLE_ADDRESS_SANITIZER=ON + -DCELIX_BUILD_ON_CI=ON -DCMAKE_BUILD_TYPE=Release run: | mkdir build install diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 0be655a61..ef3be93f3 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -49,6 +49,7 @@ jobs: -o celix:build_all=True -o celix:enable_testing_for_cxx14=True -o celix:enable_testing_dependency_manager_for_cxx11=True + -o celix:celix_build_on_ci=True run: | #force require libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 conan install . celix/ci -pr:b release -pr:h default -if build ${CONAN_BUILD_OPTIONS} -b missing -b cpputest --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s @@ -109,6 +110,7 @@ jobs: -DRSA_SHM=ON -DRSA_REMOTE_SERVICE_ADMIN_SHM_V2=ON -DSHELL_BONJOUR=ON + -DCELIX_BUILD_ON_CI=ON -DCMAKE_BUILD_TYPE=Debug run: | mkdir build install From 6fc10988f90257d87dc5e3e4a183bedc143e9d36 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 13:18:22 +0200 Subject: [PATCH 27/33] Rename "build on ci" cmake var to ENABLE_TESTING_ON_CI --- .github/workflows/coverage.yml | 4 ++-- .github/workflows/macos.yml | 6 +++--- .github/workflows/ubuntu.yml | 4 ++-- CMakeLists.txt | 2 +- conanfile.py | 5 +++-- libs/framework/gtest/CMakeLists.txt | 6 +++--- libs/framework/gtest/src/ScheduledEventTestSuite.cc | 2 +- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5fb707650..5e1c0d27b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,9 +27,9 @@ jobs: -o celix:enable_code_coverage=True -o celix:enable_testing_for_cxx14=True -o celix:enable_testing_dependency_manager_for_cxx11=True - -o celix:celix_build_on_ci=True + -o celix:enable_testing_on_ci=True run: | - #force reequire libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 + #force require libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 conan install . celix/ci -pr:b default -pr:h default -if build ${CONAN_BUILD_OPTIONS} -b missing -b cpputest --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s - name: Build run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d5706bf51..869f125ef 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -30,9 +30,9 @@ jobs: -o celix:build_all=True -o celix:enable_testing_for_cxx14=True -o celix:enable_testing_dependency_manager_for_cxx11=True - -o celix:celix_build_on_ci=True + -o celix:enable_testing_on_ci=True run: | - #force reequire libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 + #force require libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 conan install . celix/ci -pr:b default -pr:h default -if build ${CONAN_BUILD_OPTIONS} -b missing -b cpputest --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s - name: Build run: | @@ -66,7 +66,7 @@ jobs: -DENABLE_TESTING_DEPENDENCY_MANAGER_FOR_CXX11=ON -DENABLE_TESTING_FOR_CXX14=ON -DENABLE_ADDRESS_SANITIZER=ON - -DCELIX_BUILD_ON_CI=ON + -DENABLE_TESTING_ON_CI=ON -DCMAKE_BUILD_TYPE=Release run: | mkdir build install diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ef3be93f3..31cbd6413 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -49,7 +49,7 @@ jobs: -o celix:build_all=True -o celix:enable_testing_for_cxx14=True -o celix:enable_testing_dependency_manager_for_cxx11=True - -o celix:celix_build_on_ci=True + -o celix:enable_testing_on_ci=True run: | #force require libcurl 7.64.1, due to a sha256 verify issue in libcurl/7.87.0 conan install . celix/ci -pr:b release -pr:h default -if build ${CONAN_BUILD_OPTIONS} -b missing -b cpputest --require-override=libcurl/7.64.1 --require-override=openssl/1.1.1s @@ -110,7 +110,7 @@ jobs: -DRSA_SHM=ON -DRSA_REMOTE_SERVICE_ADMIN_SHM_V2=ON -DSHELL_BONJOUR=ON - -DCELIX_BUILD_ON_CI=ON + -DENABLE_TESTING_ON_CI=ON -DCMAKE_BUILD_TYPE=Debug run: | mkdir build install diff --git a/CMakeLists.txt b/CMakeLists.txt index 36d5fa245..b7cc21003 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,7 +183,7 @@ endif () option(ENABLE_TESTING_DEPENDENCY_MANAGER_FOR_CXX11 "Test the Dependency Manager for C++11 support" OFF) option(ENABLE_TESTING_FOR_CXX14 "Test celix utils and framework C++ header for C++14 support" OFF) -option(CELIX_BUILD_ON_CI "Whether the build is done on a CI server" OFF) +option(ENABLE_TESTING_ON_CI "Whether to enable testing on CI. This influence allowed timing errors during unit tests" OFF) if (CELIX_INSTALL_DEPRECATED_API) #ignore deprecated warnings diff --git a/conanfile.py b/conanfile.py index 53fb5cb47..8f062786f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -85,7 +85,7 @@ class CelixConan(ConanFile): "celix_install_deprecated_api": [True, False], "celix_use_compression_for_bundle_zips": [True, False], "celix_err_buffer_size": "ANY", - "celix_build_on_ci": [True, False], + "enable_testing_on_ci": [True, False], } default_options = { "enable_testing": False, @@ -135,7 +135,7 @@ class CelixConan(ConanFile): "celix_install_deprecated_api": False, "celix_use_compression_for_bundle_zips": True, "celix_err_buffer_size": 512, - "celix_build_on_ci": False, + "enable_testing_on_ci": False, } _cmake = None @@ -166,6 +166,7 @@ def package_id(self): del self.info.options.build_shell_bonjour del self.info.options.enable_testing_dependency_manager_for_cxx11 del self.info.options.enable_testing_for_cxx14 + del self.info.options.enable_testing_on_ci def build_requirements(self): if self.options.enable_testing: diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index a0f1a125a..845fd9d73 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -124,9 +124,9 @@ target_compile_definitions(test_framework PRIVATE COND_TEST_BUNDLE_LOC="${COND_TEST_BUNDLE_LOC}" INSTALL_AND_START_BUNDLES_CONFIG_PROPERTIES_FILE="${CMAKE_CURRENT_BINARY_DIR}/install_and_start_bundles.properties" ) -if (CELIX_BUILD_ON_CI) - target_compile_definitions(test_framework PRIVATE CELIX_BUILD_ON_CI=1) -endif (CELIX_BUILD_ON_CI) +if (ENABLE_TESTING_ON_CI) + target_compile_definitions(test_framework PRIVATE TESTING_ON_CI=1) +endif () add_test(NAME test_framework COMMAND test_framework) setup_target_for_coverage(test_framework SCAN_DIR ..) diff --git a/libs/framework/gtest/src/ScheduledEventTestSuite.cc b/libs/framework/gtest/src/ScheduledEventTestSuite.cc index e8291b71f..eef25f6fc 100644 --- a/libs/framework/gtest/src/ScheduledEventTestSuite.cc +++ b/libs/framework/gtest/src/ScheduledEventTestSuite.cc @@ -25,7 +25,7 @@ class ScheduledEventTestSuite : public ::testing::Test { public: -#ifdef CELIX_BUILD_ON_CI +#ifdef TESTING_ON_CI const int ALLOWED_ERROR_MARGIN_IN_MS = 1000; #else const int ALLOWED_ERROR_MARGIN_IN_MS = 100; From 360f2b4761076fb0d8dc23e0f6c93416da0bafb6 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 14:14:05 +0200 Subject: [PATCH 28/33] Add "build on ci" configuration to test_framework_with_cxx14 --- libs/framework/gtest/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index 845fd9d73..740f37275 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -234,6 +234,9 @@ if (ENABLE_TESTING_FOR_CXX14) COND_TEST_BUNDLE_LOC="${COND_TEST_BUNDLE_LOC}" INSTALL_AND_START_BUNDLES_CONFIG_PROPERTIES_FILE="${CMAKE_CURRENT_BINARY_DIR}/install_and_start_bundles.properties" ) + if (ENABLE_TESTING_ON_CI) + target_compile_definitions(test_framework_with_cxx14 PRIVATE TESTING_ON_CI=1) + endif () add_test(NAME test_framework_with_cxx14 COMMAND test_framework_with_cxx14) setup_target_for_coverage(test_framework_with_cxx14 SCAN_DIR ..) endif () From 9c961f57a9d871fd1f0f9f8651f3272647c710b6 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 14:40:22 +0200 Subject: [PATCH 29/33] Add "build on ci" configuration to components ready test suite --- bundles/components_ready_check/gtest/CMakeLists.txt | 3 +++ .../gtest/src/ComponentsReadyTestSuite.cc | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bundles/components_ready_check/gtest/CMakeLists.txt b/bundles/components_ready_check/gtest/CMakeLists.txt index a98fff208..67b2b7452 100644 --- a/bundles/components_ready_check/gtest/CMakeLists.txt +++ b/bundles/components_ready_check/gtest/CMakeLists.txt @@ -28,6 +28,9 @@ target_link_libraries(test_components_ready PRIVATE GTest::gtest GTest::gtest_main ) +if (ENABLE_TESTING_ON_CI) + target_compile_definitions(test_components_ready PRIVATE TESTING_ON_CI=1) +endif () celix_target_bundle_set_definition(test_components_ready NAME COMPONENTS_READY_CHECK_BUNDLE_SET Celix::components_ready_check) celix_target_bundle_set_definition(test_components_ready NAME ACTIVE_CMP_TEST_BUNDLE_SET ActiveComponentBundle) celix_target_bundle_set_definition(test_components_ready NAME INACTIVE_CMP_TEST_BUNDLE_SET InactiveComponentBundle) diff --git a/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc index 57c2c016e..43d0d4966 100644 --- a/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc +++ b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc @@ -26,7 +26,12 @@ class ComponentsReadyTestSuite : public ::testing::Test { public: +#ifdef TESTING_ON_CI + const int USE_SERVICE_TIMEOUT_IN_MS = 1000; +#else const int USE_SERVICE_TIMEOUT_IN_MS = 250; +#endif + const std::string componentsReadyFilter = std::string{"("} + CELIX_CONDITION_ID + "=" + CELIX_CONDITION_ID_COMPONENTS_READY + ")"; @@ -77,7 +82,7 @@ TEST_F(ComponentsReadyTestSuite, ComponentsReadyWithInactiveComponentTest) { // When a test bundle with a not active-able component is installed celix::installBundleSet(*fw, INACTIVE_CMP_TEST_BUNDLE_SET); - // WAnd the components ready check bundle is installed + // And the components ready check bundle is installed celix::installBundleSet(*fw, COMPONENTS_READY_CHECK_BUNDLE_SET); // Then the "components.ready" condition will not become available, because the test bundle contains a component From 0843c187fa94251e3da4dd46a6617d4da114b21b Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 18:06:12 +0200 Subject: [PATCH 30/33] Add additional ei test for framework bundle --- .../FrameworkBundleWithErrorInjectionTestSuite.cc | 12 ++++++++++-- libs/framework/src/celix_framework_bundle.c | 11 ++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc index 79a16f465..a967c6881 100644 --- a/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc +++ b/libs/framework/gtest/src/FrameworkBundleWithErrorInjectionTestSuite.cc @@ -22,7 +22,7 @@ #include "celix/FrameworkFactory.h" #include "celix_condition.h" #include "celix_framework_bundle.h" - +#include "framework_private.h" #include "celix_properties_ei.h" #include "celix_threads_ei.h" #include "malloc_ei.h" @@ -37,6 +37,7 @@ class FrameworkBundleWithErrorInjectionTestSuite : public ::testing::Test { ~FrameworkBundleWithErrorInjectionTestSuite() noexcept override { // reset error injections + celix_ei_expect_malloc(nullptr, 0, nullptr); celix_ei_expect_calloc(nullptr, 0, nullptr); celix_ei_expect_celixThreadMutex_create(nullptr, 0, CELIX_SUCCESS); celix_ei_expect_celix_properties_create(nullptr, 0, nullptr); @@ -68,7 +69,7 @@ TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorStartingFrameworkBundleT } TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringFrameworkReadyConditionTest) { - // When an error injection for celix_properties_create is primed when called from celix_frameworkBundle_handleFrameworkEvent + // Given an error injection for celix_properties_create is primed when called from celix_frameworkBundle_handleFrameworkEvent celix_ei_expect_celix_properties_create((void*)celix_frameworkBundle_handleFrameworkEvent, 0, nullptr); // And a framework instance is created @@ -83,3 +84,10 @@ TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorRegisteringFrameworkRead EXPECT_EQ(count, 0); } +TEST_F(FrameworkBundleWithErrorInjectionTestSuite, ErrorAddingFwListenerTest) { + // Given an error injection for malloc is primed when called from fw_addFrameworkListener + celix_ei_expect_malloc((void*)fw_addFrameworkListener, 0, nullptr); + + // Then an exception is expected when creating a framework instance, because fw bundle cannot be started + EXPECT_ANY_THROW(celix::createFramework()); +} diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c index 89feff348..d8f691bf4 100644 --- a/libs/framework/src/celix_framework_bundle.c +++ b/libs/framework/src/celix_framework_bundle.c @@ -125,14 +125,19 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ return CELIX_SUCCESS; } + celix_status_t status = fw_addFrameworkListener( + celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); + if (status != CELIX_SUCCESS) { + celix_bundleContext_log( + ctx, CELIX_LOG_LEVEL_ERROR, "Cannot add framework listener for framework bundle"); + return status; + } + celix_frameworkBundle_registerTrueCondition(act); if (act->trueConditionSvcId < 0) { return CELIX_BUNDLE_EXCEPTION; } - fw_addFrameworkListener( - celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); - return CELIX_SUCCESS; } From d223f7eddf3f69a504092c36a3667b07aaa88142 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 19:11:25 +0200 Subject: [PATCH 31/33] Fix memleak in framework bundle --- libs/framework/src/celix_framework_bundle.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/framework/src/celix_framework_bundle.c b/libs/framework/src/celix_framework_bundle.c index d8f691bf4..6e40c58d8 100644 --- a/libs/framework/src/celix_framework_bundle.c +++ b/libs/framework/src/celix_framework_bundle.c @@ -118,6 +118,8 @@ celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framewor celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_t* ctx) { celix_framework_bundle_t* act = userData; + celix_framework_t* fw = celix_bundleContext_getFramework(ctx); + celix_bundle_t* bnd = celix_bundleContext_getBundle(ctx); bool conditionsEnabled = celix_bundleContext_getPropertyAsBool( ctx, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT); @@ -125,8 +127,7 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ return CELIX_SUCCESS; } - celix_status_t status = fw_addFrameworkListener( - celix_bundleContext_getFramework(ctx), celix_bundleContext_getBundle(ctx), &act->listener); + celix_status_t status = fw_addFrameworkListener(fw, bnd, &act->listener); if (status != CELIX_SUCCESS) { celix_bundleContext_log( ctx, CELIX_LOG_LEVEL_ERROR, "Cannot add framework listener for framework bundle"); @@ -135,6 +136,7 @@ celix_status_t celix_frameworkBundle_start(void* userData, celix_bundle_context_ celix_frameworkBundle_registerTrueCondition(act); if (act->trueConditionSvcId < 0) { + fw_removeFrameworkListener(fw, bnd, &act->listener); return CELIX_BUNDLE_EXCEPTION; } From 5f77f428e5bf89714258a11f5db63e5ac71d413f Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 30 Jul 2023 19:42:42 +0200 Subject: [PATCH 32/33] Relax timing requirements for CI testing on macos --- .../gtest/src/ComponentsReadyTestSuite.cc | 4 +++- libs/framework/gtest/src/ScheduledEventTestSuite.cc | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc index 43d0d4966..3dfbf94f6 100644 --- a/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc +++ b/bundles/components_ready_check/gtest/src/ComponentsReadyTestSuite.cc @@ -26,7 +26,9 @@ class ComponentsReadyTestSuite : public ::testing::Test { public: -#ifdef TESTING_ON_CI +#if defined(__APPLE__) && defined(TESTING_ON_CI) + const int USE_SERVICE_TIMEOUT_IN_MS = 1500; +#elif TESTING_ON_CI const int USE_SERVICE_TIMEOUT_IN_MS = 1000; #else const int USE_SERVICE_TIMEOUT_IN_MS = 250; diff --git a/libs/framework/gtest/src/ScheduledEventTestSuite.cc b/libs/framework/gtest/src/ScheduledEventTestSuite.cc index eef25f6fc..497c751e1 100644 --- a/libs/framework/gtest/src/ScheduledEventTestSuite.cc +++ b/libs/framework/gtest/src/ScheduledEventTestSuite.cc @@ -25,7 +25,9 @@ class ScheduledEventTestSuite : public ::testing::Test { public: -#ifdef TESTING_ON_CI +#if defined(__APPLE__) && defined(TESTING_ON_CI) + const int ALLOWED_ERROR_MARGIN_IN_MS = 1500; +#elif TESTING_ON_CI const int ALLOWED_ERROR_MARGIN_IN_MS = 1000; #else const int ALLOWED_ERROR_MARGIN_IN_MS = 100; From 43a913167324e5ce4c703860cec06cd0d4ecf308 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Mon, 31 Jul 2023 11:42:47 +0800 Subject: [PATCH 33/33] Add missing intra-package dependency deduction and test package. --- conanfile.py | 3 +++ examples/conan_test_package/CMakeLists.txt | 5 +++++ examples/conan_test_package/conanfile.py | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/conanfile.py b/conanfile.py index c7a8ccae1..aa8444ee9 100644 --- a/conanfile.py +++ b/conanfile.py @@ -348,6 +348,9 @@ def configure(self): if self.options.celix_install_deprecated_api: self.options.build_framework = True + if self.options.build_components_ready_check: + self.options.build_framework = True + if self.options.build_rcm: self.options.build_utils = True diff --git a/examples/conan_test_package/CMakeLists.txt b/examples/conan_test_package/CMakeLists.txt index 2de423d48..d3889bc7c 100644 --- a/examples/conan_test_package/CMakeLists.txt +++ b/examples/conan_test_package/CMakeLists.txt @@ -362,4 +362,9 @@ option(TEST_UTILS "Test utils" OFF) if (TEST_UTILS) add_executable(use_utils test_utils.c) target_link_libraries(use_utils PRIVATE Celix::utils) +endif () + +option(TEST_COMPONENTS_READY_CHECK "Test the components.ready condition checking bundle" OFF) +if (TEST_COMPONENTS_READY_CHECK) + add_celix_container("use_components_ready_check" BUNDLES Celix::components_ready_check hello) endif () \ No newline at end of file diff --git a/examples/conan_test_package/conanfile.py b/examples/conan_test_package/conanfile.py index 88c58a8e2..078632092 100644 --- a/examples/conan_test_package/conanfile.py +++ b/examples/conan_test_package/conanfile.py @@ -65,6 +65,7 @@ def build(self): cmake.definitions["TEST_SHELL_BONJOUR"] = self.options["celix"].build_shell_bonjour cmake.definitions["TEST_CELIX_DFI"] = self.options["celix"].build_celix_dfi cmake.definitions["TEST_UTILS"] = self.options["celix"].build_utils + cmake.definitions["TEST_COMPONENTS_READY_CHECK"] = self.options["celix"].build_components_ready_check cmake.definitions["CMAKE_PROJECT_test_package_INCLUDE"] = os.path.join(self.build_folder, "conan_paths.cmake") # the following is workaround https://github.com/conan-io/conan/issues/7192 if self.settings.os == "Linux": @@ -163,3 +164,6 @@ def test(self): self.run("./use_celix_dfi", run_environment=True) if self.options["celix"].build_utils: self.run("./use_utils", run_environment=True) + if self.options["celix"].build_components_ready_check: + self.run("./use_components_ready_check", + cwd=os.path.join("deploy", "use_components_ready_check"), run_environment=True)