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

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

commit 950e5f65521ac3337d63dc6803fa157ea251e168
Author: Pepijn Noltes <[email protected]>
AuthorDate: Sun Jun 11 19:52:36 2023 +0200

    Add remove callback for scheduled event
---
 documents/README.md                                |   1 +
 .../readme_c_examples/CMakeLists.txt               |   6 +
 .../src/schedule_events_bundle_activator.c         |  70 ++++
 .../readme_cxx_examples/CMakeLists.txt             |   7 +
 .../src/ScheduleEventsBundleActivator.cc           |  51 +++
 .../framework/gtest/src/ScheduledEventTestSuite.cc | 384 ++++++++++++++++-----
 .../ScheduledEventWithErrorInjectionTestSuite.cc   |   7 +-
 libs/framework/include/celix/BundleContext.h       |   9 +
 libs/framework/include/celix/Framework.h           |   2 +-
 libs/framework/include/celix/ScheduledEvent.h      | 143 ++++++++
 .../include/celix/ScheduledEventBuilder.h          | 128 +++++++
 libs/framework/include/celix/dm/Component.h        |   2 +
 .../framework/include/celix/dm/DependencyManager.h |   3 +
 libs/framework/include/celix_bundle_context.h      |  45 ++-
 libs/framework/include/celix_dependency_manager.h  |   2 +
 libs/framework/include/celix_framework.h           |  30 +-
 libs/framework/src/bundle_context.c                |  49 ++-
 libs/framework/src/celix_scheduled_event.c         |  67 ++--
 libs/framework/src/celix_scheduled_event.h         |  27 +-
 libs/framework/src/framework.c                     |  77 +++--
 libs/framework/src/framework_private.h             |  19 +-
 21 files changed, 917 insertions(+), 212 deletions(-)

diff --git a/documents/README.md b/documents/README.md
index 85e2806b..4b490e5d 100644
--- a/documents/README.md
+++ b/documents/README.md
@@ -86,5 +86,6 @@ bundles contains binaries depending on the stdlibc++ library.
   * [Apache Celix Framework](framework.md)
   * [Apache Celix Containers](containers.md)
   * [Apache Celix Patterns](patterns.md)
+  * [Apache Celix Scheduled Events](scheduled_events.md)
 * [Apache Celix CMake Commands](cmake_commands)
 * [Apache Celix Sub Projects](subprojects.md)
diff --git a/examples/celix-examples/readme_c_examples/CMakeLists.txt 
b/examples/celix-examples/readme_c_examples/CMakeLists.txt
index 6ea7c06d..e934c067 100644
--- a/examples/celix-examples/readme_c_examples/CMakeLists.txt
+++ b/examples/celix-examples/readme_c_examples/CMakeLists.txt
@@ -77,6 +77,11 @@ add_celix_bundle(component_with_service_dependency_bundle
 )
 target_link_libraries(component_with_service_dependency_bundle PRIVATE 
Celix::shell_api)
 
+add_celix_bundle(schedule_events_bundle
+    VERSION 1.0.0
+    SOURCES src/schedule_events_bundle_activator.c
+)
+
 add_celix_container(readme_c_examples_container
     C
     BUNDLES
@@ -88,6 +93,7 @@ add_celix_container(readme_c_examples_container
         simple_component_bundle
         component_with_provided_service_bundle
         component_with_service_dependency_bundle
+        schedule_events_bundle
 )
 
 
diff --git 
a/examples/celix-examples/readme_c_examples/src/schedule_events_bundle_activator.c
 
b/examples/celix-examples/readme_c_examples/src/schedule_events_bundle_activator.c
new file mode 100644
index 00000000..b1718839
--- /dev/null
+++ 
b/examples/celix-examples/readme_c_examples/src/schedule_events_bundle_activator.c
@@ -0,0 +1,70 @@
+/*
+ * 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 <stdio.h>
+#include <celix_bundle_activator.h>
+
+typedef struct schedule_events_bundle_activator_data {
+    celix_bundle_context_t* ctx;
+    long scheduledEventId;
+} schedule_events_bundle_activator_data_t;
+
+void scheduleEventsBundle_oneShot(void* data) {
+    schedule_events_bundle_activator_data_t* act = data;
+    celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_INFO, "One shot 
scheduled event fired");
+}
+
+void scheduleEventsBundle_process(void* data) {
+    schedule_events_bundle_activator_data_t* act = data;
+    celix_bundleContext_log(act->ctx, CELIX_LOG_LEVEL_INFO, "Recurring 
scheduled event fired");
+}
+
+static celix_status_t 
scheduleEventsBundle_start(schedule_events_bundle_activator_data_t *data, 
celix_bundle_context_t *ctx) {
+    data->ctx = ctx;
+
+    //schedule recurring event
+    {
+        celix_scheduled_event_options_t opts = 
CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS;
+        opts.name = "recurring scheduled event example";
+        opts.initialDelayInSeconds = 0.1;
+        opts.intervalInSeconds = 1.0;
+        opts.callbackData = data;
+        opts.callback = scheduleEventsBundle_process;
+        data->scheduledEventId = celix_bundleContext_scheduleEvent(ctx, &opts);
+    }
+
+    //schedule one time event
+    {
+        celix_scheduled_event_options_t opts = 
CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS;
+        opts.name = "one shot scheduled event example";
+        opts.initialDelayInSeconds = 0.1;
+        opts.callbackData = data;
+        opts.callback = scheduleEventsBundle_oneShot;
+        celix_bundleContext_scheduleEvent(ctx, &opts);
+    }
+
+    return CELIX_SUCCESS;
+}
+
+static celix_status_t 
scheduleEventsBundle_stop(schedule_events_bundle_activator_data_t *data, 
celix_bundle_context_t *ctx) {
+    celix_bundleContext_removeScheduledEvent(ctx, data->scheduledEventId);
+    return CELIX_SUCCESS;
+}
+
+CELIX_GEN_BUNDLE_ACTIVATOR(schedule_events_bundle_activator_data_t, 
scheduleEventsBundle_start, scheduleEventsBundle_stop)
diff --git a/examples/celix-examples/readme_cxx_examples/CMakeLists.txt 
b/examples/celix-examples/readme_cxx_examples/CMakeLists.txt
index 1656d373..0ee6b955 100644
--- a/examples/celix-examples/readme_cxx_examples/CMakeLists.txt
+++ b/examples/celix-examples/readme_cxx_examples/CMakeLists.txt
@@ -140,6 +140,12 @@ add_celix_bundle(ComponentWithServiceDependencyBundle
 )
 target_link_libraries(ComponentWithServiceDependencyBundle PRIVATE 
Celix::shell_api)
 
+
+add_celix_bundle(ScheduleEventsBundle
+    VERSION 1.0.0
+    SOURCES src/ScheduleEventsBundleActivator.cc
+)
+
 add_celix_container(ReadmeCxxExamplesContainer
     BUNDLES
         Celix::ShellCxx
@@ -151,6 +157,7 @@ add_celix_container(ReadmeCxxExamplesContainer
         SimpleComponentBundle
         ComponentWithProvidedServiceBundle
         ComponentWithServiceDependencyBundle
+        ScheduleEventsBundle
 )
 
 if (TARGET my_shell_command_provider_bundle)
diff --git 
a/examples/celix-examples/readme_cxx_examples/src/ScheduleEventsBundleActivator.cc
 
b/examples/celix-examples/readme_cxx_examples/src/ScheduleEventsBundleActivator.cc
new file mode 100644
index 00000000..95bf0d12
--- /dev/null
+++ 
b/examples/celix-examples/readme_cxx_examples/src/ScheduleEventsBundleActivator.cc
@@ -0,0 +1,51 @@
+/*
+ * 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 <iostream>
+#include "celix/BundleActivator.h"
+
+class ScheduleEventsBundleActivator {
+public:
+    explicit ScheduleEventsBundleActivator(const 
std::shared_ptr<celix::BundleContext>& ctx) {
+        //schedule recurring event
+        event = ctx->scheduledEvent()
+                .withInitialDelay(std::chrono::milliseconds {10})
+                .withInterval(std::chrono::seconds{1})
+                .withCallback([ctx] {
+                    ctx->logInfo("Recurring scheduled event fired");
+                })
+                .build();
+
+        //schedule one time event
+        ctx->scheduledEvent()
+                .withInitialDelay(std::chrono::milliseconds {10})
+                .withCallback([ctx] {
+                    ctx->logInfo("One shot scheduled event fired");
+                })
+                .build();
+    }
+
+    ~ScheduleEventsBundleActivator() noexcept {
+        std::cout << "Goodbye world" << std::endl;
+    }
+private:
+    celix::ScheduledEvent event{};
+};
+
+CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ScheduleEventsBundleActivator)
diff --git a/libs/framework/gtest/src/ScheduledEventTestSuite.cc 
b/libs/framework/gtest/src/ScheduledEventTestSuite.cc
index 295f8c27..9e53e69b 100644
--- a/libs/framework/gtest/src/ScheduledEventTestSuite.cc
+++ b/libs/framework/gtest/src/ScheduledEventTestSuite.cc
@@ -24,10 +24,8 @@
 #include "celix_scheduled_event.h"
 
 class ScheduledEventTestSuite : public ::testing::Test {
-public:
-    ScheduledEventTestSuite() {
-        fw = 
celix::createFramework({{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"}});
-    }
+  public:
+    ScheduledEventTestSuite() { fw = 
celix::createFramework({{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"}}); }
 
     std::shared_ptr<celix::Framework> fw{};
 };
@@ -37,27 +35,37 @@ TEST_F(ScheduledEventTestSuite, OnceShotEventTest) {
 
     struct event_info {
         std::atomic<int> count{0};
+        std::atomic<bool> removed{false};
     };
     event_info info{};
 
-    auto callback = [](void *data) {
+    auto callback = [](void* data) {
         auto* info = static_cast<event_info*>(data);
         info->count++;
     };
 
-    //When I create a scheduled event with a 0 delay and a 0 interval (one 
short, directly scheduled)
-    celix_scheduled_event_options_t opts{};
-    opts.eventData = &info;
-    opts.eventCallback = callback;
+    auto removeCallback = [](void* data) {
+        auto* info = static_cast<event_info*>(data);
+        info->removed = true;
+    };
 
+    // When I create a scheduled event with a 0 delay and a 0 interval (one 
short, directly scheduled)
+    celix_scheduled_event_options_t opts{};
+    opts.callbackData = &info;
+    opts.callback = callback;
+    opts.removeCallbackData = &info;
+    opts.removeCallback = removeCallback;
 
-    //And I schedule the event
-    long eventId = 
celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts);
+    // And I schedule the event
+    long eventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), 
&opts);
     EXPECT_GE(eventId, 0);
 
-    //Then the event is called once
+    // Then the event is called once
     std::this_thread::sleep_for(std::chrono::milliseconds{10});
     EXPECT_EQ(1, info.count.load());
+
+    // And the event remove callback is called
+    EXPECT_TRUE(info.removed.load());
 }
 
 TEST_F(ScheduledEventTestSuite, ScheduledEventTest) {
@@ -65,34 +73,48 @@ TEST_F(ScheduledEventTestSuite, ScheduledEventTest) {
 
     struct event_info {
         std::atomic<int> count{0};
+        std::atomic<bool> removed{false};
     };
     event_info info{};
 
-    auto callback = [](void *data) {
+    auto callback = [](void* data) {
         auto* info = static_cast<event_info*>(data);
         info->count++;
     };
 
-    //When I create a scheduled event with a 10ms delay and a 20 ms interval
+    auto removeCallback = [](void* data) {
+        auto* info = static_cast<event_info*>(data);
+        info->removed = true;
+    };
+
+    // When I create a scheduled event with a 10ms delay and a 20 ms interval
     celix_scheduled_event_options_t opts{};
-    opts.eventName = "Scheduled event test";
+    opts.name = "Scheduled event test";
     opts.initialDelayInSeconds = 0.01;
     opts.intervalInSeconds = 0.02;
-    opts.eventData = &info;
-    opts.eventCallback = callback;
+    opts.callbackData = &info;
+    opts.callback = callback;
+    opts.removeCallbackData = &info;
+    opts.removeCallback = removeCallback;
 
-    //And I schedule the event
-    long eventId = 
celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts);
+    // And I schedule the event
+    long eventId = celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), 
&opts);
     EXPECT_GE(eventId, 0);
 
-    //And wait more than 10 ms + 2x 20ms + 10ms error margin
+    // And wait more than 10 ms + 2x 20ms + 10ms error margin
     std::this_thread::sleep_for(std::chrono::milliseconds{60});
 
-    //Then the event is called at least 3 times
-    EXPECT_GE(info.count.load(),3);
+    // Then the event is called at least 3 times
+    EXPECT_GE(info.count.load(), 3);
 
-    //And when I remove the event
+    // And the event remove callback is not called
+    EXPECT_FALSE(info.removed.load());
+
+    // And when I remove the event
     celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), 
eventId);
+
+    // Then the event remove callback is called
+    EXPECT_TRUE(info.removed.load());
 }
 
 TEST_F(ScheduledEventTestSuite, ManyScheduledEventTest) {
@@ -102,95 +124,102 @@ TEST_F(ScheduledEventTestSuite, ManyScheduledEventTest) {
         std::atomic<int> count{0};
     };
     event_info info{};
-    auto callback = [](void *data) {
+    auto callback = [](void* data) {
         auto* info = static_cast<event_info*>(data);
         info->count++;
     };
 
     std::vector<long> eventIds{};
 
-    //When 1000 scheduled events are with a random interval between 1 and 59 ms
+    // When 1000 scheduled events are with a random interval between 1 and 59 
ms
     for (int i = 0; i < 1000; ++i) {
-        //When I create a scheduled event with a 10ms delay and a 20 ms 
interval
+        // When I create a scheduled event with a 10ms delay and a 20 ms 
interval
         celix_scheduled_event_options_t opts{};
-        opts.eventName = "Scheduled event test";
-        opts.intervalInSeconds = (i % 50) / 100.0; //note will also contain 
one-shot scheduled events
-        opts.eventData = &info;
-        opts.eventCallback = callback;
-        long eventId = 
celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts);
+        opts.name = "Scheduled event test";
+        opts.intervalInSeconds = (i % 50) / 100.0; // note will also contain 
one-shot scheduled events
+        opts.callbackData = &info;
+        opts.callback = callback;
+        long eventId = 
celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts);
         EXPECT_GE(eventId, 0);
-        if (opts.intervalInSeconds > 0) { //not a one-shot event
+        if (opts.intervalInSeconds > 0) { // not a one-shot event
             eventIds.push_back(eventId);
         }
     }
 
-    //And some time passes, to let some events be called
+    // And some time passes, to let some events be called
     std::this_thread::sleep_for(std::chrono::milliseconds{10});
 
-    //Then the events can safely be removed
+    // Then the events can safely be removed
     for (auto id : eventIds) {
         celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), id);
     }
     EXPECT_GT(info.count, 0);
 }
 
-
 TEST_F(ScheduledEventTestSuite, AddWithoutRemoveScheduledEventTest) {
-    //When I create a scheduled event
+    // When I create a scheduled event
     auto ctx = fw->getFrameworkBundleContext();
 
-    auto callback = [](void */*data*/) {
-        fprintf(stdout, "Scheduled event called\n");
-    };
+    auto callback = [](void* /*data*/) { fprintf(stdout, "Scheduled event 
called\n"); };
     celix_scheduled_event_options_t opts{};
-    opts.eventName = "Unremoved scheduled event test";
+    opts.name = "Unremoved scheduled event test";
     opts.intervalInSeconds = 0.02;
-    opts.eventCallback = callback;
-    long scheduledEventId = 
celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts);
+    opts.callback = callback;
+    long scheduledEventId = 
celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts);
     EXPECT_GE(scheduledEventId, 0);
 
-    //And I do not remove the event, but let the bundle framework stpp
-    //Then I expect no memory leaks
+    // And I do not remove the event, but let the bundle framework stpp
+    // Then I expect no memory leaks
 }
 
 TEST_F(ScheduledEventTestSuite, AddWithoutRemoveOneShotEventTest) {
-    //When I create a one-shot scheduled event with a long initial delay
+    // When I create a one-shot scheduled event with a long initial delay
     auto ctx = fw->getFrameworkBundleContext();
 
-    auto callback = [](void */*data*/) {
-        FAIL() << "Scheduled event called, but should not be called";
-    };
+    auto callback = [](void* /*data*/) { FAIL() << "Scheduled event called, 
but should not be called"; };
     celix_scheduled_event_options_t opts{};
-    opts.eventName = "Unremoved one-shot scheduled event test";
+    opts.name = "Unremoved one-shot scheduled event test";
     opts.initialDelayInSeconds = 100;
-    opts.eventCallback = callback;
-    long scheduledEventId = 
celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts);
+    opts.callback = callback;
+    long scheduledEventId = 
celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts);
     EXPECT_GE(scheduledEventId, 0);
 
-    //And I do let the one-shot event trigger, but let the bundle framework 
stop
-    //Then I expect no memory leaks
+    // And I do let the one-shot event trigger, but let the bundle framework 
stop
+    // Then I expect no memory leaks
 }
 
 TEST_F(ScheduledEventTestSuite, InvalidOptionsAndArgumentsTest) {
-    //When I create a scheduled event with an invalid options
+    // When I create a scheduled event with an invalid options
     auto ctx = fw->getFrameworkBundleContext();
-    celix_scheduled_event_options_t opts{}; //no callback
-    long scheduledEventId = 
celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts);
+    celix_scheduled_event_options_t opts{}; // no callback
+    long scheduledEventId = 
celix_bundleContext_scheduleEvent(ctx->getCBundleContext(), &opts);
 
-    //Then I expect an error
+    // Then I expect an error
     EXPECT_LT(scheduledEventId, 0);
 
-    //celix_scheduleEvent_release and celix_scheduledEvent_retain can be 
called with NULL
+    // celix_scheduleEvent_release and celix_scheduledEvent_retain can be 
called with NULL
     celix_scheduledEvent_release(nullptr);
     celix_scheduledEvent_retain(nullptr);
 
-    //celix_bundleContext_removeScheduledEvent can handle invalid eventIds
+    // celix_bundleContext_removeScheduledEvent can handle invalid eventIds
     celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), -1);
     celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), 404);
 
-    //celix_framework_addScheduledEvent with an invalid bndId should return -1
-    scheduledEventId = celix_framework_addScheduledEvent(
-        ctx->getFramework()->getCFramework(), 404, nullptr, 0.0, 0.0, nullptr, 
[](void*) { /*nop*/ });
+    // celix_framework_scheduledEvent with no callback should return -1
+    scheduledEventId = 
celix_framework_scheduleEvent(ctx->getFramework()->getCFramework(),
+                                                     CELIX_FRAMEWORK_BUNDLE_ID,
+                                                     nullptr,
+                                                     0.0,
+                                                     0.0,
+                                                     nullptr,
+                                                     nullptr,
+                                                     nullptr,
+                                                     nullptr);
+    EXPECT_EQ(scheduledEventId, -1);
+
+    // celix_framework_scheduledEvent with an invalid bndId should return -1
+    scheduledEventId = celix_framework_scheduleEvent(
+        ctx->getFramework()->getCFramework(), 404, nullptr, 0.0, 0.0, nullptr, 
[](void*) { /*nop*/ }, nullptr, nullptr);
     EXPECT_EQ(scheduledEventId, -1);
 }
 
@@ -198,20 +227,22 @@ TEST_F(ScheduledEventTestSuite, WakeUpEventTest) {
     // Given a counter scheduled event with a long initial delay is added
     std::atomic<int> count{0};
     celix_scheduled_event_options_t opts{};
-    opts.eventName = "test wakeup";
+    opts.name = "test wakeup";
     opts.initialDelayInSeconds = 0.1;
     opts.intervalInSeconds = 0.1;
-    opts.eventData = static_cast<void*>(&count);
-    opts.eventCallback = [](void* countPtr) {
+    opts.callbackData = static_cast<void*>(&count);
+    opts.callback = [](void* countPtr) {
         auto* count = static_cast<std::atomic<int>*>(countPtr);
         count->fetch_add(1);
     };
-    long scheduledEventId = 
celix_bundleContext_addScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 &opts);
+    long scheduledEventId =
+        
celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 &opts);
     ASSERT_NE(-1L, scheduledEventId);
     EXPECT_EQ(0, count.load());
 
     // When the scheduled event is woken up
-    celix_status_t status = 
celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 scheduledEventId, 1);
+    celix_status_t status = celix_bundleContext_wakeupScheduledEvent(
+        fw->getFrameworkBundleContext()->getCBundleContext(), 
scheduledEventId, 1);
 
     // Then the status is CELIX_SUCCESS
     ASSERT_EQ(CELIX_SUCCESS, status);
@@ -226,19 +257,21 @@ TEST_F(ScheduledEventTestSuite, WakeUpEventTest) {
     EXPECT_EQ(2, count.load());
 
     // When the scheduled event is woken up again without waiting 
(waitTimeInSec = 0)
-    status = 
celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 scheduledEventId, 0);
+    status = celix_bundleContext_wakeupScheduledEvent(
+        fw->getFrameworkBundleContext()->getCBundleContext(), 
scheduledEventId, 0);
 
     // And the process is delayed to ensure the event is called
-    std::this_thread::sleep_for(std::chrono::milliseconds{10}); 
+    std::this_thread::sleep_for(std::chrono::milliseconds{10});
 
     // Then the status is CELIX_SUCCESS
     ASSERT_EQ(CELIX_SUCCESS, status);
-    
+
     // And the count is increased
     EXPECT_EQ(3, count.load());
 
     // When the scheduled event is woken up again
-    status = 
celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 scheduledEventId, 1);
+    status = celix_bundleContext_wakeupScheduledEvent(
+        fw->getFrameworkBundleContext()->getCBundleContext(), 
scheduledEventId, 1);
 
     // Then the status is CELIX_SUCCESS
     ASSERT_EQ(CELIX_SUCCESS, status);
@@ -253,19 +286,21 @@ TEST_F(ScheduledEventTestSuite, WakeUpOneShotEventTest) {
     // Given a counter scheduled event with a long initial delay is added
     std::atomic<int> count{0};
     celix_scheduled_event_options_t opts{};
-    opts.eventName = "test one-shot wakeup";
+    opts.name = "test one-shot wakeup";
     opts.initialDelayInSeconds = 5;
-    opts.eventData = static_cast<void*>(&count);
-    opts.eventCallback = [](void* countPtr) {
+    opts.callbackData = static_cast<void*>(&count);
+    opts.callback = [](void* countPtr) {
         auto* count = static_cast<std::atomic<int>*>(countPtr);
         count->fetch_add(1);
     };
-    long scheduledEventId = 
celix_bundleContext_addScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 &opts);
-    ASSERT_NE(-1L, scheduledEventId);
+    long scheduledEventId =
+        
celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 &opts);
+    ASSERT_GE(scheduledEventId, 0);
     EXPECT_EQ(0, count.load());
 
     // When the scheduled event is woken up
-    celix_status_t status = 
celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 scheduledEventId, 1);
+    celix_status_t status = celix_bundleContext_wakeupScheduledEvent(
+        fw->getFrameworkBundleContext()->getCBundleContext(), 
scheduledEventId, 1);
 
     // Then the status is CELIX_SUCCESS
     ASSERT_EQ(CELIX_SUCCESS, status);
@@ -274,8 +309,201 @@ TEST_F(ScheduledEventTestSuite, WakeUpOneShotEventTest) {
     EXPECT_EQ(1, count.load());
 
     // And when the scheduled event is woken up again
-    status = 
celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 scheduledEventId, 0);
+    status = celix_bundleContext_wakeupScheduledEvent(
+        fw->getFrameworkBundleContext()->getCBundleContext(), 
scheduledEventId, 0);
 
     // Then the status is ILLEGAL_ARGUMENT, becuase the scheduled event is 
already woken up and a one-shot event
     ASSERT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
 }
+
+TEST_F(ScheduledEventTestSuite, CxxScheduledEventTest) {
+    // Given a count and a callback to increase the count
+    std::atomic<int> count{0};
+    auto callback = [&count]() { count.fetch_add(1); };
+
+    std::atomic<bool> removed{false};
+    auto removeCallback = [&removed]() { removed.store(true); };
+
+    // And a scheduled event with a initial delay and interval of 50ms
+    auto event = fw->getFrameworkBundleContext()
+                     ->scheduledEvent()
+                     .withName("test cxx")
+                     .withInitialDelay(std::chrono::milliseconds{50})
+                     .withInterval(std::chrono::milliseconds{50})
+                     .withCallback(callback)
+                     .withRemoveCallback(removeCallback)
+                     .build();
+
+    // Then the count is not yet increased
+    ASSERT_EQ(0, count.load());
+
+    // When waiting longer than the initial delay
+    std::this_thread::sleep_for(std::chrono::milliseconds{60});
+
+    // Then the count is increased
+    EXPECT_EQ(1, count.load());
+
+    // When waking up the event with a wait time of 1s
+    event.wakeup(std::chrono::seconds{1});
+
+    // Then the count is increased
+    EXPECT_EQ(2, count.load());
+
+    // When waking up the event without waiting
+    event.wakeup();
+
+    // And the process is delayed to ensure the event is called
+    std::this_thread::sleep_for(std::chrono::milliseconds{10});
+
+    // Then the count is increased
+    EXPECT_EQ(3, count.load());
+
+    // And the remove callback is not yet called
+    EXPECT_FALSE(removed.load());
+
+    // When canceling the event
+    event.cancel();
+
+    // And waiting longer than the interval
+    std::this_thread::sleep_for(std::chrono::milliseconds{60});
+
+    // Then the count is not increased
+    EXPECT_EQ(3, count.load());
+
+    // And the remove callback is called
+    EXPECT_TRUE(removed.load());
+}
+
+TEST_F(ScheduledEventTestSuite, CxxScheduledEventRAIITest) {
+    // Given a count and a callback to increase the count
+    std::atomic<int> count{0};
+    auto callback = [&count]() { count.fetch_add(1); };
+
+    std::atomic<bool> removed{false};
+    auto removeCallback = [&removed]() { removed.store(true); };
+
+    {
+        // And a scoped scheduled event with a initial delay and interval of 
100ms
+        auto event = fw->getFrameworkBundleContext()
+                         ->scheduledEvent()
+                         .withName("test cxx")
+                         .withInitialDelay(std::chrono::milliseconds{50})
+                         .withInterval(std::chrono::milliseconds{50})
+                         .withCallback(callback)
+                         .withRemoveCallback(removeCallback)
+                         .build();
+
+        // Then the count is not yet increased
+        ASSERT_EQ(0, count.load());
+    }
+    // When the event goes out of scope
+
+    // When the remove callback is called
+    EXPECT_TRUE(removed.load());
+
+    // When waiting longer than the initial delay
+    std::this_thread::sleep_for(std::chrono::milliseconds{60});
+
+    // Then the count is not increased
+    EXPECT_EQ(0, count.load());
+}
+
+TEST_F(ScheduledEventTestSuite, CxxOneShotScheduledEventTest) {
+    // Given a count and a callback to increase the count
+    std::atomic<int> count{0};
+    auto callback = [&count]() { count.fetch_add(1); };
+
+    std::atomic<bool> removed{false};
+    auto removeCallback = [&removed]() { removed.store(true); };
+
+    // And a scheduled event with a initial delay of 50ms
+    auto event = fw->getFrameworkBundleContext()
+                     ->scheduledEvent()
+                     .withName("test cxx one-shot")
+                     .withInitialDelay(std::chrono::milliseconds{50})
+                     .withCallback(callback)
+                     .withRemoveCallback(removeCallback)
+                     .build();
+
+    // Then the count is not yet increased
+    ASSERT_EQ(0, count.load());
+
+    // And the remove callback is not yet called
+    EXPECT_FALSE(removed.load());
+
+    // When waiting longer than the initial delay
+    std::this_thread::sleep_for(std::chrono::milliseconds{60});
+
+    // Then the count is increased
+    EXPECT_EQ(1, count.load());
+
+    // And the remove callback is called
+    EXPECT_TRUE(removed.load());
+
+    // When waking up the event with a wait time of 1s
+    event.wakeup(std::chrono::seconds{1});
+
+    // Then the count is not increased, because the event is a one-shot event
+    EXPECT_EQ(1, count.load());
+}
+
+TEST_F(ScheduledEventTestSuite, CxxOneShotScheduledEventRAIITest) {
+    // Given a count and a callback to increase the count
+    std::atomic<int> count{0};
+    auto callback = [&count]() { count.fetch_add(1); };
+
+    std::atomic<bool> removed{false};
+    auto removeCallback = [&removed]() { removed.store(true); };
+
+    {
+        // And a scoped scheduled event with a initial delay of 50ms
+        auto event = fw->getFrameworkBundleContext()
+                         ->scheduledEvent()
+                         .withName("test cxx one-shot")
+                         .withInitialDelay(std::chrono::milliseconds{50})
+                         .withCallback(callback)
+                         .withRemoveCallback(removeCallback)
+                         .build();
+
+        // Then the count is not yet increased
+        ASSERT_EQ(0, count.load());
+    }
+    // When the event is out of scope
+
+    // Then the remove callback is not yet called
+    EXPECT_FALSE(removed.load());
+
+    // And waiting longer than the initial delay
+    std::this_thread::sleep_for(std::chrono::milliseconds{60});
+
+    // Then the count is increased, because one-shot events are not canceled 
when out of scope
+    EXPECT_EQ(1, count.load());
+
+    // And the remove callback is called
+    EXPECT_TRUE(removed.load());
+}
+
+TEST_F(ScheduledEventTestSuite, CxxCreateScheduledEventWithNoCallbackTest) {
+    
EXPECT_ANY_THROW(fw->getFrameworkBundleContext()->scheduledEvent().build()); // 
Note no callback
+}
+
+TEST_F(ScheduledEventTestSuite, TimeoutOnWakeupTest) {
+    auto callback = [](void*) {
+        // delay process to check if wakeup timeout works
+        std::this_thread::sleep_for(std::chrono::milliseconds{10});
+    };
+
+    celix_scheduled_event_options_t opts{};
+    opts.callback = callback;
+    opts.initialDelayInSeconds = 1;
+    long eventId = 
celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 &opts);
+    EXPECT_GE(eventId, 0);
+
+    auto status =
+        
celix_bundleContext_wakeupScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 eventId, 0.001);
+    EXPECT_EQ(CELIX_TIMEOUT, status);
+
+    //sleep to ensure the event is processed
+    //TODO fixme, if removed the tests leaks
+    std::this_thread::sleep_for(std::chrono::milliseconds{10});
+}
\ No newline at end of file
diff --git 
a/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc 
b/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc
index cf56bd3f..ea3e96ac 100644
--- a/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc
+++ b/libs/framework/gtest/src/ScheduledEventWithErrorInjectionTestSuite.cc
@@ -46,9 +46,10 @@ TEST_F(ScheduledEventWithErrorInjectionTestSuite, 
MallocFailsTest) {
 
     //When a scheduled event is added
     celix_scheduled_event_options_t opts{};
-    opts.eventName = "malloc fail test";
-    opts.eventCallback = [](void*){/*nop*/};
-    long scheduledEventId = 
celix_bundleContext_addScheduledEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
 &opts);
+    opts.name = "malloc fail test";
+    opts.callback = [](void*){/*nop*/};
+    long scheduledEventId = 
celix_bundleContext_scheduleEvent(fw->getFrameworkBundleContext()->getCBundleContext(),
+                                                              &opts);
 
     //Then the scheduled event id is -1 (error)
     EXPECT_EQ(-1L, scheduledEventId);
diff --git a/libs/framework/include/celix/BundleContext.h 
b/libs/framework/include/celix/BundleContext.h
index eaf2ff32..59134a8f 100644
--- a/libs/framework/include/celix/BundleContext.h
+++ b/libs/framework/include/celix/BundleContext.h
@@ -29,6 +29,7 @@
 #include "celix/ServiceRegistrationBuilder.h"
 #include "celix/UseServiceBuilder.h"
 #include "celix/TrackerBuilders.h"
+#include "celix/ScheduledEventBuilder.h"
 #include "celix/Bundle.h"
 #include "celix/Framework.h"
 
@@ -394,6 +395,14 @@ namespace celix {
             return MetaTrackerBuilder(cCtx, {});
         }
 
+        /**
+         * @brief Schedule a callback to be called after the given initial 
delay and/or interval using a fluent 
+         * builder API.
+         */
+        ScheduledEventBuilder scheduledEvent() {
+            return ScheduledEventBuilder{cCtx};
+        }
+
         /**
          * @brief Install and optional start a bundle.
          *
diff --git a/libs/framework/include/celix/Framework.h 
b/libs/framework/include/celix/Framework.h
index e557e55f..e0e62eeb 100644
--- a/libs/framework/include/celix/Framework.h
+++ b/libs/framework/include/celix/Framework.h
@@ -61,7 +61,7 @@ namespace celix {
          * The event will be added to the event loop and handled on the event 
loop thread.
          *
          * if bndId >=0 the bundle usage count will be increased while the 
event is not yet processed or finished processing.
-         * The eventName is expected to be const char* valid during til the 
event is finished processing.
+         * The name is expected to be const char* valid during til the event 
is finished processing.
          *
          * if eventId >=0 this will be used, otherwise a new event id will be 
generated.
          *
diff --git a/libs/framework/include/celix/ScheduledEvent.h 
b/libs/framework/include/celix/ScheduledEvent.h
index c1aa1df0..6358f0ca 100644
--- a/libs/framework/include/celix/ScheduledEvent.h
+++ b/libs/framework/include/celix/ScheduledEvent.h
@@ -18,3 +18,146 @@
  */
 
 #pragma once
+
+#include "celix_bundle_context.h"
+
+namespace celix {
+
+/**
+ * @brief A C++ abstraction for a scheduled event in Celix.
+ *
+ * A scheduled event is an event that is scheduled to be executed at a certain 
initial delay and/or interval.
+ * A new scheduld event should be created using 
celix::BundleContext::createScheduledEvent.
+ *
+ * This class uses RAII to automatically remove the (non one-shot) scheduled 
event from the bundle context
+ * when it is destroyed. For one-shot scheduled events, the destructor will 
not remove the scheduled event.
+ */
+class ScheduledEvent final {
+  public:
+    friend class ScheduledEventBuilder;
+
+    /**
+     * @brief Constructs a empty / not-active scheduled event.
+     */
+    ScheduledEvent() = default;
+
+    /**
+     * @brief Destroys the scheduled event by removes it from the bundle 
context if it is not a one-short event.
+     */
+    ~ScheduledEvent() noexcept {
+        if (!isOneShot) {
+            cancel();
+        }
+    }
+
+    ScheduledEvent(const ScheduledEvent&) = delete;
+    ScheduledEvent& operator=(const ScheduledEvent&) = delete;
+
+    ScheduledEvent(ScheduledEvent&&) noexcept = default;
+    ScheduledEvent& operator=(ScheduledEvent&&) noexcept = default;
+
+    /**
+     * @brief Cancels the scheduled event. Can be called multiple times. When 
this function returns, no more scheduled
+     * event callbacks will be called and, if configured, the remove callback 
is called.
+     *
+     */
+    void cancel() {
+        if (ctx) {
+            celix_bundleContext_tryRemoveScheduledEvent(ctx.get(), eventId);
+        }
+    }
+
+    /**
+     * @brief Wakes up the scheduled event and returns immediately, not 
waiting for the scheduled event callback to be
+     * called.
+     */
+    void wakeup() { wakeup(std::chrono::duration<double>{0}); }
+
+    /**
+     * @brief Wakes up the scheduled event with an optional wait time.
+     *
+     * If `waitTime` is not zero, this function will block until the scheduled 
event callback is called or the
+     * `waitTime` duration has elapsed. If `waitTime` is zero, this function 
will return immediately.
+     *
+     * @tparam Rep The representation type of the duration.
+     * @tparam Period The period type of the duration.
+     * @param[in] waitTime The wait time duration (default is zero).
+     * @return true if the scheduled event was woken up, false if a timeout 
occurred.
+     */
+    template <typename Rep, typename Period>
+    bool wakeup(std::chrono::duration<Rep, Period> waitTime) {
+        double waitTimeInSeconds = 
std::chrono::duration_cast<std::chrono::duration<double>>(waitTime).count();
+        celix_status_t status = CELIX_SUCCESS;
+        if (ctx) {
+            status = celix_bundleContext_wakeupScheduledEvent(ctx.get(), 
eventId, waitTimeInSeconds);
+        }
+        return status == CELIX_SUCCESS;
+    }
+
+  private:
+    /**
+     * @brief Constructs a scheduled event using the given bundle context and 
options.
+     *
+     * @param[in] ctx The bundle context to use.
+     * @param[in] options The options for the scheduled event.
+     */
+    ScheduledEvent(std::shared_ptr<celix_bundle_context_t> _cCtx,
+                   const std::string& _name,
+                   std::function<void()> _callback,
+                   std::function<void()> _removeCallback,
+                   celix_scheduled_event_options_t& options) {
+        ctx = std::move(_cCtx);
+        options.name = _name.c_str();
+        configureCallbacks(options, std::move(_callback), 
std::move(_removeCallback));
+        eventId = celix_bundleContext_scheduleEvent(ctx.get(), &options);
+    }
+
+    /**
+     * @brief Configure the callbacks for the scheduled event, ensuring the 
callbacks outlive the scheduled event.
+     */
+    void configureCallbacks(celix_scheduled_event_options_t& options,
+                            std::function<void()> _callback,
+                            std::function<void()> _removeCallback) {
+        isOneShot = options.intervalInSeconds == 0;
+        if (isOneShot) {
+            options.callbackData = new std::function<void()>{
+                std::move(_callback)}; // to ensure callback outlives the 
scheduled event object
+            options.callback = [](void* data) {
+                auto* cb = static_cast<std::function<void()>*>(data);
+                (*cb)();
+                delete cb;
+            };
+            if (_removeCallback) {
+                options.removeCallbackData = new 
std::function<void()>{std::move(_removeCallback)};
+                options.removeCallback = [](void* data) {
+                    auto* cb = static_cast<std::function<void()>*>(data);
+                    (*cb)();
+                    delete cb;
+                };
+            }
+        } else {
+            callback = std::move(_callback);
+            removeCallback = std::move(_removeCallback);
+            options.callbackData = &callback;
+            options.callback = [](void* data) {
+                auto* cb = static_cast<std::function<void()>*>(data);
+                (*cb)();
+            };
+            if (removeCallback) {
+                options.removeCallbackData = &removeCallback;
+                options.removeCallback = [](void* data) {
+                    auto* cb = static_cast<std::function<void()>*>(data);
+                    (*cb)();
+                };
+            }
+        }
+    }
+
+    std::shared_ptr<celix_bundle_context_t> ctx{}; /**< The bundle context for 
the scheduled event. */
+    std::function<void()> callback{};              /**< The callback for the 
scheduled event. */
+    std::function<void()> removeCallback{};        /**< The remove callback 
for the scheduled event. */
+    long eventId{-1};                              /**< The ID of the 
scheduled event. */
+    bool isOneShot{false};                         /**< Whether the scheduled 
event is a one-shot event. */
+};
+
+} // end namespace celix
diff --git a/libs/framework/include/celix/ScheduledEventBuilder.h 
b/libs/framework/include/celix/ScheduledEventBuilder.h
new file mode 100644
index 00000000..c93a93bd
--- /dev/null
+++ b/libs/framework/include/celix/ScheduledEventBuilder.h
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <functional>
+
+#include "celix/ScheduledEvent.h"
+#include "celix/Exception.h"
+
+namespace celix {
+
+/**
+ * @brief A C++ builder for a ScheduledEvent object.
+ */
+class ScheduledEventBuilder final {
+public:
+    /**
+     * @brief Construct a scheduled event builder with the given bundle 
context.
+     *
+     * @param[in] ctx The bundle context to use.
+     */
+    ScheduledEventBuilder(std::shared_ptr<celix_bundle_context_t> _cCtx) : 
ctx{std::move(_cCtx)}, options{} {}
+
+    /**
+     * @brief Set the initial delay of the scheduled event.
+     *
+     * @tparam Rep The representation type of the duration.
+     * @tparam Period The period type of the duration.
+     * @param[in] delay The delay duration.
+     * @return A reference to this builder.
+     */
+    template<typename Rep, typename Period>
+    ScheduledEventBuilder& withInitialDelay(std::chrono::duration<Rep, Period> 
delay) {
+        options.initialDelayInSeconds = 
std::chrono::duration_cast<std::chrono::duration<double>>(delay).count();
+        return *this;
+    }
+
+    /**
+     * @brief Set the interval of the scheduled event.
+     *
+     * @tparam Rep The representation type of the duration.
+     * @tparam Period The period type of the duration.
+     * @param[in] interval The interval duration.
+     * @return A reference to this builder.
+     */
+    template<typename Rep, typename Period>
+    ScheduledEventBuilder& withInterval(std::chrono::duration<Rep, Period> 
interval) {
+        options.intervalInSeconds = 
std::chrono::duration_cast<std::chrono::duration<double>>(interval).count();
+        return *this;
+    }
+
+    /**
+     * @brief Set the name of the scheduled event.
+     *
+     * @param[in] name The name of the scheduled event.
+     * @return A reference to this builder.
+     */
+    ScheduledEventBuilder& withName(std::string n) {
+        name = std::move(n);
+        return *this;
+    }
+
+    /**
+     * @brief Set the callback of the scheduled event.
+     *
+     * The callback is called when the scheduled event is triggered on the 
event thread.
+     *
+     * @param[in] cb The callback function.
+     * @return A reference to this builder.
+     */
+    ScheduledEventBuilder& withCallback(std::function<void()> cb) {
+        callback = std::move(cb);
+        return *this;
+    }
+
+    /**
+     * @brief Set the remove callback of the scheduled event.
+     *
+     * The remove callback is called when the scheduled event is removed from 
the scheduler and can be called
+     * from any thread.
+     *
+     * @param[in] cb The callback function.
+     * @return A reference to this builder.
+     */
+    ScheduledEventBuilder& withRemoveCallback(std::function<void()> cb) {
+        removeCallback = std::move(cb);
+        return *this;
+    }
+
+    /**
+     * @brief Build the scheduled event with the given options.
+     *
+     * @return The scheduled event.
+     */
+    ScheduledEvent build() {
+        if (!callback) {
+            throw celix::Exception{"Cannot build scheduled event without 
callback"}; //TODO improve error
+        }
+        return ScheduledEvent{ctx, name, std::move(callback), 
std::move(removeCallback), options};
+    }
+
+private:
+    std::shared_ptr<celix_bundle_context_t> ctx; /**< The bundle context for 
the scheduled event. */
+    celix_scheduled_event_options_t options; /**< The options for the 
scheduled event. */
+    std::string name{}; /**< The name of the scheduled event. */
+    std::function<void()> callback{}; /**< The callback function for the 
scheduled event. */
+    std::function<void()> removeCallback{}; /**< The callback function for the 
scheduled event. */
+};
+
+} // end namespace celix
diff --git a/libs/framework/include/celix/dm/Component.h 
b/libs/framework/include/celix/dm/Component.h
index a1a429dd..85f42b8e 100644
--- a/libs/framework/include/celix/dm/Component.h
+++ b/libs/framework/include/celix/dm/Component.h
@@ -124,7 +124,9 @@ namespace celix { namespace dm {
 
         /**
          * Wait for an empty Celix event queue.
+         *
          * Should not be called on the Celix event queue thread.
+         * Note scheduled events are not part of the event queue.
          *
          * Can be used to ensure that all created/updated components are 
completely processed (services registered
          * and/or service trackers are created).
diff --git a/libs/framework/include/celix/dm/DependencyManager.h 
b/libs/framework/include/celix/dm/DependencyManager.h
index a36c7e7f..02025812 100644
--- a/libs/framework/include/celix/dm/DependencyManager.h
+++ b/libs/framework/include/celix/dm/DependencyManager.h
@@ -131,6 +131,7 @@ namespace celix { namespace dm {
          * @brief Wait for an empty Celix event queue.
          *
          * Should not be called on the Celix event queue thread.
+         * Note scheduled events are not part of the event queue.
          *
          * Can be used to ensure that all created/updated components are 
completely processed (services registered
          * and/or service trackers are created).
@@ -140,6 +141,8 @@ namespace celix { namespace dm {
         /**
          * @brief Wait (if not called on the Celix event thread) for an empty 
Celix event queue
          *
+         * Note scheduled events are not part of the event queue.
+         * 
          * Can be used to ensure that all created/updated components are 
completely processed (services registered
          * and/or service trackers are created).
          */
diff --git a/libs/framework/include/celix_bundle_context.h 
b/libs/framework/include/celix_bundle_context.h
index cbd644d6..d9296ff4 100644
--- a/libs/framework/include/celix_bundle_context.h
+++ b/libs/framework/include/celix_bundle_context.h
@@ -1259,11 +1259,11 @@ CELIX_FRAMEWORK_EXPORT void 
celix_bundleContext_waitForEvents(celix_bundle_conte
  * @brief Celix scheduled event options, used for creating scheduling events 
with the celix framework.
  */
 typedef struct celix_scheduled_event_options {
-    const char* eventName CELIX_OPTS_INIT; /**<
-                                            * @brief The name of the event, 
used for logging and debugging.
-                                            *
-                                            * Expected to be const char* that 
is valid during the addScheduledEvent
-                                            * call. Can be NULL. */
+    const char* name CELIX_OPTS_INIT; /**<
+                                       * @brief The name of the event, used 
for logging and debugging.
+                                       *
+                                       * Expected to be const char* that is 
valid during the addScheduledEvent
+                                       * call. Can be NULL. */
 
     double initialDelayInSeconds CELIX_OPTS_INIT; /**< @brief Initial delay in 
seconds before the event is processed.*/
 
@@ -1271,11 +1271,21 @@ typedef struct celix_scheduled_event_options {
                                                *  0 means one-shot scheduled 
event.
                                                */
 
-    void* eventData CELIX_OPTS_INIT; /**< @brief Data passed to the 
eventCallback function when a event is scheduled.*/
+    void* callbackData CELIX_OPTS_INIT; /**< @brief Data passed to the 
callback function when a event is scheduled.*/
 
-    void (*eventCallback)(void* eventData) CELIX_OPTS_INIT; /**< @brief 
Callback function called to process a scheduled event.*/
+    void (*callback)(void* callbackData) CELIX_OPTS_INIT; /**< @brief Callback 
function called to process a scheduled
+                                                             event. Will be 
called on the event thread.*/
+
+    void* removeCallbackData
+        CELIX_OPTS_INIT; /**< @brief Data passed to the done callback function 
when a scheduled event is removed.*/
+
+    void (*removeCallback)(void* removeCallbackData)
+        CELIX_OPTS_INIT; /**< @brief Callback function called when a scheduled 
event is removed. Can called from the
+                            event thread or another thread.*/
 } celix_scheduled_event_options_t;
 
+#define CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS {NULL, 0.0, 0.0, NULL, NULL, NULL, 
NULL}
+
 /**
  * @brief Add a scheduled event to the Celix framework.
  *
@@ -1303,8 +1313,8 @@ typedef struct celix_scheduled_event_options {
  * @return The scheduled event id of the scheduled event. Can be used to 
cancel the event.
  * @retval <0 If the event could not be added.
  */
-CELIX_FRAMEWORK_EXPORT long 
celix_bundleContext_addScheduledEvent(celix_bundle_context_t* ctx,
-                                                                  const 
celix_scheduled_event_options_t* options);
+CELIX_FRAMEWORK_EXPORT long 
celix_bundleContext_scheduleEvent(celix_bundle_context_t* ctx,
+                                                              const 
celix_scheduled_event_options_t* options);
 
 /**
  * @brief Wakeup a scheduled event.
@@ -1327,7 +1337,8 @@ CELIX_FRAMEWORK_EXPORT celix_status_t 
celix_bundleContext_wakeupScheduledEvent(
 /**
  * @brief Cancel and remove a scheduled event.
  *
- * When this function returns, no more scheduled event callbacks will be 
called.
+ * When this function returns, no more scheduled event callbacks will be 
called amd, if configured, the remove callback
+ * is called.
  *
  * @param[in] ctx The bundle context.
  * @param[in] scheduledEventId The scheduled event id to cancel.
@@ -1336,6 +1347,20 @@ CELIX_FRAMEWORK_EXPORT celix_status_t 
celix_bundleContext_wakeupScheduledEvent(
 CELIX_FRAMEWORK_EXPORT bool 
celix_bundleContext_removeScheduledEvent(celix_bundle_context_t* ctx,
                                                                       long 
scheduledEventId);
 
+/**
+ * @brief Try to cancel and remove a scheduled event.
+ *
+ * When this function returns, no more scheduled event callbacks will be 
called amd, if configured, the remove callback
+ * is called.
+ * Will not log an error if the scheduled event id is not known.
+ *
+ * @param[in] ctx The bundle context.
+ * @param[in] scheduledEventId The scheduled event id to cancel.
+ * @return true if a scheduled event is cancelled, false if the scheduled 
event id is not known.
+ */
+CELIX_FRAMEWORK_EXPORT bool 
celix_bundleContext_tryRemoveScheduledEvent(celix_bundle_context_t* ctx,
+                                                                        long 
scheduledEventId);
+
 /**
  * @brief Returns the bundle for this bundle context.
  */
diff --git a/libs/framework/include/celix_dependency_manager.h 
b/libs/framework/include/celix_dependency_manager.h
index b8c9c330..a8b4b755 100644
--- a/libs/framework/include/celix_dependency_manager.h
+++ b/libs/framework/include/celix_dependency_manager.h
@@ -121,7 +121,9 @@ CELIX_FRAMEWORK_EXPORT size_t 
celix_dependencyManager_nrOfComponents(celix_depen
 
 /**
  * Wait for an empty Celix event queue.
+ * 
  * Should not be called on the Celix event queue thread.
+ * Note scheduled events are not part of the event queue.
  *
  * Can be used to ensure that all created/updated components are completely 
processed (services registered
  * and/or service trackers are created).
diff --git a/libs/framework/include/celix_framework.h 
b/libs/framework/include/celix_framework.h
index 9c7c2e18..cffc5afb 100644
--- a/libs/framework/include/celix_framework.h
+++ b/libs/framework/include/celix_framework.h
@@ -274,6 +274,8 @@ CELIX_FRAMEWORK_EXPORT void 
celix_framework_setLogCallback(celix_framework_t* fw
  *
  * The Celix framework has an event queue which (among others) handles bundle 
events.
  * This function can be used to ensure that all queue event are handled.
+ * 
+ * Note scheduled events are not part of the event queue.
  *
  * @param fw The Celix Framework
  */
@@ -284,6 +286,8 @@ CELIX_FRAMEWORK_EXPORT void 
celix_framework_waitForEmptyEventQueue(celix_framewo
  *
  * The Celix framework has an event queue which (among others) handles bundle 
events.
  * This function can be used to ensure that all queue event are handled.
+ * 
+ * Note scheduled events are not part of the event queue.
  *
  * @param[in] fw The Celix Framework.
  * @param[in] timeoutInSeconds The period in seconds to wait for the event 
queue to be empty. 0 means wait forever.
@@ -292,21 +296,26 @@ CELIX_FRAMEWORK_EXPORT void 
celix_framework_waitForEmptyEventQueue(celix_framewo
 CELIX_FRAMEWORK_EXPORT celix_status_t 
celix_framework_timedWaitForEmptyEventQueue(celix_framework_t *fw, double 
timeoutInSeconds);
 
 /**
- * @brief wait until all events for the bundle identified by the bndId are 
processed.
+ * @brief wait until all events from the event queue for the bundle identified 
by the bndId are processed.
+ * 
+ * Note scheduled events are not part of the event queue.
+ * 
  */
 CELIX_FRAMEWORK_EXPORT void 
celix_framework_waitUntilNoEventsForBnd(celix_framework_t* fw, long bndId);
 
 /**
- * @brief wait until all pending service registration  are processed.
+ * @brief wait until all pending service registration are processed.
  */
 CELIX_FRAMEWORK_EXPORT void 
celix_framework_waitUntilNoPendingRegistration(celix_framework_t* fw);
 
 /**
  * @brief Returns whether the current thread is the Celix framework event loop 
thread.
+ * 
+ * Note scheduled events are not part of the event queue.
+ * 
  */
 CELIX_FRAMEWORK_EXPORT bool 
celix_framework_isCurrentThreadTheEventLoop(celix_framework_t* fw);
 
-
 /**
  * @brief Fire a generic event. The event will be added to the event loop and 
handled on the event loop thread.
  *
@@ -314,12 +323,19 @@ CELIX_FRAMEWORK_EXPORT bool 
celix_framework_isCurrentThreadTheEventLoop(celix_fr
  * the framework event queue will be blocked and framework will not function 
properly.
  *
  * if bndId >=0 the bundle usage count will be increased while the event is 
not yet processed or finished processing.
- * The eventName is expected to be const char* valid during til the event is 
finished processing.
+ * The name is expected to be const char* valid during til the event is 
finished processing.
  *
  * if eventId >=0 this will be used, otherwise a new event id will be generated
  * return eventId
  */
-CELIX_FRAMEWORK_EXPORT long 
celix_framework_fireGenericEvent(celix_framework_t* fw, long eventId, long 
bndId, const char *eventName, void* processData, void (*processCallback)(void 
*data), void* doneData, void (*doneCallback)(void* doneData));
+CELIX_FRAMEWORK_EXPORT long 
celix_framework_fireGenericEvent(celix_framework_t* fw,
+                                                             long eventId,
+                                                             long bndId,
+                                                             const char* 
eventName,
+                                                             void* processData,
+                                                             void 
(*processCallback)(void* data),
+                                                             void* doneData,
+                                                             void 
(*doneCallback)(void* doneData));
 
 /**
  * @brief Get the next event id.
@@ -331,8 +347,10 @@ CELIX_FRAMEWORK_EXPORT long 
celix_framework_fireGenericEvent(celix_framework_t*
 CELIX_FRAMEWORK_EXPORT long celix_framework_nextEventId(celix_framework_t *fw);
 
 /**
- * @brief Wait until a event with the provided event id is completely handled.
+ * @brief Wait until a event from the event queue with the provided event id 
is completely handled.
  * This function will directly return if the provided event id is not in the 
event loop (already done or never issued).
+ * 
+ * Note scheduled events are not part of the event queue.
  */
 CELIX_FRAMEWORK_EXPORT void 
celix_framework_waitForGenericEvent(celix_framework_t *fw, long eventId);
 
diff --git a/libs/framework/src/bundle_context.c 
b/libs/framework/src/bundle_context.c
index 01c44143..d58e3794 100644
--- a/libs/framework/src/bundle_context.c
+++ b/libs/framework/src/bundle_context.c
@@ -111,14 +111,20 @@ celix_status_t bundleContext_destroy(bundle_context_pt 
context) {
        return status;
 }
 
-void celix_bundleContext_cleanup(celix_bundle_context_t *ctx) {
-    celix_framework_cleanupScheduledEvents(ctx->framework, 
celix_bundle_getId(ctx->bundle));
-    //NOTE not perfect, because stopping of registrations/tracker when the 
activator is destroyed can lead to segfault.
-    //but at least we can try to warn the bundle implementer that some cleanup 
is missing.
-    bundleContext_cleanupBundleTrackers(ctx);
-    bundleContext_cleanupServiceTrackers(ctx);
-    bundleContext_cleanupServiceTrackerTrackers(ctx);
-    bundleContext_cleanupServiceRegistration(ctx);
+void celix_bundleContext_cleanup(celix_bundle_context_t* ctx) {
+        fw_log(ctx->framework->logger,
+               CELIX_LOG_LEVEL_TRACE,
+               "Cleaning up bundle context `%s` (id=%li)",
+               celix_bundle_getSymbolicName(ctx->bundle),
+               celix_bundle_getId(ctx->bundle));
+
+        celix_framework_cleanupScheduledEvents(ctx->framework, 
celix_bundle_getId(ctx->bundle));
+        // NOTE not perfect, because stopping of registrations/tracker when 
the activator is destroyed can lead to
+        // segfault. but at least we can try to warn the bundle implementer 
that some cleanup is missing.
+        bundleContext_cleanupBundleTrackers(ctx);
+        bundleContext_cleanupServiceTrackers(ctx);
+        bundleContext_cleanupServiceTrackerTrackers(ctx);
+        bundleContext_cleanupServiceRegistration(ctx);
 }
 
 celix_status_t bundleContext_getBundle(bundle_context_pt context, bundle_pt 
*out) {
@@ -1483,15 +1489,17 @@ void 
celix_bundleContext_waitForEvents(celix_bundle_context_t* ctx) {
     celix_framework_waitUntilNoEventsForBnd(ctx->framework, 
celix_bundle_getId(ctx->bundle));
 }
 
-long celix_bundleContext_addScheduledEvent(celix_bundle_context_t* ctx,
-                                           const 
celix_scheduled_event_options_t* options) {
-    return celix_framework_addScheduledEvent(ctx->framework,
-                                             celix_bundle_getId(ctx->bundle),
-                                             options->eventName,
-                                             options->initialDelayInSeconds,
-                                             options->intervalInSeconds,
-                                             options->eventData,
-                                             options->eventCallback);
+long celix_bundleContext_scheduleEvent(celix_bundle_context_t* ctx,
+                                       const celix_scheduled_event_options_t* 
options) {
+    return celix_framework_scheduleEvent(ctx->framework,
+                                          celix_bundle_getId(ctx->bundle),
+                                          options->name,
+                                          options->initialDelayInSeconds,
+                                          options->intervalInSeconds,
+                                          options->callbackData,
+                                          options->callback,
+                                          options->removeCallbackData,
+                                          options->removeCallback);
 }
 
 celix_status_t celix_bundleContext_wakeupScheduledEvent(
@@ -1502,7 +1510,12 @@ celix_status_t celix_bundleContext_wakeupScheduledEvent(
 }
 
 bool celix_bundleContext_removeScheduledEvent(celix_bundle_context_t* ctx, 
long scheduledEventId) {
-    return celix_framework_removeScheduledEvent(ctx->framework, 
scheduledEventId);
+    return celix_framework_removeScheduledEvent(ctx->framework, false, 
scheduledEventId);
+}
+
+CELIX_FRAMEWORK_EXPORT bool 
celix_bundleContext_tryRemoveScheduledEvent(celix_bundle_context_t* ctx,
+                                                                        long 
scheduledEventId) {
+    return celix_framework_removeScheduledEvent(ctx->framework, true, 
scheduledEventId);
 }
 
 celix_bundle_t* celix_bundleContext_getBundle(const celix_bundle_context_t 
*ctx) {
diff --git a/libs/framework/src/celix_scheduled_event.c 
b/libs/framework/src/celix_scheduled_event.c
index 6ef8533a..9bad56da 100644
--- a/libs/framework/src/celix_scheduled_event.c
+++ b/libs/framework/src/celix_scheduled_event.c
@@ -37,29 +37,29 @@ static const char* const CELIX_SCHEDULED_EVENT_DEFAULT_NAME 
= "unnamed";
  * @brief Struct representing a scheduled event.
  *
  * A scheduled event is an event that is scheduled to be executed at a certain 
ititial delay and/or interval.
- * It is created using the `celix_bundleContext_addScheduledEvent` function 
and can be woken up
+ * It is created using the `celix_bundleContext_scheduleEvent` function and 
can be woken up
  * using the `celix_bundleContext_wakeupScheduledEvent` function.
  *
  * The struct contains information about the scheduled event, such as the 
event name, initial delay,
  * interval, and callback function. It also contains synchronization 
primitives to protect the use
  * count and call count of the event.
  *
- * @see celix_bundleContext_addScheduledEvent
+ * @see celix_bundleContext_scheduleEvent
  * @see celix_bundleContext_wakeupScheduledEvent
  */
 struct celix_scheduled_event {
     long scheduledEventId;            /**< The ID of the scheduled event. */
     celix_framework_logger_t* logger; /**< The framework logger used to log 
information */
-    celix_framework_bundle_entry_t*
-        bndEntry; /**< The bundle entry for the scheduled event. Note the 
scheduled event keeps a use count on the
-                     bundle entry and decreased this during the destruction of 
the scheduled event. */
-
+    long bndId;                       /**< The bundle id for the bundle that 
owns the scheduled event. */
     char* eventName; /**< The name of the scheduled event. Will be 
CELIX_SCHEDULED_EVENT_DEFAULT_NAME if no name is
                         provided during creation. */
-    double initialDelayInSeconds;           /**< The initial delay of the 
scheduled event in seconds. */
-    double intervalInSeconds;               /**< The interval of the scheduled 
event in seconds. */
-    void* eventCallbackData;                /**< The data for the scheduled 
event callback. */
-    void (*eventCallback)(void* eventData); /**< The callback function for the 
scheduled event. */
+    double initialDelayInSeconds;                       /**< The initial delay 
of the scheduled event in seconds. */
+    double intervalInSeconds;                           /**< The interval of 
the scheduled event in seconds. */
+    void* callbackData;                                 /**< The data for the 
scheduled event callback. */
+    void (*callback)(void* callbackData);               /**< The callback 
function for the scheduled event. */
+    void* removedCallbackData;                          /**< The data for the 
scheduled event removed callback. */
+    void (*removedCallback)(void* removedCallbackData); /**< The callback 
function for the scheduled event removed
+                                                            callback. */
 
     celix_thread_mutex_t mutex; /**< The mutex to protect the data below. */
     celix_thread_cond_t cond;   /**< The condition variable to signal the 
scheduled event for a changed callCount. */
@@ -71,13 +71,15 @@ struct celix_scheduled_event {
 };
 
 celix_scheduled_event_t* celix_scheduledEvent_create(celix_framework_logger_t* 
logger,
-                                                     
celix_framework_bundle_entry_t* bndEntry,
+                                                     long bndId,
                                                      long scheduledEventId,
                                                      const char* 
providedEventName,
                                                      double 
initialDelayInSeconds,
                                                      double intervalInSeconds,
-                                                     void* eventData,
-                                                     void 
(*eventCallback)(void* eventData)) {
+                                                     void* callbackData,
+                                                     void (*callback)(void* 
callbackData),
+                                                     void* removedCallbackData,
+                                                     void 
(*removedCallback)(void* removedCallbackData)) {
     celix_scheduled_event_t* event = malloc(sizeof(*event));
     char* eventName =
         providedEventName == NULL ? (char*)CELIX_SCHEDULED_EVENT_DEFAULT_NAME 
: celix_utils_strdup(providedEventName);
@@ -85,24 +87,25 @@ celix_scheduled_event_t* 
celix_scheduledEvent_create(celix_framework_logger_t* l
         fw_log(logger,
                CELIX_LOG_LEVEL_ERROR,
                "Cannot add scheduled event for bundle id %li. Out of memory",
-               bndEntry->bndId);
+               bndId);
         free(event);
         if (eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) {
             free(eventName);
         }
-        celix_framework_bundleEntry_decreaseUseCount(bndEntry);
         return NULL;
     }
 
     event->scheduledEventId = scheduledEventId;
     event->logger = logger;
-    event->bndEntry = bndEntry;
+    event->bndId = bndId;
 
     event->eventName = eventName;
     event->initialDelayInSeconds = initialDelayInSeconds;
     event->intervalInSeconds = intervalInSeconds;
-    event->eventCallbackData = eventData;
-    event->eventCallback = eventCallback;
+    event->callbackData = callbackData;
+    event->callback = callback;
+    event->removedCallbackData = removedCallbackData;
+    event->removedCallback = removedCallback;
     event->useCount = 1;
     event->callCount = 0;
     clock_gettime(CLOCK_MONOTONIC, &event->lastScheduledEventTime);
@@ -115,7 +118,6 @@ celix_scheduled_event_t* 
celix_scheduledEvent_create(celix_framework_logger_t* l
 }
 
 static void celix_scheduledEvent_destroy(celix_scheduled_event_t* event) {
-    celix_framework_bundleEntry_decreaseUseCount(event->bndEntry);
     celixThreadMutex_destroy(&event->mutex);
     celixThreadCondition_destroy(&event->cond);
     if (event->eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) {
@@ -145,6 +147,9 @@ void celix_scheduledEvent_release(celix_scheduled_event_t* 
event) {
     celixThreadMutex_unlock(&event->mutex);
 
     if (unused) {
+        if (event->removedCallback) {
+            event->removedCallback(event->removedCallbackData);
+        }
         celix_scheduledEvent_destroy(event);
     }
 }
@@ -153,16 +158,12 @@ const char* celix_scheduledEvent_getName(const 
celix_scheduled_event_t* event) {
 
 long celix_scheduledEvent_getId(const celix_scheduled_event_t* event) { return 
event->scheduledEventId; }
 
-double celix_scheduledEvent_getInitialDelayInSeconds(const 
celix_scheduled_event_t* event) {
-    return event->initialDelayInSeconds;
-}
-
 double celix_scheduledEvent_getIntervalInSeconds(const 
celix_scheduled_event_t* event) {
     return event->intervalInSeconds;
 }
 
-celix_framework_bundle_entry_t* celix_scheduledEvent_getBundleEntry(const 
celix_scheduled_event_t* event) {
-    return event->bndEntry;
+long celix_scheduledEvent_getBundleId(const celix_scheduled_event_t* event) {
+    return event->bndId;
 }
 
 bool celix_scheduledEvent_deadlineReached(celix_scheduled_event_t* event,
@@ -193,19 +194,14 @@ void 
celix_scheduledEvent_process(celix_scheduled_event_t* event, const struct t
            CELIX_LOG_LEVEL_TRACE,
            "Processing scheduled event %s for bundle id %li",
            event->eventName,
-           event->bndEntry->bndId);
-
-    void (*callback)(void*) = NULL;
-    void* callbackData = NULL;
+           event->bndId);
 
     celixThreadMutex_lock(&event->mutex);
-    callback = event->eventCallback;
-    callbackData = event->eventCallbackData;
     event->useCount += 1;
     celixThreadMutex_unlock(&event->mutex);
-    assert(callback != NULL);
+    assert(event->callback != NULL);
 
-    callback(callbackData); // note called outside of lock
+    event->callback(event->callbackData); // note called outside of lock
 
     celixThreadMutex_lock(&event->mutex);
     event->lastScheduledEventTime = *currentTime;
@@ -232,11 +228,10 @@ size_t 
celix_scheduledEvent_configureWakeup(celix_scheduled_event_t* event) {
 
     fw_log(event->logger,
            CELIX_LOG_LEVEL_DEBUG,
-           "Wakeup scheduled event '%s' (id=%li) for bundle '%s' (id=%li)",
+           "Wakeup scheduled event '%s' (id=%li) for bundle id %li",
            event->eventName,
            event->scheduledEventId,
-           celix_bundle_getSymbolicName(event->bndEntry->bnd),
-           event->bndEntry->bndId);
+           event->bndId);
 
     return currentCallCount + 1;
 }
diff --git a/libs/framework/src/celix_scheduled_event.h 
b/libs/framework/src/celix_scheduled_event.h
index 87e9d14a..18872e44 100644
--- a/libs/framework/src/celix_scheduled_event.h
+++ b/libs/framework/src/celix_scheduled_event.h
@@ -34,23 +34,27 @@ typedef struct celix_scheduled_event 
celix_scheduled_event_t;
  *
  * The scheduled event will be created with a use count of 1.
  *
- * @param[in] bndEntry The bundle entry for which the scheduled event is 
created.
+ * @param[in] bndId The bundle id for the bundle which the scheduled event is 
created.
  * @param[in] scheduledEventId The id of the scheduled event.
  * @param[in] eventName The name of the event. If NULL, 
CELIX_SCHEDULED_EVENT_DEFAULT_NAME is used.
  * @param[in] initialDelayInSeconds The initial delay in seconds.
  * @param[in] intervalInSeconds The interval in seconds.
- * @param[in] eventData The event data.
- * @param[in] eventCallback The event callback.
+ * @param[in] callbackData The event data.
+ * @param[in] callback The event callback.
+ * @param[in] removedCallbackData The removed callback data.
+ * @param[in] removedCallback The removed callback.
  * @return A new scheduled event or NULL if failed.
  */
 celix_scheduled_event_t* celix_scheduledEvent_create(celix_framework_logger_t* 
logger,
-                                                     
celix_framework_bundle_entry_t* bndEntry,
+                                                     long bndId,
                                                      long scheduledEventId,
-                                                     const char* eventName,
+                                                     const char* 
providedEventName,
                                                      double 
initialDelayInSeconds,
                                                      double intervalInSeconds,
-                                                     void* eventData,
-                                                     void 
(*eventCallback)(void* eventData));
+                                                     void* callbackData,
+                                                     void (*callback)(void* 
callbackData),
+                                                     void* removedCallbackData,
+                                                     void 
(*removedCallback)(void* removedCallbackData));
 
 /**
  * @brief Retain the scheduled event by increasing the use count.
@@ -74,20 +78,15 @@ const char* celix_scheduledEvent_getName(const 
celix_scheduled_event_t* event);
  */
 long celix_scheduledEvent_getId(const celix_scheduled_event_t* event);
 
-/**
- * @brief Returns the initial delay of the scheduled event in seconds.
- */
-double celix_scheduledEvent_getInitialDelayInSeconds(const 
celix_scheduled_event_t* event);
-
 /**
  * @brief Returns the interval of the scheduled event in seconds.
  */
 double celix_scheduledEvent_getIntervalInSeconds(const 
celix_scheduled_event_t* event);
 
 /**
- * @brief Returns the framework bundle entry for this scheduled event.
+ * @brief Returns the bundle id of the bundle which created the scheduled 
event.
  */
-celix_framework_bundle_entry_t* celix_scheduledEvent_getBundleEntry(const 
celix_scheduled_event_t* event);
+long celix_scheduledEvent_getBundleId(const celix_scheduled_event_t* event);
 
 /**
  * @brief Returns whether the event deadline is reached and the event should 
be processed.
diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c
index f0d9b860..bcd9a8ec 100644
--- a/libs/framework/src/framework.c
+++ b/libs/framework/src/framework.c
@@ -1181,6 +1181,9 @@ static void* framework_shutdown(void *framework) {
         //NOTE possible starvation.
         fw_bundleEntry_waitTillUseCountIs(entry, 1);  //note this function has 
1 use count.
 
+        //note race between condition (use count == 1) and bundle stop, 
meaning use count can be > 1 when
+        //celix_framework_stopBundleEntry is called.
+
         bundle_state_e state = celix_bundle_getState(entry->bnd);
         if (state == CELIX_BUNDLE_STATE_ACTIVE || state == 
CELIX_BUNDLE_STATE_STARTING) {
             celix_framework_stopBundleEntry(fw, entry);
@@ -1458,11 +1461,10 @@ static double 
celix_framework_processScheduledEvents(celix_framework_t* fw) {
                 celixThreadMutex_lock(&fw->dispatcher.mutex);
                 fw_log(fw->logger,
                        CELIX_LOG_LEVEL_DEBUG,
-                       "Removing processed one-shot scheduled event '%s' 
(id=%li) for bundle '%s' (id=%li)",
+                       "Removing processed one-shot scheduled event '%s' 
(id=%li) for bundle if %li.",
                        celix_scheduledEvent_getName(callEvent),
                        celix_scheduledEvent_getId(callEvent),
-                       
celix_bundle_getSymbolicName(celix_scheduledEvent_getBundleEntry(callEvent)->bnd),
-                       celix_scheduledEvent_getBundleEntry(callEvent)->bndId);
+                       celix_scheduledEvent_getBundleId(callEvent));
                 celix_longHashMap_remove(fw->dispatcher.scheduledEvents, 
celix_scheduledEvent_getId(callEvent));
                 celix_scheduledEvent_release(callEvent);
                 celixThreadMutex_unlock(&fw->dispatcher.mutex);
@@ -1480,8 +1482,7 @@ void 
celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId) {
         celixThreadMutex_lock(&fw->dispatcher.mutex);
         CELIX_LONG_HASH_MAP_ITERATE(fw->dispatcher.scheduledEvents, entry) {
             celix_scheduled_event_t* visit = entry.value.ptrValue;
-            celix_framework_bundle_entry_t* bndEntry = 
celix_scheduledEvent_getBundleEntry(visit);
-            if (bndEntry->bndId == bndId) {
+            if (bndId == celix_scheduledEvent_getBundleId(visit)) {
                 celix_longHashMap_remove(fw->dispatcher.scheduledEvents, 
celix_scheduledEvent_getId(visit));
                 removedEvent = visit;
                 break;
@@ -1490,26 +1491,24 @@ void 
celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId) {
         celixThreadMutex_unlock(&fw->dispatcher.mutex);
 
         if (removedEvent) {
-            celix_framework_bundle_entry_t* bndEntry = 
celix_scheduledEvent_getBundleEntry(removedEvent);
             const char* eventName = celix_scheduledEvent_getName(removedEvent);
             double interval = 
celix_scheduledEvent_getIntervalInSeconds(removedEvent);
             long eventId = celix_scheduledEvent_getId(removedEvent);
+            long eventBndId = celix_scheduledEvent_getBundleId(removedEvent);
             if (interval > 0) {
                 fw_log(fw->logger,
                        CELIX_LOG_LEVEL_WARNING,
-                       "Removing dangling scheduled event '%s' (id=%li) for 
bundle '%s' (id=%li). This should have been cleaned up by the bundle.",
+                       "Removing dangling scheduled event '%s' (id=%li) for 
bundle id %li. This should have been cleaned up by the bundle.",
                        eventName,
                        eventId,
-                       celix_bundle_getSymbolicName(bndEntry->bnd),
-                       bndEntry->bndId);
+                       eventBndId);
             } else {
                 fw_log(fw->logger,
                        CELIX_LOG_LEVEL_DEBUG,
-                       "Removing unprocessed one-shot scheduled event '%s' 
(id=%li) for bundle '%s' (id=%li)",
+                       "Removing unprocessed one-shot scheduled event '%s' 
(id=%li) for bundle id %li.",
                        eventName,
                        eventId,
-                       celix_bundle_getSymbolicName(bndEntry->bnd),
-                       bndEntry->bndId);
+                       eventBndId);
             } 
             celix_scheduledEvent_release(removedEvent);
         }
@@ -2528,35 +2527,39 @@ void 
celix_framework_waitUntilNoPendingRegistration(celix_framework_t* fw)
     celixThreadMutex_unlock(&fw->dispatcher.mutex);
 }
 
-long celix_framework_addScheduledEvent(celix_framework_t* fw,
-                                       long bndId,
-                                       const char* eventName,
-                                       double initialDelayInSeconds,
-                                       double intervalInSeconds,
-                                       void* eventData,
-                                       void (*eventCallback)(void* eventData)) 
{
-    if (eventCallback == NULL) {
+long celix_framework_scheduleEvent(celix_framework_t* fw,
+                                    long bndId,
+                                    const char* eventName,
+                                    double initialDelayInSeconds,
+                                    double intervalInSeconds,
+                                    void* callbackData,
+                                    void (*callback)(void*),
+                                    void* removeCallbackData,
+                                    void (*removeCallback)(void*)) {
+    if (callback == NULL) {
         fw_log(fw->logger,
                CELIX_LOG_LEVEL_ERROR,
-               "Cannot add scheduled event for bundle id %li. Invalid NULL 
event callback",
+               "Cannot add scheduled event for bundle id %li. Invalid NULL 
event callback.",
                bndId);
         return -1;
     }
 
     celix_framework_bundle_entry_t* bndEntry = 
celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId);
     if (bndEntry == NULL) {
-        fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event 
for non existing bundle id %li", bndId);
+        fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event 
for non existing bundle id %li.", bndId);
         return -1;
     }
-
     celix_scheduled_event_t* event = celix_scheduledEvent_create(fw->logger,
-                                                                 bndEntry,
+                                                                 
bndEntry->bndId,
                                                                  
celix_framework_nextScheduledEventId(fw),
                                                                  eventName,
                                                                  
initialDelayInSeconds,
                                                                  
intervalInSeconds,
-                                                                 eventData,
-                                                                 
eventCallback);
+                                                                 callbackData,
+                                                                 callback,
+                                                                 
removeCallbackData,
+                                                                 
removeCallback);
+    celix_framework_bundleEntry_decreaseUseCount(bndEntry);
 
     if (event == NULL) {
         return -1L; //error logged by celix_scheduledEvent_create
@@ -2564,7 +2567,7 @@ long celix_framework_addScheduledEvent(celix_framework_t* 
fw,
 
     fw_log(fw->logger,
            CELIX_LOG_LEVEL_DEBUG,
-           "Added scheduled event '%s' (id=%li) for bundle '%s' (id=%li)",
+           "Added scheduled event '%s' (id=%li) for bundle '%s' (id=%li).",
            celix_scheduledEvent_getName(event),
            celix_scheduledEvent_getId(event),
            celix_bundle_getSymbolicName(bndEntry->bnd),
@@ -2591,13 +2594,13 @@ celix_status_t 
celix_framework_wakeupScheduledEvent(celix_framework_t* fw,
     if (event == NULL) {
         fw_log(fw->logger,
                CELIX_LOG_LEVEL_WARNING,
-               "celix_framework_wakeupScheduledEvent called with unknown 
scheduled event id %li",
+               "celix_framework_wakeupScheduledEvent called with unknown 
scheduled event id %li.",
                scheduledEventId);
         return CELIX_ILLEGAL_ARGUMENT;
     }
 
     
-    size_t callCountAfterWakup = celix_scheduledEvent_configureWakeup(event);
+    size_t callCountAfterWakeup = celix_scheduledEvent_configureWakeup(event);
     celixThreadMutex_lock(&fw->dispatcher.mutex);
     celixThreadCondition_broadcast(&fw->dispatcher.cond); //notify dispatcher 
thread for configured wakeup
     celixThreadMutex_unlock(&fw->dispatcher.mutex);
@@ -2608,15 +2611,14 @@ celix_status_t 
celix_framework_wakeupScheduledEvent(celix_framework_t* fw,
             fw_log(fw->logger, CELIX_LOG_LEVEL_WARNING, 
"celix_framework_wakeupScheduledEvent called from the "
                 "event loop thread. This can result in a deadlock!");
         }
-        status = celix_scheduledEvent_waitForAtLeastCallCount(event, 
callCountAfterWakup, waitTimeInSeconds);
+        status = celix_scheduledEvent_waitForAtLeastCallCount(event, 
callCountAfterWakeup, waitTimeInSeconds);
     } 
     celix_scheduledEvent_release(event);
-    
 
     return status;
 }
 
-bool celix_framework_removeScheduledEvent(celix_framework_t* fw, long 
scheduledEventId) {
+bool celix_framework_removeScheduledEvent(celix_framework_t* fw, bool 
errorIfNotFound, long scheduledEventId) {
     if (scheduledEventId < 0) {
         return false; //silently ignore
     }
@@ -2627,19 +2629,18 @@ bool 
celix_framework_removeScheduledEvent(celix_framework_t* fw, long scheduledE
     celixThreadMutex_unlock(&fw->dispatcher.mutex);
 
     if (event == NULL) {
-        fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot remove scheduled 
event with id %li. Not found",
+        celix_log_level_e level = errorIfNotFound ? CELIX_LOG_LEVEL_TRACE : 
CELIX_LOG_LEVEL_ERROR;
+        fw_log(fw->logger, level, "Cannot remove scheduled event with id %li. 
Not found.",
                scheduledEventId);
         return false;
     }
 
-    celix_framework_bundle_entry_t* bndEntry = 
celix_scheduledEvent_getBundleEntry(event);
     fw_log(fw->logger,
            CELIX_LOG_LEVEL_DEBUG,
-           "Removing scheduled event '%s' (id=%li) for bundle '%s' (id=%li)",
+           "Removing scheduled event '%s' (id=%li) for bundle id %li.",
            celix_scheduledEvent_getName(event),
            celix_scheduledEvent_getId(event),
-           celix_bundle_getSymbolicName(bndEntry->bnd),
-           bndEntry->bndId);
+           celix_scheduledEvent_getBundleId(event));
     celix_scheduledEvent_release(event);
     return true;
 }
@@ -2653,7 +2654,7 @@ long celix_framework_fireGenericEvent(framework_t* fw, 
long eventId, long bndId,
     if (bndId >=0) {
         bndEntry = 
celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId);
         if (bndEntry == NULL) {
-            fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot find bundle for 
id %li", bndId);
+            fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot find bundle for 
id %li.", bndId);
             return -1L;
         }
     }
diff --git a/libs/framework/src/framework_private.h 
b/libs/framework/src/framework_private.h
index 0a031719..b59d0371 100644
--- a/libs/framework/src/framework_private.h
+++ b/libs/framework/src/framework_private.h
@@ -464,13 +464,15 @@ long celix_framework_nextScheduledEventId(framework_t 
*fw);
  * @return The scheduled event id of the scheduled event. Can be used to 
cancel the event.
  * @retval <0 If the event could not be added.
  */
-long celix_framework_addScheduledEvent(celix_framework_t* fw,
-                                       long bndId,
-                                       const char* eventName,
-                                       double initialDelayInSeconds,
-                                       double intervalInSeconds,
-                                       void* eventData,
-                                       void (*eventCallback)(void* eventData));
+long celix_framework_scheduleEvent(celix_framework_t* fw,
+                                    long bndId,
+                                    const char* eventName,
+                                    double initialDelayInSeconds,
+                                    double intervalInSeconds,
+                                    void* callbackData,
+                                    void (*callback)(void*),
+                                    void* removeCallbackData,
+                                    void (*removeCallback)(void*));
                                        
 /**
  * @brief Wakeup a scheduled event.
@@ -494,10 +496,11 @@ celix_status_t 
celix_framework_wakeupScheduledEvent(celix_framework_t* fw,
  * When this function returns, no more scheduled event callbacks will be 
called.
  *
  * @param[in] fw The Celix framework
+ * @param[in] errorIfNotFound If true, removal of a non existing scheduled 
event id will not be logged.
  * @param[in] scheduledEventId The scheduled event id to cancel.
  * @return true if a scheduled event is cancelled, false if the scheduled 
event id is not known.
  */
-bool celix_framework_removeScheduledEvent(celix_framework_t* fw, long 
scheduledEventId);
+bool celix_framework_removeScheduledEvent(celix_framework_t* fw, bool 
errorIfNotFound, long scheduledEventId);
 
 /**
  * Remove all scheduled events for the provided bundle id and logs warning if 
there are still un-removed scheduled


Reply via email to