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
