PengZheng commented on code in PR #588: URL: https://github.com/apache/celix/pull/588#discussion_r1260597606
########## documents/framework.md: ########## @@ -206,6 +206,36 @@ add_executable(create_framework_with_celix_launcher src/launcher.c) target_link_libraries(create_framework_with_celix_launcher PRIVATE Celix::framework) ``` +## 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 Review Comment: Empty event queue is a transient state (bundles are free to use `celix_framework_fireGenericEvent` whenever they like), and thus does not qualify as part of long lasting condition. ########## libs/framework/include/celix_condition.h: ########## @@ -0,0 +1,100 @@ +/* + * 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_ + +#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 "condition.id" + +/** + * @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 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 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 + 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. Review Comment: But not both? ########## libs/framework/src/celix_framework_bundle.c: ########## @@ -0,0 +1,223 @@ +/* + * 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_constants.h" +#include "celix_threads.h" +#include "celix_dependency_manager.h" +#include "framework_private.h" + +#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT true + +/** + * @brief Celix framework bundle activator struct. + */ +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. */ + + celix_thread_mutex_t mutex; /**< protects below. */ + 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) { + *userData = NULL; + celix_framework_bundle_t* act = calloc(1, sizeof(*act)); + if (!act) { + return ENOMEM; + } + + celix_status_t status = celixThreadMutex_create(&act->mutex, NULL); + if (status != CELIX_SUCCESS) { + free(act); + return status; + } + + act->ctx = ctx; + act->trueConditionSvcId = -1L; + 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; + + return CELIX_SUCCESS; +} + +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; + 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); + } else { + celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for true condition service"); + } +} + +celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framework_event_t* event) { + framework_listener_t* 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); + + 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 (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 event received -> registering framework.error condition service"); + } + 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_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; Review Comment: Note that `celix_dependencyManager_allComponentsActive` calls `celix_bundleContext_useBundles`, which should really be `celix_framework_useActiveBundles` to be absolutely safe. 1000Hz polling seems scary. ########## libs/framework/src/framework.c: ########## @@ -439,53 +437,66 @@ 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; } -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_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)); - 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)); + 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); + 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_status_t startStatus = framework_autoStartConfiguredBundles(framework); + celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); - 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 (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); Review Comment: This impairs start-up speed for several hundred milli-seconds if there are 20+ bundles. ########## libs/framework/src/framework.c: ########## @@ -439,53 +437,66 @@ 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; } -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_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)); - 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)); + 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); + 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_status_t startStatus = framework_autoStartConfiguredBundles(framework); + celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); - 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 (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 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"); Review Comment: `status == CELIX_SUCCESS` ########## libs/framework/src/framework.c: ########## @@ -439,53 +437,66 @@ 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; } -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_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)); - 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)); + 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); + 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_status_t startStatus = framework_autoStartConfiguredBundles(framework); + celix_status_t installStatus = framework_autoInstallConfiguredBundles(framework); - 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 (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 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); } - framework_autoStartConfiguredBundles(framework); - framework_autoInstallConfiguredBundles(framework); - - 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"); Review Comment: Unnecessary, `status == CELIX_SUCCESS` ########## libs/framework/src/framework.c: ########## @@ -527,16 +546,19 @@ 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); } } else { fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Could not auto install bundles, out of memory."); } celix_utils_freeStringIfNotEqual(autoStartBuffer, autoStart); + return status;; Review Comment: We need error code for out of memory case. ########## libs/framework/src/framework.c: ########## @@ -548,8 +570,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; Review Comment: If a bundle fails to start, status is not set correctly (it is still CELIX_SUCCESS). We need a test case for this. ########## documents/framework.md: ########## @@ -206,6 +206,36 @@ add_executable(create_framework_with_celix_launcher src/launcher.c) target_link_libraries(create_framework_with_celix_launcher PRIVATE Celix::framework) ``` +## 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. +- 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. Review Comment: Transient state as part of long lasting condition. ########## libs/framework/src/celix_framework_bundle.c: ########## @@ -0,0 +1,223 @@ +/* + * 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_constants.h" +#include "celix_threads.h" +#include "celix_dependency_manager.h" +#include "framework_private.h" + +#define CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT true + +/** + * @brief Celix framework bundle activator struct. + */ +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. */ + + celix_thread_mutex_t mutex; /**< protects below. */ + 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) { + *userData = NULL; + celix_framework_bundle_t* act = calloc(1, sizeof(*act)); + if (!act) { + return ENOMEM; + } + + celix_status_t status = celixThreadMutex_create(&act->mutex, NULL); + if (status != CELIX_SUCCESS) { + free(act); + return status; + } + + act->ctx = ctx; + act->trueConditionSvcId = -1L; + 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; + + return CELIX_SUCCESS; +} + +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; + 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); + } else { + celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_ERROR, "Cannot create properties for true condition service"); + } +} + +celix_status_t celix_frameworkBundle_handleFrameworkEvent(void* handle, framework_event_t* event) { + framework_listener_t* 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); + + 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 (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 event received -> registering framework.error condition service"); + } + 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_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; + + bool conditionsEnabled = celix_bundleContext_getPropertyAsBool( + ctx, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED, CELIX_FRAMEWORK_CONDITION_SERVICES_ENABLED_DEFAULT); + if (!conditionsEnabled) { + return CELIX_SUCCESS; + } + + celix_frameworkBundle_registerTrueCondition(act); + if (act->trueConditionSvcId < 0) { + return CELIX_BUNDLE_EXCEPTION; + } + + fw_addFrameworkListener( Review Comment: return value should be checked. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@celix.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org