You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by pn...@apache.org on 2023/06/30 18:33:26 UTC

[celix] 05/06: Refactor conditions to framework bundle

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

pnoltes pushed a commit to branch feature/585-celix-conditions
in repository https://gitbox.apache.org/repos/asf/celix.git

commit b5964b6427db0f0004162b86f9230dbc90fc1498
Author: Pepijn Noltes <pe...@gmail.com>
AuthorDate: Thu Jun 29 23:02:50 2023 +0200

    Refactor conditions to framework bundle
---
 documents/conditions.md                            |  23 ---
 documents/framework.md                             |   4 +
 libs/framework/CMakeLists.txt                      |   1 +
 .../src/CelixBundleContextBundlesTestSuite.cc      |  10 +-
 .../src/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(-)

diff --git a/documents/conditions.md b/documents/conditions.md
deleted file mode 100644
index b2c170ee..00000000
--- a/documents/conditions.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: Apache Celix Conditions
----
-
-<!--
-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.
--->
-
-# Apache Celix Conditions
-TODO
\ No newline at end of file
diff --git a/documents/framework.md b/documents/framework.md
index 4eb0b99d..d697bd4a 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 3cde3860..ad6c5921 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 37cf9b2a..fd5c5421 100644
--- a/libs/framework/gtest/src/CelixBundleContextBundlesTestSuite.cc
+++ b/libs/framework/gtest/src/CelixBundleContextBundlesTestSuite.cc
@@ -46,10 +46,12 @@ public:
     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 f9115f41..8780a4b7 100644
--- a/libs/framework/gtest/src/CelixBundleContextServicesTestSuite.cc
+++ b/libs/framework/gtest/src/CelixBundleContextServicesTestSuite.cc
@@ -47,6 +47,7 @@ public:
         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 7c0e7db9..004b87bd 100644
--- a/libs/framework/gtest/src/CxxBundleContextTestSuite.cc
+++ b/libs/framework/gtest/src/CxxBundleContextTestSuite.cc
@@ -33,10 +33,10 @@ public:
     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<celix_framework_t>{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 55fe37a1..8c442e8b 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 749752f7..cd99d403 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 00000000..282f5511
--- /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 00000000..29776bc5
--- /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 c993e80a..d9015c2d 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 2482b352..0f8bcfbf 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