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


Reply via email to