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 ab6b1aa532da88ecbbf5be1f154e82e87cb9c2cb Author: Pepijn Noltes <[email protected]> AuthorDate: Sun May 14 19:51:47 2023 +0200 Add initial implement of scheduled event for the event thread --- libs/framework/CMakeLists.txt | 3 +- libs/framework/gtest/CMakeLists.txt | 1 + .../framework/gtest/src/CelixFrameworkTestSuite.cc | 20 ++ .../framework/gtest/src/ScheduledEventTestSuite.cc | 187 +++++++++++++++ libs/framework/include/celix_bundle_context.h | 85 ++++++- libs/framework/include/celix_framework.h | 30 ++- libs/framework/src/bundle_context.c | 16 ++ libs/framework/src/celix_scheduled_event.c | 144 ++++++++++++ libs/framework/src/celix_scheduled_event.h | 128 +++++++++++ libs/framework/src/framework.c | 250 +++++++++++++++++++-- libs/framework/src/framework_private.h | 68 +++++- libs/utils/include/celix_errno.h | 2 + libs/utils/src/celix_threads.c | 2 +- 13 files changed, 895 insertions(+), 41 deletions(-) diff --git a/libs/framework/CMakeLists.txt b/libs/framework/CMakeLists.txt index 82968846..3cde3860 100644 --- a/libs/framework/CMakeLists.txt +++ b/libs/framework/CMakeLists.txt @@ -34,7 +34,8 @@ set(FRAMEWORK_SRC src/framework_bundle_lifecycle_handler.c src/celix_bundle_state.c src/celix_framework_utils.c - src/celix_module_private.h) + src/celix_scheduled_event.c +) set(FRAMEWORK_DEPS libuuid::libuuid CURL::libcurl ZLIB::ZLIB ${CMAKE_DL_LIBS}) add_library(framework SHARED ${FRAMEWORK_SRC}) diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index c05fe4f5..b2844a1b 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -56,6 +56,7 @@ set(CELIX_FRAMEWORK_TEST_SOURCES src/CxxBundleActivatorTestSuite.cc src/BundleArchiveTestSuite.cc src/CelixLauncherTestSuite.cc + src/ScheduledEventTestSuite.cc ) add_executable(test_framework ${CELIX_FRAMEWORK_TEST_SOURCES}) diff --git a/libs/framework/gtest/src/CelixFrameworkTestSuite.cc b/libs/framework/gtest/src/CelixFrameworkTestSuite.cc index 1a3562c1..92c9d0d1 100644 --- a/libs/framework/gtest/src/CelixFrameworkTestSuite.cc +++ b/libs/framework/gtest/src/CelixFrameworkTestSuite.cc @@ -79,6 +79,26 @@ TEST_F(CelixFrameworkTestSuite, EventQueueTest) { EXPECT_EQ(4, count); } +TEST_F(CelixFrameworkTestSuite, TimedWaitEventQueueTest) { + //When there is a emtpy event queue + celix_framework_waitForEmptyEventQueue(framework.get()); + + //And a generic event is fired, that block the queue for 20ms + auto callback = [](void* /*data*/) { + std::this_thread::sleep_for(std::chrono::milliseconds{20}); + }; + celix_framework_fireGenericEvent(framework.get(), -1L, -1L, "test", nullptr, callback, nullptr, nullptr); + + //Then a wait for empty event queue for max 5ms will return a timeout + celix_status_t status = celix_framework_timedWaitForEmptyEventQueue(framework.get(), 0.005); + EXPECT_EQ(CELIX_TIMEOUT, status); + + //And a wait for empty event queue for max 30ms will return success + status = celix_framework_timedWaitForEmptyEventQueue(framework.get(), 0.03); + EXPECT_EQ(CELIX_SUCCESS, status); +} + + TEST_F(CelixFrameworkTestSuite, AsyncInstallStartStopAndUninstallBundleTest) { long bndId = celix_framework_installBundleAsync(framework.get(), SIMPLE_TEST_BUNDLE1_LOCATION, false); EXPECT_GE(bndId, 0); diff --git a/libs/framework/gtest/src/ScheduledEventTestSuite.cc b/libs/framework/gtest/src/ScheduledEventTestSuite.cc new file mode 100644 index 00000000..aab19a1d --- /dev/null +++ b/libs/framework/gtest/src/ScheduledEventTestSuite.cc @@ -0,0 +1,187 @@ +/* + * 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 <gtest/gtest.h> + +#include "celix/FrameworkFactory.h" +#include "celix_bundle_context.h" +#include "celix_scheduled_event.h" +#include "framework_private.h" + +class ScheduledEventTestSuite : public ::testing::Test { +public: + ScheduledEventTestSuite() { + fw = celix::createFramework({{"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "info"}}); + } + + std::shared_ptr<celix::Framework> fw{}; +}; + +TEST_F(ScheduledEventTestSuite, OnceShotEventTest) { + auto ctx = fw->getFrameworkBundleContext(); + + struct event_info { + std::atomic<int> count{0}; + }; + event_info info{}; + + 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; + + + //And I schedule the event + long eventId = celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts); + EXPECT_GE(eventId, 0); + + //Then the event is called once + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + EXPECT_EQ(1, info.count.load()); +} + +TEST_F(ScheduledEventTestSuite, ScheduledEventTest) { + auto ctx = fw->getFrameworkBundleContext(); + + struct event_info { + std::atomic<int> count{0}; + }; + event_info info{}; + + 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 + celix_scheduled_event_options_t opts{}; + opts.eventName = "Scheduled event test"; + opts.initialDelayInSeconds = 0.01; + opts.intervalInSeconds = 0.02; + opts.eventData = &info; + opts.eventCallback = callback; + + //And I schedule the event + long eventId = celix_bundleContext_addScheduledEvent(ctx->getCBundleContext(), &opts); + EXPECT_GE(eventId, 0); + + //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); + + //And when I remove the event + celix_bundleContext_removeScheduledEvent(ctx->getCBundleContext(), eventId); +} + +TEST_F(ScheduledEventTestSuite, ManyScheduledEventTest) { + auto ctx = fw->getFrameworkBundleContext(); + + struct event_info { + std::atomic<int> count{0}; + }; + event_info info{}; + 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 + for (int i = 0; i < 1000; ++i) { + //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); + EXPECT_GE(eventId, 0); + if (opts.intervalInSeconds > 0) { //not a one-shot event + eventIds.push_back(eventId); + } + } + + //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 + 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 + auto ctx = fw->getFrameworkBundleContext(); + + auto callback = [](void */*data*/) { + fprintf(stdout, "Scheduled event called\n"); + }; + celix_scheduled_event_options_t opts{}; + opts.eventName = "Unremoved scheduled event test"; + opts.intervalInSeconds = 0.02; + opts.eventCallback = callback; + long scheduledEventId = celix_bundleContext_addScheduledEvent(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 +} + +TEST_F(ScheduledEventTestSuite, AddWithoutRemoveOneShotEventTest) { + //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"; + }; + celix_scheduled_event_options_t opts{}; + opts.eventName = "Unremoved one-shot scheduled event test"; + opts.initialDelayInSeconds = 100; + opts.eventCallback = callback; + long scheduledEventId = celix_bundleContext_addScheduledEvent(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 +} + +TEST_F(ScheduledEventTestSuite, InvalidOptionsAndArgumentsTest) { + //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); + + //Then I expect an error + EXPECT_LT(scheduledEventId, 0); + + //celix_scheduleEvent_destroy and celix_scheduledEvent_waitAndDestroy can be called with NULL + celix_scheduledEvent_destroy(nullptr); + celix_scheduledEvent_waitAndDestroy(nullptr); +} diff --git a/libs/framework/include/celix_bundle_context.h b/libs/framework/include/celix_bundle_context.h index 8b1ef2bc..c0e15bf0 100644 --- a/libs/framework/include/celix_bundle_context.h +++ b/libs/framework/include/celix_bundle_context.h @@ -203,10 +203,10 @@ typedef struct celix_service_registration_options { void (*asyncCallback)(void *data, long serviceId) CELIX_OPTS_INIT; } celix_service_registration_options_t; -/** +#ifndef __cplusplus +/*! * @brief C Macro to create a empty celix_service_registration_options_t type. */ -#ifndef __cplusplus #define CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS { .svc = NULL, \ .factory = NULL, \ .serviceName = NULL, \ @@ -356,10 +356,10 @@ typedef struct celix_service_filter_options { bool ignoreServiceLanguage CELIX_OPTS_INIT; } celix_service_filter_options_t; -/** +#ifndef __cplusplus +/*! * @brief C Macro to create a empty celix_service_filter_options_t type. */ -#ifndef __cplusplus #define CELIX_EMPTY_SERVICE_FILTER_OPTIONS {.serviceName = NULL, .versionRange = NULL, .filter = NULL, .serviceLanguage = NULL, .ignoreServiceLanguage = false} #endif @@ -563,10 +563,10 @@ typedef struct celix_service_tracking_options { void (*trackerCreatedCallback)(void *trackerCreatedCallbackData) CELIX_OPTS_INIT; } celix_service_tracking_options_t; -/** +#ifndef __cplusplus +/*! * @brief C Macro to create a empty celix_service_tracking_options_t type. */ -#ifndef __cplusplus #define CELIX_EMPTY_SERVICE_TRACKING_OPTIONS { .filter.serviceName = NULL, \ .filter.versionRange = NULL, \ .filter.filter = NULL, \ @@ -781,10 +781,10 @@ typedef struct celix_service_use_options { int flags CELIX_OPTS_INIT; } celix_service_use_options_t; -/** +#ifndef __cplusplus +/*! * @brief C Macro to create a empty celix_service_use_options_t type. */ -#ifndef __cplusplus #define CELIX_EMPTY_SERVICE_USE_OPTIONS {.filter.serviceName = NULL, \ .filter.versionRange = NULL, \ .filter.filter = NULL, \ @@ -1074,10 +1074,10 @@ typedef struct celix_bundle_tracker_options { void (*trackerCreatedCallback)(void *trackerCreatedCallbackData) CELIX_OPTS_INIT; } celix_bundle_tracking_options_t; -/** +#ifndef __cplusplus +/*! * @brief C Macro to create a empty celix_service_filter_options_t type. */ -#ifndef __cplusplus #define CELIX_EMPTY_BUNDLE_TRACKING_OPTIONS {.callbackHandle = NULL, .onInstalled = NULL, .onStarted = NULL, .onStopped = NULL, .onBundleEvent = NULL, .includeFrameworkBundle = false, .trackerCreatedCallbackData = NULL, .trackerCreatedCallback = NULL} #endif @@ -1225,7 +1225,7 @@ CELIX_FRAMEWORK_EXPORT long celix_bundleContext_trackServiceTrackersAsync( * * This tracker can be stopped with the celix_bundleContext_stopTracker function. * - * @param ctx The bundle context + * @param ctx The bundle context. * @param serviceName The target service name for the service tracker to track. * If NULL is provided, add/remove callbacks will be called for all service trackers in the framework. * @param callbackHandle The callback handle which will be provided as handle in the trackerAdd and trackerRemove callback. @@ -1254,6 +1254,69 @@ CELIX_FRAMEWORK_EXPORT celix_dependency_manager_t* celix_bundleContext_getDepend */ CELIX_FRAMEWORK_EXPORT void celix_bundleContext_waitForEvents(celix_bundle_context_t *ctx); +/** + * @struct celix_scheduled_event_options + * @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. */ + + double initialDelayInSeconds CELIX_OPTS_INIT; /**< @brief Initial delay in seconds before the event is processed.*/ + + double intervalInSeconds CELIX_OPTS_INIT; /**< @brief Schedule interval in seconds. + * 0 means one shot scheduled event. + */ + + void* eventData CELIX_OPTS_INIT; /**< @brief Data passed to the eventCallback function when a event is scheduled.*/ + + void (*eventCallback)(void* eventData) CELIX_OPTS_INIT; /**< @brief Callback function called to process a scheduled event.*/ +} celix_scheduled_event_options_t; + +/** + * @brief Add a scheduled event to the Celix framework. + * + * The scheduled event wil be called repeatedly on the Celix framework event thread using the provided interval. + * For the interval time a monotonic clock is used. The event callback should be fast and non-blocking, otherwise + * the framework event queue will be blocked and framework will not function properly. + * + * Scheduled events can be scheduled later than the provided initial delay and interval, because they are processed + * after other events in the Celix event thread. + * The target - but not guaranteed - precision of the scheduled event trigger is 1 microsecond. + * + * If the provided interval is 0, the scheduled event will a one short scheduled event and will be called once + * after the provided initial delay. If a bundle stops before the one short scheduled event is called, the scheduled + * event will be removed and not called. + * + * Scheduled events should be removed by the caller when not needed anymore. + * Exception are the one shot scheduled events, these are automatically removed after the event callback is called. + * + * Note during bundle stop the framework will check if all scheduled events for the bundle are removed. + * For every not removed scheduled event, which is not a one short event, a warning will be logged and the + * scheduled event will be removed. + * + * @param[in] ctx The bundle context. + * @param[in] options The scheduled event options, which describe the to be added scheduled event. + * @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); + +/** + * @brief Cancel and remove a scheduled event. + * + * When this function returns, no more scheduled event callbacks will be called. + * + * @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_removeScheduledEvent(celix_bundle_context_t* ctx, + long scheduledEventId); /** * @brief Returns the bundle for this bundle context. diff --git a/libs/framework/include/celix_framework.h b/libs/framework/include/celix_framework.h index e5beb0d2..9c7c2e18 100644 --- a/libs/framework/include/celix_framework.h +++ b/libs/framework/include/celix_framework.h @@ -50,7 +50,6 @@ extern "C" { * @note The Celix framework instance is thread safe. */ - /** * @brief Returns the framework UUID. This is unique for every created framework and will not be the same if the process is * restarted. @@ -261,26 +260,36 @@ CELIX_FRAMEWORK_EXPORT celix_array_list_t* celix_framework_listBundles(celix_fra */ CELIX_FRAMEWORK_EXPORT celix_array_list_t* celix_framework_listInstalledBundles(celix_framework_t* framework); +/** + * @brief Sets the log function for this framework. + * Default the celix framework will log to stdout/stderr. + * + * A log function can be injected to change how the Celix framework logs. + * Can be reset by setting the log function to NULL. + */ +CELIX_FRAMEWORK_EXPORT void celix_framework_setLogCallback(celix_framework_t* fw, void* logHandle, void (*logFunction)(void* handle, celix_log_level_e level, const char* file, const char *function, int line, const char *format, va_list formatArgs)); + /** * @brief Wait until the framework event queue is empty. * * 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, mainly useful - * for testing. + * This function can be used to ensure that all queue event are handled. * * @param fw The Celix Framework */ CELIX_FRAMEWORK_EXPORT void celix_framework_waitForEmptyEventQueue(celix_framework_t *fw); /** - * @brief Sets the log function for this framework. - * Default the celix framework will log to stdout/stderr. + * @brief Wait until the framework event queue is empty or the provided period is reached. * - * A log function can be injected to change how the Celix framework logs. - * Can be reset by setting the log function to NULL. + * 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. + * + * @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. + * @return CELIX_SUCCESS if the event queue is empty or CELIX_TIMEOUT if the timeoutInSeconds is reached. */ -CELIX_FRAMEWORK_EXPORT void celix_framework_setLogCallback(celix_framework_t* fw, void* logHandle, void (*logFunction)(void* handle, celix_log_level_e level, const char* file, const char *function, int line, const char *format, va_list formatArgs)); - +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. @@ -301,6 +310,9 @@ CELIX_FRAMEWORK_EXPORT bool celix_framework_isCurrentThreadTheEventLoop(celix_fr /** * @brief Fire a generic event. The event will be added to the event loop and handled on the event loop thread. * + * The process callback should be fast and non-blocking, otherwise + * 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. * diff --git a/libs/framework/src/bundle_context.c b/libs/framework/src/bundle_context.c index d5bb0540..f60ea47b 100644 --- a/libs/framework/src/bundle_context.c +++ b/libs/framework/src/bundle_context.c @@ -112,6 +112,7 @@ celix_status_t bundleContext_destroy(bundle_context_pt context) { } 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); @@ -1482,6 +1483,21 @@ 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); +} + +bool celix_bundleContext_removeScheduledEvent(celix_bundle_context_t* ctx, long scheduledEventId) { + return celix_framework_removeScheduledEvent(ctx->framework, scheduledEventId); +} + celix_bundle_t* celix_bundleContext_getBundle(const celix_bundle_context_t *ctx) { celix_bundle_t *bnd = NULL; if (ctx != NULL) { diff --git a/libs/framework/src/celix_scheduled_event.c b/libs/framework/src/celix_scheduled_event.c new file mode 100644 index 00000000..66c02920 --- /dev/null +++ b/libs/framework/src/celix_scheduled_event.c @@ -0,0 +1,144 @@ +/* + * 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 <assert.h> + +#include "celix_scheduled_event.h" +#include "celix_utils.h" + +celix_scheduled_event_t* celix_scheduledEvent_create(celix_framework_logger_t* logger, + celix_framework_bundle_entry_t* bndEntry, + long scheduledEventId, + const char* providedEventName, + double initialDelayInSeconds, + double intervalInSeconds, + void* eventData, + void (*eventCallback)(void* eventData)) { + if (eventCallback == NULL) { + fw_log(logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event for bundle id %li. No event callback provided", bndEntry->bndId); + return NULL; + } + + celix_scheduled_event_t* event = malloc(sizeof(*event)); + char* eventName = providedEventName == NULL ? (char*)CELIX_SCHEDULED_EVENT_DEFAULT_NAME + : celix_utils_strdup(providedEventName); + if (event == NULL || eventName == NULL) { + fw_log(logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event for bundle id %li. Out of memory", bndEntry->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->eventName = eventName; + event->initialDelayInSeconds = initialDelayInSeconds; + event->intervalInSeconds = intervalInSeconds; + event->eventCallbackData = eventData; + event->eventCallback = eventCallback; + event->useCount = 0; + event->callCount = 0; + clock_gettime(CLOCK_MONOTONIC, &event->lastScheduledEventTime); + celixThreadMutex_create(&event->mutex, NULL); + celixThreadCondition_init(&event->cond, NULL); + + return event; +} + +void celix_scheduledEvent_destroy(celix_scheduled_event_t* event) { + if (event != NULL) { + celix_framework_bundleEntry_decreaseUseCount(event->bndEntry); + celixThreadMutex_destroy(&event->mutex); + celixThreadCondition_destroy(&event->cond); + if (event->eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) { + free(event->eventName); + } + free(event); + } +} + +void celix_scheduledEvent_waitAndDestroy(celix_scheduled_event_t* event) { + if (event == NULL) { + return; + } + + //wait for use count 0; + celixThreadMutex_lock(&event->mutex); + while (event->useCount) { + celixThreadCondition_wait(&event->cond, &event->mutex); + } + celixThreadMutex_unlock(&event->mutex); + + celix_scheduledEvent_destroy(event); +} + +bool celix_scheduledEvent_deadlineReached(celix_scheduled_event_t* event, + const struct timespec* currentTime, + double* nextProcessTimeInSeconds) { + celixThreadMutex_lock(&event->mutex); + double elapsed = celix_difftime(&event->lastScheduledEventTime, currentTime); + double deadline = event->callCount == 0 ? event->initialDelayInSeconds : event->intervalInSeconds; + deadline -= CELIX_SCHEDULED_EVENT_INTERVAL_ALLOW_ERROR_IN_SECONDS; + bool deadlineReached = elapsed >= deadline; + if (deadlineReached) { + *nextProcessTimeInSeconds = + event->intervalInSeconds == 0 /*one shot*/ ? CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS + : event->intervalInSeconds; + } else { + *nextProcessTimeInSeconds = event->callCount == 0 ? event->initialDelayInSeconds : event->intervalInSeconds; + } + celixThreadMutex_unlock(&event->mutex); + return deadlineReached; +} + +void celix_scheduledEvent_process(celix_scheduled_event_t* event, const struct timespec* currentTime) { + void (*callback)(void*) = NULL; + void* callbackData = NULL; + + celixThreadMutex_lock(&event->mutex); + callback = event->eventCallback; + callbackData = event->eventCallbackData; + event->useCount += 1; + celixThreadCondition_broadcast(&event->cond); //broadcast for changed useCount + celixThreadMutex_unlock(&event->mutex); + assert(callback != NULL); + + callback(callbackData); //note called outside of lock + + celixThreadMutex_lock(&event->mutex); + event->lastScheduledEventTime = *currentTime; + event->useCount -= 1; + event->callCount += 1; + celixThreadCondition_broadcast(&event->cond); //broadcast for changed useCount + celixThreadMutex_unlock(&event->mutex); +} + + +bool celix_scheduleEvent_isDone(celix_scheduled_event_t* event) { + bool isDone = false; + celixThreadMutex_lock(&event->mutex); + isDone = event->intervalInSeconds == 0 && event->callCount > 0; + celixThreadMutex_unlock(&event->mutex); + return isDone; +} diff --git a/libs/framework/src/celix_scheduled_event.h b/libs/framework/src/celix_scheduled_event.h new file mode 100644 index 00000000..d1976152 --- /dev/null +++ b/libs/framework/src/celix_scheduled_event.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. + */ + +#ifndef CELIX_CELIX_SCHEDULED_EVENT_H +#define CELIX_CELIX_SCHEDULED_EVENT_H + +#include "celix_bundle_context.h" +#include "framework_private.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//Allow, 1 microsecond error in interval to ensure pthread cond wakeups result in a call. +#define CELIX_SCHEDULED_EVENT_INTERVAL_ALLOW_ERROR_IN_SECONDS 0.000001 + +static const char* const CELIX_SCHEDULED_EVENT_DEFAULT_NAME = "unnamed"; + +typedef struct celix_scheduled_event { + long scheduledEventId; + celix_framework_logger_t* logger; + celix_framework_bundle_entry_t* bndEntry; + + char* eventName; + double initialDelayInSeconds; + double intervalInSeconds; + void* eventCallbackData; + void (*eventCallback)(void* eventData); + + celix_thread_mutex_t mutex; //protects below + celix_thread_cond_t cond; + size_t useCount; //use count, how many times the event is used to process a eventCallback or doneCallback. + size_t callCount; //nr of times the eventCallback is called + struct timespec lastScheduledEventTime; +} celix_scheduled_event_t; + +/** + * @brief Create a scheduled event for the given bundle. + * @param[in] bndEntry The bundle entry for 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. + * @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 scheduledEventId, + const char* eventName, + double initialDelayInSeconds, + double intervalInSeconds, + void* eventData, + void (*eventCallback)(void* eventData)); + +/** + * @brief Destroy the event. + */ +void celix_scheduledEvent_destroy(celix_scheduled_event_t* event); + +/** + * @brief Wait until the useCount is 0 and destroy the event. + */ +void celix_scheduledEvent_waitAndDestroy(celix_scheduled_event_t* event); + +/** + * @brief Returns whether the event deadline is reached and the event should be processed. + * @param[in] event The event to check. + * @param[in] currentTime The current time. + * @param[out] nextProcessTimeInSeconds The time in seconds until the next event should be processed. + * if the deadline is reached, this is the next interval. + * @return true if the event deadline is reached and the event should be processed. + */ +bool celix_scheduledEvent_deadlineReached(celix_scheduled_event_t* event, + const struct timespec* currentTime, + double* nextProcessTimeInSeconds); + +/** + * @brief Process the event by calling the event callback. + * + * Must be called on the Celix event thread. + * + * @param[in] event The event to process. + * @param[in] currentTime The current time. + * @return The time in seconds until the next event should be processed. + */ +void celix_scheduledEvent_process(celix_scheduled_event_t* event, const struct timespec* currentTime); + +/** + * @brief Returns true if the event is a one-shot event and is done. + * @param[in] event The event to check. + * @return true if the event is a one-shot event and is done. + */ +bool celix_scheduleEvent_isDone(celix_scheduled_event_t* event); + +/** + * @brief Wait for a scheduled event to reach at least the provided call count. + * @param[in] event The event to wait for. + * @param[in] callCount The call count to wait for. + * @param[in] timeout The max time to wait in seconds. + * @return CELIX_SUCCESS if the scheduled event reached the call count, CELIX_TIMEOUT if the scheduled event + * did not reach the call count within the timeout. + */ +celix_status_t +celix_scheduledEvent_waitForAtLeastCallCount(celix_scheduled_event_t* event, size_t useCount, double timeout); + +#ifdef __cplusplus +}; +#endif + +#endif //CELIX_CELIX_SCHEDULED_EVENT_H diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index df4bb7b4..b8252c01 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -18,14 +18,12 @@ */ #include <stdlib.h> -#include <stdio.h> #include <string.h> -#include <unistd.h> #include <stdbool.h> #include <uuid/uuid.h> #include <assert.h> -#include <celix_log_utils.h> +#include "celix_log_utils.h" #include "celix_dependency_manager.h" #include "framework_private.h" #include "celix_constants.h" @@ -43,6 +41,7 @@ #include "bundle_archive_private.h" #include "celix_module_private.h" #include "celix_convert_utils.h" +#include "celix_scheduled_event.h" #include "celix_build_assert.h" struct celix_bundle_activator { @@ -249,6 +248,7 @@ celix_status_t framework_create(framework_pt *out, celix_properties_t* config) { framework->dispatcher.eventQueueCap = (int)celix_framework_getConfigPropertyAsLong(framework, CELIX_FRAMEWORK_STATIC_EVENT_QUEUE_SIZE, CELIX_FRAMEWORK_DEFAULT_STATIC_EVENT_QUEUE_SIZE, NULL); framework->dispatcher.eventQueue = malloc(sizeof(celix_framework_event_t) * framework->dispatcher.eventQueueCap); framework->dispatcher.dynamicEventQueue = celix_arrayList_create(); + framework->dispatcher.scheduledEventQueue = celix_arrayList_create(); //create and store framework uuid char uuid[37]; @@ -293,6 +293,11 @@ celix_status_t framework_create(framework_pt *out, celix_properties_t* config) { } celix_status_t framework_destroy(framework_pt framework) { + if (framework == NULL) { + //nop + return CELIX_SUCCESS; + } + celix_status_t status = CELIX_SUCCESS; celixThreadMutex_lock(&framework->shutdown.mutex); @@ -376,6 +381,9 @@ celix_status_t framework_destroy(framework_pt framework) { assert(celix_arrayList_size(framework->dispatcher.dynamicEventQueue) == 0); celix_arrayList_destroy(framework->dispatcher.dynamicEventQueue); + assert(celix_arrayList_size(framework->dispatcher.scheduledEventQueue) == 0); + celix_arrayList_destroy(framework->dispatcher.scheduledEventQueue); + celix_bundleCache_destroy(framework->cache); celixThreadCondition_destroy(&framework->dispatcher.cond); @@ -1388,6 +1396,7 @@ static inline bool fw_removeTopEventFromQueue(celix_framework_t* fw) { celix_arrayList_removeAt(fw->dispatcher.dynamicEventQueue, 0); dynamicallyAllocated = true; } + celixThreadCondition_broadcast(&fw->dispatcher.cond); //notify that the queue size is changed celixThreadMutex_unlock(&fw->dispatcher.mutex); return dynamicallyAllocated; } @@ -1396,32 +1405,128 @@ static inline bool fw_removeTopEventFromQueue(celix_framework_t* fw) { static inline void fw_handleEvents(celix_framework_t* framework) { celixThreadMutex_lock(&framework->dispatcher.mutex); int size = framework->dispatcher.eventQueueSize + celix_arrayList_size(framework->dispatcher.dynamicEventQueue); - if (size == 0 && framework->dispatcher.active) { - celixThreadCondition_timedwaitRelative(&framework->dispatcher.cond, &framework->dispatcher.mutex, 1, 0); - } - size = framework->dispatcher.eventQueueSize + celix_arrayList_size(framework->dispatcher.dynamicEventQueue); celixThreadMutex_unlock(&framework->dispatcher.mutex); while (size > 0) { celix_framework_event_t* topEvent = fw_topEventFromQueue(framework); fw_handleEventRequest(framework, topEvent); - bool dynamiclyAllocatedEvent = fw_removeTopEventFromQueue(framework); + bool dynamicallyAllocatedEvent = fw_removeTopEventFromQueue(framework); if (topEvent->bndEntry != NULL) { celix_framework_bundleEntry_decreaseUseCount(topEvent->bndEntry); } free(topEvent->serviceName); - if (dynamiclyAllocatedEvent) { + if (dynamicallyAllocatedEvent) { free(topEvent); } celixThreadMutex_lock(&framework->dispatcher.mutex); size = framework->dispatcher.eventQueueSize + celix_arrayList_size(framework->dispatcher.dynamicEventQueue); - celixThreadCondition_broadcast(&framework->dispatcher.cond); celixThreadMutex_unlock(&framework->dispatcher.mutex); } } +/** + * @brief Process all scheduled events. + */ +static double celix_framework_processScheduledEvents(celix_framework_t* fw) { + double nextScheduledEvent = 0.0; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + celix_scheduled_event_t* callEvent; + do { + callEvent = NULL; + double nextEvent; + celixThreadMutex_lock(&fw->dispatcher.mutex); + for (int i = 0; i< celix_arrayList_size(fw->dispatcher.scheduledEventQueue); ++i) { + celix_scheduled_event_t* event = celix_arrayList_get(fw->dispatcher.scheduledEventQueue, i); + bool call = celix_scheduledEvent_deadlineReached(event, &ts, &nextEvent); + if (nextScheduledEvent == 0.0 || nextEvent < nextScheduledEvent) { + nextScheduledEvent = nextEvent; + } + if (call) { + callEvent = event; + break; + } + } + celixThreadMutex_unlock(&fw->dispatcher.mutex); + + if (callEvent != NULL) { + celix_scheduledEvent_process(callEvent, &ts); + if (celix_scheduleEvent_isDone(callEvent)) { + 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)", + callEvent->eventName, + callEvent->scheduledEventId, + celix_bundle_getSymbolicName(callEvent->bndEntry->bnd), + callEvent->bndEntry->bndId); + celix_arrayList_remove(fw->dispatcher.scheduledEventQueue, callEvent); + celixThreadMutex_unlock(&fw->dispatcher.mutex); + celix_scheduledEvent_destroy(callEvent); + } + } + } while (callEvent != NULL); + + return nextScheduledEvent; +} + +void celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId) { + celix_scheduled_event_t* removedEvent; + do { + removedEvent = NULL; + celixThreadMutex_lock(&fw->dispatcher.mutex); + for (int i = 0; i< celix_arrayList_size(fw->dispatcher.scheduledEventQueue); ++i) { + celix_scheduled_event_t* visit = celix_arrayList_get(fw->dispatcher.scheduledEventQueue, i); + if (visit->bndEntry->bndId == bndId) { + removedEvent = visit; + celix_arrayList_removeAt(fw->dispatcher.scheduledEventQueue, i); + break; + } + } + celixThreadMutex_unlock(&fw->dispatcher.mutex); + + if (removedEvent) { + if (removedEvent->intervalInSeconds > 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.", + removedEvent->eventName, + removedEvent->scheduledEventId, + celix_bundle_getSymbolicName(removedEvent->bndEntry->bnd), + removedEvent->bndEntry->bndId); + } else { + fw_log(fw->logger, + CELIX_LOG_LEVEL_DEBUG, + "Removing unprocessed one-shot scheduled event '%s' (id=%li) for bundle '%s' (id=%li)", + removedEvent->eventName, + removedEvent->scheduledEventId, + celix_bundle_getSymbolicName(removedEvent->bndEntry->bnd), + removedEvent->bndEntry->bndId); + } + celix_scheduledEvent_waitAndDestroy(removedEvent); + } + } while (removedEvent != NULL); +} + +static void celix_framework_waitForNextEvent(celix_framework_t* fw, double nextScheduledEvent) { + long seconds = CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS; + long nanoseconds = 0; + if (nextScheduledEvent > 0) { + seconds = (long) nextScheduledEvent; + nanoseconds = ((long)(nextScheduledEvent * 1000000000L)) - seconds; + } + + celixThreadMutex_lock(&fw->dispatcher.mutex); + int size = fw->dispatcher.eventQueueSize + celix_arrayList_size(fw->dispatcher.dynamicEventQueue); + if (size == 0 && fw->dispatcher.active) { + celixThreadCondition_timedwaitRelative(&fw->dispatcher.cond, &fw->dispatcher.mutex, seconds, nanoseconds); + } + celixThreadMutex_unlock(&fw->dispatcher.mutex); +} + static void *fw_eventDispatcher(void *fw) { framework_pt framework = (framework_pt) fw; @@ -1431,6 +1536,9 @@ static void *fw_eventDispatcher(void *fw) { while (active) { fw_handleEvents(framework); + double nextScheduledEvent = celix_framework_processScheduledEvents(framework); + celix_framework_waitForNextEvent(framework, nextScheduledEvent); + celixThreadMutex_lock(&framework->dispatcher.mutex); active = framework->dispatcher.active; celixThreadMutex_unlock(&framework->dispatcher.mutex); @@ -1811,7 +1919,6 @@ bundle_pt framework_getBundleById(framework_pt framework, long id) { return bnd; } - bool celix_framework_isBundleInstalled(celix_framework_t *fw, long bndId) { bool isInstalled = false; celix_framework_bundle_entry_t *entry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId); @@ -2350,14 +2457,31 @@ celix_array_list_t* celix_framework_listInstalledBundles(celix_framework_t* fram return celix_framework_listBundlesInternal(framework, false); } -void celix_framework_waitForEmptyEventQueue(celix_framework_t *fw) { +celix_status_t celix_framework_timedWaitForEmptyEventQueue(celix_framework_t *fw, double periodInSeconds) { assert(!celix_framework_isCurrentThreadTheEventLoop(fw)); + celix_status_t status = CELIX_SUCCESS; + long seconds = (long) periodInSeconds; + long nanoseconds = (long) ((periodInSeconds - (double)seconds) * 1000000000L); + celixThreadMutex_lock(&fw->dispatcher.mutex); while (fw->dispatcher.eventQueueSize > 0 || celix_arrayList_size(fw->dispatcher.dynamicEventQueue) > 0) { - celixThreadCondition_wait(&fw->dispatcher.cond, &fw->dispatcher.mutex); + if (periodInSeconds > 0) { + status = celixThreadCondition_timedwaitRelative(&fw->dispatcher.cond, &fw->dispatcher.mutex, seconds, nanoseconds); + if (status != CELIX_SUCCESS) { + status = CELIX_TIMEOUT; + break; + } + } else { + celixThreadCondition_wait(&fw->dispatcher.cond, &fw->dispatcher.mutex); + } } celixThreadMutex_unlock(&fw->dispatcher.mutex); + return status; +} + +void celix_framework_waitForEmptyEventQueue(celix_framework_t *fw) { + celix_framework_waitUntilNoEventsForBnd(fw, 0); } void celix_framework_waitUntilNoEventsForBnd(celix_framework_t* fw, long bndId) { @@ -2399,6 +2523,96 @@ 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) { + fw_log(fw->logger, + CELIX_LOG_LEVEL_ERROR, + "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); + return -1; + } + + celix_scheduled_event_t* event = celix_scheduledEvent_create(fw->logger, + bndEntry, + celix_framework_nextScheduledEventId(fw), + eventName, + initialDelayInSeconds, + intervalInSeconds, + eventData, + eventCallback); + + if (event == NULL) { + return -1; //error logged by celix_scheduledEvent_create + } + + celixThreadMutex_lock(&fw->dispatcher.mutex); + celix_status_t addStatus = celix_arrayList_add(fw->dispatcher.scheduledEventQueue, event); + celixThreadCondition_broadcast(&fw->dispatcher.cond); //notify dispatcher thread for newly added scheduled event + celixThreadMutex_unlock(&fw->dispatcher.mutex); + + if (addStatus != CELIX_SUCCESS) { + fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event for bundle id %li. Out of memory", bndId); + celix_scheduledEvent_destroy(event); + return -1; + } + + fw_log(fw->logger, + CELIX_LOG_LEVEL_DEBUG, + "Added scheduled event '%s' (id=%li) for bundle '%s' (id=%li)", + event->eventName, + event->scheduledEventId, + celix_bundle_getSymbolicName(bndEntry->bnd), + bndId); + + return event->scheduledEventId; +} + +bool celix_framework_removeScheduledEvent(celix_framework_t* fw, long scheduledEventId) { + if (scheduledEventId < 0) { + return false; //silently ignore + } + + celix_scheduled_event_t* event = NULL; + celixThreadMutex_lock(&fw->dispatcher.mutex); + for (int i = 0; i < celix_arrayList_size(fw->dispatcher.scheduledEventQueue); ++i) { + celix_scheduled_event_t* visit = celix_arrayList_get(fw->dispatcher.scheduledEventQueue, i); + if (visit->scheduledEventId == scheduledEventId) { + event = visit; + celix_arrayList_removeAt(fw->dispatcher.scheduledEventQueue, i); + break; + } + } + 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", + scheduledEventId); + return false; + } + + fw_log(fw->logger, + CELIX_LOG_LEVEL_DEBUG, + "Removing scheduled event '%s' (id=%li) for bundle '%s' (id=%li)", + event->eventName, + event->scheduledEventId, + celix_bundle_getSymbolicName(event->bndEntry->bnd), + event->bndEntry->bndId); + celix_scheduledEvent_waitAndDestroy(event); + return true; +} + void celix_framework_setLogCallback(celix_framework_t* fw, void* logHandle, void (*logFunction)(void* handle, celix_log_level_e level, const char* file, const char *function, int line, const char *format, va_list formatArgs)) { celix_frameworkLogger_setLogCallback(fw->logger, logHandle, logFunction); } @@ -2434,7 +2648,11 @@ long celix_framework_fireGenericEvent(framework_t* fw, long eventId, long bndId, } long celix_framework_nextEventId(framework_t *fw) { - return __atomic_fetch_add(&fw->nextGenericEventId, 1, __ATOMIC_RELAXED); + return __atomic_fetch_add(&fw->dispatcher.nextEventId, 1, __ATOMIC_RELAXED); +} + +long celix_framework_nextScheduledEventId(framework_t *fw) { + return __atomic_fetch_add(&fw->dispatcher.nextScheduledEventId, 1, __ATOMIC_RELAXED); } void celix_framework_waitForGenericEvent(framework_t *fw, long eventId) { @@ -2478,3 +2696,7 @@ void celix_framework_waitForStop(celix_framework_t *framework) { celixThreadMutex_unlock(&framework->shutdown.mutex); } + +celix_framework_logger_t* celix_framework_getLogger(celix_framework_t* fw) { + return fw->logger; +} diff --git a/libs/framework/src/framework_private.h b/libs/framework/src/framework_private.h index 8d60d4c7..04b4b700 100644 --- a/libs/framework/src/framework_private.h +++ b/libs/framework/src/framework_private.h @@ -20,9 +20,10 @@ #ifndef FRAMEWORK_PRIVATE_H_ #define FRAMEWORK_PRIVATE_H_ +#include <stdbool.h> + #include "celix_framework.h" #include "framework.h" - #include "manifest.h" #include "wire.h" #include "hash_map.h" @@ -37,15 +38,15 @@ #include "bundle_context.h" #include "celix_bundle_cache.h" #include "celix_log.h" - #include "celix_threads.h" #include "service_registry.h" -#include <stdbool.h> #ifndef CELIX_FRAMEWORK_DEFAULT_STATIC_EVENT_QUEUE_SIZE #define CELIX_FRAMEWORK_DEFAULT_STATIC_EVENT_QUEUE_SIZE 1024 #endif +#define CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS 1 + #define CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE_DEFAULT false #define CELIX_FRAMEWORK_CACHE_USE_TMP_DIR_DEFAULT false #define CELIX_FRAMEWORK_FRAMEWORK_CACHE_DIR_DEFAULT ".cache" @@ -158,10 +159,15 @@ struct celix_framework { struct { + long nextEventId; //atomic + long nextScheduledEventId; //atomic + celix_thread_cond_t cond; celix_thread_t thread; celix_thread_mutex_t mutex; //protects below bool active; + + //normal event queue celix_framework_event_t* eventQueue; //ring buffer int eventQueueCap; int eventQueueSize; @@ -174,12 +180,11 @@ struct celix_framework { int nbUnregister; // number of pending async de-registration int nbEvent; // number of pending generic events } stats; + celix_array_list_t *scheduledEventQueue; //entry = celix_framework_scheduled_event_t*. Used for scheduled events } dispatcher; celix_framework_logger_t* logger; - long nextGenericEventId; - struct { celix_thread_cond_t cond; celix_thread_mutex_t mutex; //protects below @@ -434,4 +439,57 @@ celix_status_t celix_framework_uninstallBundleEntry(celix_framework_t* fw, celix */ celix_status_t celix_framework_updateBundleEntry(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry, const char* updatedBundleUrl); + +/** @brief Return the next scheduled event id. + * @param[in] fw The Celix framework + * @return The next scheduled event id. + */ +long celix_framework_nextScheduledEventId(framework_t *fw); + +/** + * @brief Add a scheduled event to the Celix framework. + * + * + * @param[in] fw The Celix framework + * @param[in] bndId The bundle id to add the scheduled event for. If < 0 the framework bundle is used. + * @param[in] eventName The event name to use for the scheduled event. If NULL, a default event name is used. + * @param[in] initialDelayInSeconds The initial delay in seconds before the first event callback is called. + * @param[in] intervalInSeconds The interval in seconds between event callbacks. + * @param[in] eventData The event data to pass to the event callback. + * @param[in] eventCallback The event callback to call when the scheduled event is triggered. + * @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)); + +/** + * @brief Cancel a scheduled event. + * + * When this function returns, no more scheduled event callbacks will be called. + * + * @param[in] fw The Celix framework + * @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); + +/** + * Remove all scheduled events for the provided bundle id and logs warning if there are still un-removed scheduled + * events that are not a one time event. + * @param[in] fw The Celix framework. + * @param[in] bndId The bundle id to remove the scheduled events for. + */ +void celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId); + +/** + * @brief Return the framework logger. Note logger lifetime is the same as the framework. + */ +celix_framework_logger_t* celix_framework_getLogger(celix_framework_t* fw); + #endif /* FRAMEWORK_PRIVATE_H_ */ diff --git a/libs/utils/include/celix_errno.h b/libs/utils/include/celix_errno.h index 68250016..a11d44a4 100644 --- a/libs/utils/include/celix_errno.h +++ b/libs/utils/include/celix_errno.h @@ -190,6 +190,8 @@ CELIX_UTILS_EXPORT bool celix_utils_isCustomerStatusCode(celix_status_t code); #define CELIX_FRAMEWORK_EXCEPTION CELIX_ERROR_MAKE(CELIX_FACILITY_FRAMEWORK, 4471) #define CELIX_FILE_IO_EXCEPTION CELIX_ERROR_MAKE(CELIX_FACILITY_FRAMEWORK, 4472) #define CELIX_SERVICE_EXCEPTION CELIX_ERROR_MAKE(CELIX_FACILITY_FRAMEWORK, 4473) +#define CELIX_TIMEOUT CELIX_ERROR_MAKE(CELIX_FACILITY_FRAMEWORK, 4474) + /*! * Exception indicating a problem with a interceptor */ diff --git a/libs/utils/src/celix_threads.c b/libs/utils/src/celix_threads.c index 088f216e..bd56f3d2 100644 --- a/libs/utils/src/celix_threads.c +++ b/libs/utils/src/celix_threads.c @@ -186,7 +186,7 @@ celix_status_t celixThreadCondition_timedwaitRelative(celix_thread_cond_t *cond, seconds = seconds >= 0 ? seconds : 0; time = celix_gettime(CLOCK_MONOTONIC); time.tv_sec += seconds; - if(nanoseconds > 0) { + if (nanoseconds > 0) { time.tv_nsec += nanoseconds; while (time.tv_nsec > CELIX_NS_IN_SEC) { time.tv_sec++;
