PengZheng commented on code in PR #583: URL: https://github.com/apache/celix/pull/583#discussion_r1249269791
########## libs/framework/src/celix_scheduled_event.c: ########## @@ -0,0 +1,312 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "celix_scheduled_event.h" + +#include <assert.h> + +#include "celix_constants.h" +#include "celix_utils.h" + +/*! + * @brief Allow error in seconds for the interval. This ensure pthread cond wakeups result in a call even if + * the exact wakeupt time is a bit off. + */ +#define CELIX_SCHEDULED_EVENT_INTERVAL_ALLOW_ERROR_IN_SECONDS 0.000001 + +/** + * @brief Default name for a scheduled event. + */ +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 initial delay and/or interval. + * 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_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 */ + 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 logTimeoutInSeconds; /**< The timeout, in seconds, before a log message is printed while waiting for a + scheduled event to be processed or removed. */ + 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 and + isRemoved. */ + size_t useCount; /**< The use count of the scheduled event. */ + size_t callCount; /**< The call count of the scheduled event. */ + bool isMarkedForRemoval; /**< Whether the scheduled event is marked for removal. */ + bool isRemoved; /**< Whether the scheduled event is removed. */ + struct timespec lastScheduledEventTime; /**< The last scheduled event time of the scheduled event. */ + bool processForWakeup; /**< Whether the scheduled event should be processed directly due to a wakeupScheduledEvent + call. */ +}; + +celix_scheduled_event_t* celix_scheduledEvent_create(celix_framework_t* fw, + long bndId, + long scheduledEventId, + const char* providedEventName, + double initialDelayInSeconds, + double intervalInSeconds, + 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); + if (event == NULL || eventName == NULL) { + fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event for bundle id %li. Out of memory", bndId); + free(event); + if (eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) { + free(eventName); + } + return NULL; + } + + event->scheduledEventId = scheduledEventId; + event->logger = fw->logger; + event->bndId = bndId; + + event->eventName = eventName; + event->logTimeoutInSeconds = + celix_framework_getConfigPropertyAsDouble(fw, + CELIX_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS, + CELIX_DEFAULT_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS, + NULL); + event->initialDelayInSeconds = initialDelayInSeconds; + event->intervalInSeconds = intervalInSeconds; + event->callbackData = callbackData; + event->callback = callback; + event->removedCallbackData = removedCallbackData; + event->removedCallback = removedCallback; + event->isMarkedForRemoval = false; + event->useCount = 1; + event->callCount = 0; + event->isRemoved = false; + event->lastScheduledEventTime = celixThreadCondition_getTime(); + event->processForWakeup = false; + + celixThreadMutex_create(&event->mutex, NULL); + celixThreadCondition_init(&event->cond, NULL); + + return event; +} + +static void celix_scheduledEvent_destroy(celix_scheduled_event_t* event) { + celixThreadMutex_destroy(&event->mutex); + celixThreadCondition_destroy(&event->cond); + if (event->eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) { + free(event->eventName); + } + free(event); +} + +celix_scheduled_event_t* celix_scheduledEvent_retain(celix_scheduled_event_t* event) { + if (event == NULL) { + return NULL; + } + + celixThreadMutex_lock(&event->mutex); + event->useCount += 1; + celixThreadMutex_unlock(&event->mutex); + return event; +} + +void celix_scheduledEvent_release(celix_scheduled_event_t* event) { + if (event == NULL) { + return; + } + + celixThreadMutex_lock(&event->mutex); + event->useCount -= 1; + bool unused = event->useCount == 0; + celixThreadMutex_unlock(&event->mutex); + + if (unused) { + celix_scheduledEvent_destroy(event); + } +} + +void celix_ScheduledEvent_cleanup(celix_scheduled_event_t** event) { + if (event) { + celix_scheduledEvent_release(*event); + } +} + +const char* celix_scheduledEvent_getName(const celix_scheduled_event_t* event) { return event->eventName; } + +long celix_scheduledEvent_getId(const celix_scheduled_event_t* event) { return event->scheduledEventId; } + +long celix_scheduledEvent_getBundleId(const celix_scheduled_event_t* event) { return event->bndId; } + +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 (event->processForWakeup) { + deadlineReached = true; + } + + if (deadlineReached && nextProcessTimeInSeconds) { + *nextProcessTimeInSeconds = + event->intervalInSeconds == 0 /*one shot*/ ? CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS + : event->intervalInSeconds; + } else if (nextProcessTimeInSeconds) { + *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) { + fw_log(event->logger, + CELIX_LOG_LEVEL_TRACE, + "Processing scheduled event %s for bundle id %li", + event->eventName, + event->bndId); + assert(event->callback != NULL); + + struct timespec start = celix_gettime(CLOCK_MONOTONIC); + event->callback(event->callbackData); // note called outside of lock + struct timespec end = celix_gettime(CLOCK_MONOTONIC); + + celixThreadMutex_lock(&event->mutex); + event->lastScheduledEventTime = *currentTime; + event->callCount += 1; + event->processForWakeup = false; + celixThreadCondition_broadcast(&event->cond); // for changed callCount + celixThreadMutex_unlock(&event->mutex); + + if (celix_difftime(&start, &end) > event->logTimeoutInSeconds) { + fw_log(event->logger, + CELIX_LOG_LEVEL_WARNING, + "Processing scheduled event %s for bundle id %li took %f seconds", + event->eventName, + event->bndId, + celix_difftime(&start, &end)); + } +} + +bool celix_scheduledEvent_isSingleShot(const celix_scheduled_event_t* event) { + bool isDone = false; + isDone = event->intervalInSeconds == 0; + return isDone; +} + +size_t celix_scheduledEvent_markForWakeup(celix_scheduled_event_t* event) { + celixThreadMutex_lock(&event->mutex); + event->processForWakeup = true; + size_t currentCallCount = event->callCount; + celixThreadMutex_unlock(&event->mutex); + + fw_log(event->logger, + CELIX_LOG_LEVEL_DEBUG, + "Wakeup scheduled event '%s' (id=%li) for bundle id %li", + event->eventName, + event->scheduledEventId, + event->bndId); + + return currentCallCount + 1; +} + +void celix_scheduledEvent_waitForRemoved(celix_scheduled_event_t* event) { + struct timespec absLogTimeout = + celixThreadCondition_getDelayedTime(event->logTimeoutInSeconds); + celixThreadMutex_lock(&event->mutex); + while (!event->isRemoved) { + celix_status_t waitStatus = celixThreadCondition_waitUntil(&event->cond, &event->mutex, &absLogTimeout); + if (waitStatus == ETIMEDOUT) { + fw_log(event->logger, + CELIX_LOG_LEVEL_WARNING, + "Timeout while waiting for removal of scheduled event '%s' (id=%li) for bundle id %li.", + event->eventName, + event->scheduledEventId, + event->bndId); + absLogTimeout = celixThreadCondition_getDelayedTime(event->logTimeoutInSeconds); + } + } + celixThreadMutex_unlock(&event->mutex); +} + +celix_status_t celix_scheduledEvent_wait(celix_scheduled_event_t* event, double timeoutInSeconds) { + celix_status_t status = CELIX_SUCCESS; + celixThreadMutex_lock(&event->mutex); + size_t targetCallCount = event->callCount + 1; + struct timespec absTimeoutTime = celixThreadCondition_getDelayedTime(timeoutInSeconds); + while (event->callCount < targetCallCount && !event->isRemoved) { + status = celixThreadCondition_waitUntil(&event->cond, &event->mutex, &absTimeoutTime); + if (status == ETIMEDOUT) { + break; + } + } + celixThreadMutex_unlock(&event->mutex); + return status; Review Comment: `EINTR` could be returned even if the `wait` succeeds. ########## libs/framework/src/framework.c: ########## @@ -2440,6 +2584,127 @@ void celix_framework_waitUntilNoPendingRegistration(celix_framework_t* fw) celixThreadMutex_unlock(&fw->dispatcher.mutex); } +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.", + 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* newEvent = celix_scheduledEvent_create(fw, + bndEntry->bndId, + celix_framework_nextScheduledEventId(fw), + eventName, + initialDelayInSeconds, + intervalInSeconds, + callbackData, + callback, + removeCallbackData, + removeCallback); + CELIX_SCHEDULED_EVENT_RETAIN_GUARD(event, newEvent); + celix_framework_bundleEntry_decreaseUseCount(bndEntry); + + if (event == NULL) { + return -1L; //error logged by celix_scheduledEvent_create + } + + fw_log(fw->logger, + CELIX_LOG_LEVEL_DEBUG, + "Added scheduled event '%s' (id=%li) for bundle '%s' (id=%li).", + celix_scheduledEvent_getName(event), + celix_scheduledEvent_getId(event), + celix_bundle_getSymbolicName(bndEntry->bnd), Review Comment: Use-after-free? ########## libs/framework/src/framework.c: ########## @@ -2440,6 +2584,127 @@ void celix_framework_waitUntilNoPendingRegistration(celix_framework_t* fw) celixThreadMutex_unlock(&fw->dispatcher.mutex); } +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.", + 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* newEvent = celix_scheduledEvent_create(fw, + bndEntry->bndId, + celix_framework_nextScheduledEventId(fw), + eventName, + initialDelayInSeconds, + intervalInSeconds, + callbackData, + callback, + removeCallbackData, + removeCallback); + CELIX_SCHEDULED_EVENT_RETAIN_GUARD(event, newEvent); Review Comment: This unnecessarily increases `useCount` to 2. ########## libs/framework/src/framework.c: ########## @@ -1408,32 +1412,151 @@ 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); Review Comment: What if a new event is enqueued each time the event callback is called? Will the following `while` loop become a dead loop? ########## libs/framework/src/celix_scheduled_event.c: ########## @@ -0,0 +1,312 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "celix_scheduled_event.h" + +#include <assert.h> + +#include "celix_constants.h" +#include "celix_utils.h" + +/*! + * @brief Allow error in seconds for the interval. This ensure pthread cond wakeups result in a call even if + * the exact wakeupt time is a bit off. + */ +#define CELIX_SCHEDULED_EVENT_INTERVAL_ALLOW_ERROR_IN_SECONDS 0.000001 + +/** + * @brief Default name for a scheduled event. + */ +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 initial delay and/or interval. + * 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_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 */ + 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 logTimeoutInSeconds; /**< The timeout, in seconds, before a log message is printed while waiting for a + scheduled event to be processed or removed. */ + 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 and + isRemoved. */ + size_t useCount; /**< The use count of the scheduled event. */ + size_t callCount; /**< The call count of the scheduled event. */ + bool isMarkedForRemoval; /**< Whether the scheduled event is marked for removal. */ + bool isRemoved; /**< Whether the scheduled event is removed. */ + struct timespec lastScheduledEventTime; /**< The last scheduled event time of the scheduled event. */ + bool processForWakeup; /**< Whether the scheduled event should be processed directly due to a wakeupScheduledEvent + call. */ +}; + +celix_scheduled_event_t* celix_scheduledEvent_create(celix_framework_t* fw, + long bndId, + long scheduledEventId, + const char* providedEventName, + double initialDelayInSeconds, + double intervalInSeconds, + 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); + if (event == NULL || eventName == NULL) { + fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event for bundle id %li. Out of memory", bndId); + free(event); + if (eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) { + free(eventName); + } + return NULL; + } + + event->scheduledEventId = scheduledEventId; + event->logger = fw->logger; + event->bndId = bndId; + + event->eventName = eventName; + event->logTimeoutInSeconds = + celix_framework_getConfigPropertyAsDouble(fw, + CELIX_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS, + CELIX_DEFAULT_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS, + NULL); + event->initialDelayInSeconds = initialDelayInSeconds; + event->intervalInSeconds = intervalInSeconds; + event->callbackData = callbackData; + event->callback = callback; + event->removedCallbackData = removedCallbackData; + event->removedCallback = removedCallback; + event->isMarkedForRemoval = false; + event->useCount = 1; + event->callCount = 0; + event->isRemoved = false; + event->lastScheduledEventTime = celixThreadCondition_getTime(); + event->processForWakeup = false; + + celixThreadMutex_create(&event->mutex, NULL); + celixThreadCondition_init(&event->cond, NULL); + + return event; +} + +static void celix_scheduledEvent_destroy(celix_scheduled_event_t* event) { + celixThreadMutex_destroy(&event->mutex); + celixThreadCondition_destroy(&event->cond); + if (event->eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) { + free(event->eventName); + } + free(event); +} + +celix_scheduled_event_t* celix_scheduledEvent_retain(celix_scheduled_event_t* event) { + if (event == NULL) { + return NULL; + } + + celixThreadMutex_lock(&event->mutex); + event->useCount += 1; + celixThreadMutex_unlock(&event->mutex); + return event; +} + +void celix_scheduledEvent_release(celix_scheduled_event_t* event) { + if (event == NULL) { + return; + } + + celixThreadMutex_lock(&event->mutex); + event->useCount -= 1; + bool unused = event->useCount == 0; + celixThreadMutex_unlock(&event->mutex); + + if (unused) { + celix_scheduledEvent_destroy(event); + } +} + +void celix_ScheduledEvent_cleanup(celix_scheduled_event_t** event) { + if (event) { + celix_scheduledEvent_release(*event); + } +} + +const char* celix_scheduledEvent_getName(const celix_scheduled_event_t* event) { return event->eventName; } + +long celix_scheduledEvent_getId(const celix_scheduled_event_t* event) { return event->scheduledEventId; } + +long celix_scheduledEvent_getBundleId(const celix_scheduled_event_t* event) { return event->bndId; } + +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 (event->processForWakeup) { + deadlineReached = true; + } + + if (deadlineReached && nextProcessTimeInSeconds) { + *nextProcessTimeInSeconds = + event->intervalInSeconds == 0 /*one shot*/ ? CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS + : event->intervalInSeconds; + } else if (nextProcessTimeInSeconds) { + *nextProcessTimeInSeconds = event->callCount == 0 ? event->initialDelayInSeconds : event->intervalInSeconds; Review Comment: This does not take elapsed time into account, and will lead to delay of event delivery. ########## libs/framework/src/celix_scheduled_event.c: ########## @@ -0,0 +1,312 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "celix_scheduled_event.h" + +#include <assert.h> + +#include "celix_constants.h" +#include "celix_utils.h" + +/*! + * @brief Allow error in seconds for the interval. This ensure pthread cond wakeups result in a call even if + * the exact wakeupt time is a bit off. + */ +#define CELIX_SCHEDULED_EVENT_INTERVAL_ALLOW_ERROR_IN_SECONDS 0.000001 + +/** + * @brief Default name for a scheduled event. + */ +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 initial delay and/or interval. + * 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_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 */ + 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 logTimeoutInSeconds; /**< The timeout, in seconds, before a log message is printed while waiting for a + scheduled event to be processed or removed. */ + 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 and + isRemoved. */ + size_t useCount; /**< The use count of the scheduled event. */ + size_t callCount; /**< The call count of the scheduled event. */ + bool isMarkedForRemoval; /**< Whether the scheduled event is marked for removal. */ + bool isRemoved; /**< Whether the scheduled event is removed. */ + struct timespec lastScheduledEventTime; /**< The last scheduled event time of the scheduled event. */ + bool processForWakeup; /**< Whether the scheduled event should be processed directly due to a wakeupScheduledEvent + call. */ +}; + +celix_scheduled_event_t* celix_scheduledEvent_create(celix_framework_t* fw, + long bndId, + long scheduledEventId, + const char* providedEventName, + double initialDelayInSeconds, + double intervalInSeconds, + 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); + if (event == NULL || eventName == NULL) { + fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Cannot add scheduled event for bundle id %li. Out of memory", bndId); + free(event); + if (eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) { + free(eventName); + } + return NULL; + } + + event->scheduledEventId = scheduledEventId; + event->logger = fw->logger; + event->bndId = bndId; + + event->eventName = eventName; + event->logTimeoutInSeconds = + celix_framework_getConfigPropertyAsDouble(fw, + CELIX_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS, + CELIX_DEFAULT_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS, + NULL); + event->initialDelayInSeconds = initialDelayInSeconds; + event->intervalInSeconds = intervalInSeconds; + event->callbackData = callbackData; + event->callback = callback; + event->removedCallbackData = removedCallbackData; + event->removedCallback = removedCallback; + event->isMarkedForRemoval = false; + event->useCount = 1; + event->callCount = 0; + event->isRemoved = false; + event->lastScheduledEventTime = celixThreadCondition_getTime(); + event->processForWakeup = false; + + celixThreadMutex_create(&event->mutex, NULL); + celixThreadCondition_init(&event->cond, NULL); + + return event; +} + +static void celix_scheduledEvent_destroy(celix_scheduled_event_t* event) { + celixThreadMutex_destroy(&event->mutex); + celixThreadCondition_destroy(&event->cond); + if (event->eventName != CELIX_SCHEDULED_EVENT_DEFAULT_NAME) { + free(event->eventName); + } + free(event); +} + +celix_scheduled_event_t* celix_scheduledEvent_retain(celix_scheduled_event_t* event) { + if (event == NULL) { + return NULL; + } + + celixThreadMutex_lock(&event->mutex); + event->useCount += 1; + celixThreadMutex_unlock(&event->mutex); + return event; +} + +void celix_scheduledEvent_release(celix_scheduled_event_t* event) { + if (event == NULL) { + return; + } + + celixThreadMutex_lock(&event->mutex); + event->useCount -= 1; + bool unused = event->useCount == 0; + celixThreadMutex_unlock(&event->mutex); + + if (unused) { + celix_scheduledEvent_destroy(event); + } +} + +void celix_ScheduledEvent_cleanup(celix_scheduled_event_t** event) { + if (event) { + celix_scheduledEvent_release(*event); + } +} + +const char* celix_scheduledEvent_getName(const celix_scheduled_event_t* event) { return event->eventName; } + +long celix_scheduledEvent_getId(const celix_scheduled_event_t* event) { return event->scheduledEventId; } + +long celix_scheduledEvent_getBundleId(const celix_scheduled_event_t* event) { return event->bndId; } + +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); Review Comment: Instead of `lastScheduleEventTime`, we can record the next deadline in its absolute form so that elapsed time is always dealt with. `struct timespec* nextDeadline` instead of `nextProcessTimeInSeconds` is returned. ########## libs/framework/src/framework.c: ########## @@ -1408,32 +1412,151 @@ 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) { + struct timespec ts = celixThreadCondition_getTime(); Review Comment: This does not take event processing delay into account. Ideally, the current time is updated after each event callbacks. On the other hand, if we do update current time after each event callbacks, a long running event may expire other event timers, whose event callbacks may expire the long running event. For the current implementation, this means that we will always have a event to process (and never return from `celix_framework_processScheduledEvents`). ########## libs/framework/src/framework.c: ########## @@ -1443,17 +1566,23 @@ 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); } Review Comment: When not active, we are guaranteed that all bundles have been unloaded, and that the framework has been stopped. I can understand why we need one extra run to handle remaining events, but I don't understand why more than one runs are needed? If more than one runs are really needed, there seems to be some bugs lurking. ########## libs/framework/src/framework.c: ########## @@ -1408,32 +1412,151 @@ 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) { + struct timespec ts = celixThreadCondition_getTime(); + + double nextClosestScheduledEvent; + celix_scheduled_event_t* callEvent; + celix_scheduled_event_t* removeEvent; + do { + nextClosestScheduledEvent = -1; //negative means no event next event + callEvent = NULL; + removeEvent = NULL; + double nextEvent; + celixThreadMutex_lock(&fw->dispatcher.mutex); + CELIX_LONG_HASH_MAP_ITERATE(fw->dispatcher.scheduledEvents, entry) { + celix_scheduled_event_t* visit = entry.value.ptrValue; + if (celix_scheduledEvent_isMarkedForRemoval(visit)) { + removeEvent = visit; + celix_longHashMap_remove(fw->dispatcher.scheduledEvents, celix_scheduledEvent_getId(visit)); + break; + } + + bool call = celix_scheduledEvent_deadlineReached(visit, &ts, &nextEvent); + if (nextClosestScheduledEvent < 0 || nextEvent < nextClosestScheduledEvent) { + nextClosestScheduledEvent = nextEvent; + } + if (call) { + callEvent = visit; + if (celix_scheduledEvent_isSingleShot(visit)) { + removeEvent = visit; + celix_longHashMap_remove(fw->dispatcher.scheduledEvents, celix_scheduledEvent_getId(visit)); + } + break; + } + } + celixThreadMutex_unlock(&fw->dispatcher.mutex); + + if (callEvent != NULL) { + celix_scheduledEvent_process(callEvent, &ts); + } + if (removeEvent != NULL) { + fw_log(fw->logger, + CELIX_LOG_LEVEL_DEBUG, + "Removing processed %s""scheduled event '%s' (id=%li) for bundle if %li.", + celix_scheduledEvent_isSingleShot(removeEvent) ? "one-shot " : "", + celix_scheduledEvent_getName(removeEvent), + celix_scheduledEvent_getId(removeEvent), + celix_scheduledEvent_getBundleId(removeEvent)); + celix_scheduledEvent_setRemoved(removeEvent); + celix_scheduledEvent_release(removeEvent); + } + } while (callEvent || removeEvent); + + return nextClosestScheduledEvent; +} + +void celix_framework_cleanupScheduledEvents(celix_framework_t* fw, long bndId) { + celix_scheduled_event_t* removeEvent; + do { + removeEvent = NULL; + celixThreadMutex_lock(&fw->dispatcher.mutex); + CELIX_LONG_HASH_MAP_ITERATE(fw->dispatcher.scheduledEvents, entry) { + celix_scheduled_event_t* visit = entry.value.ptrValue; + if (bndId == celix_scheduledEvent_getBundleId(visit)) { + removeEvent = visit; + celix_scheduledEvent_retain(removeEvent); + if (!celix_scheduledEvent_isSingleShot(removeEvent)) { + fw_log(fw->logger, + CELIX_LOG_LEVEL_WARNING, + "Removing dangling scheduled event '%s' (id=%li) for bundle id %li. This scheduled event should " + "have been removed up by the bundle.", + celix_scheduledEvent_getName(removeEvent), + celix_scheduledEvent_getId(removeEvent), + celix_scheduledEvent_getBundleId(removeEvent)); + } + celix_scheduledEvent_markForRemoval(removeEvent); + celixThreadCondition_broadcast(&fw->dispatcher.cond); //notify that scheduled event is marked for removal + break; + } + } + celixThreadMutex_unlock(&fw->dispatcher.mutex); + + if (removeEvent) { + celix_scheduledEvent_waitForRemoved(removeEvent); + celix_scheduledEvent_release(removeEvent); + } + } while (removeEvent != NULL); +} + +static int celix_framework_eventQueueSize(celix_framework_t* fw) { + //precondition fw->dispatcher.mutex lockedx); + return fw->dispatcher.eventQueueSize + celix_arrayList_size(fw->dispatcher.dynamicEventQueue); +} + +static bool requiresScheduledEventsProcessing(celix_framework_t* framework) { + // precondition framework->dispatcher.mutex locked + struct timespec currentTime = celixThreadCondition_getTime(); + bool eventProcessingRequired = false; + CELIX_LONG_HASH_MAP_ITERATE(framework->dispatcher.scheduledEvents, mapEntry) { + celix_scheduled_event_t* visit = mapEntry.value.ptrValue; + if (celix_scheduledEvent_requiresProcessing(visit, ¤tTime)) { + eventProcessingRequired = true; + break; + } + } + return eventProcessingRequired; +} + +static void celix_framework_waitForNextEvent(celix_framework_t* fw, double nextScheduledEvent) { + if (nextScheduledEvent < 0 || nextScheduledEvent > CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS) { + nextScheduledEvent = CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS; + } + struct timespec absTimeout = celixThreadCondition_getDelayedTime(nextScheduledEvent); Review Comment: The computation of `nextScheduledEvent` implemented in the event class ignores elapsed time. This may be masked by the above cap by `CELIX_FRAMEWORK_DEFAULT_MAX_TIMEDWAIT_EVENT_HANDLER_IN_SECONDS`. ########## libs/framework/src/framework.c: ########## @@ -1408,32 +1412,151 @@ 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) { + struct timespec ts = celixThreadCondition_getTime(); + + double nextClosestScheduledEvent; + celix_scheduled_event_t* callEvent; + celix_scheduled_event_t* removeEvent; + do { + nextClosestScheduledEvent = -1; //negative means no event next event + callEvent = NULL; + removeEvent = NULL; + double nextEvent; + celixThreadMutex_lock(&fw->dispatcher.mutex); + CELIX_LONG_HASH_MAP_ITERATE(fw->dispatcher.scheduledEvents, entry) { Review Comment: It is better to finish all processing within a single iteration. ########## libs/framework/src/service_tracker.c: ########## @@ -697,66 +697,57 @@ void celix_serviceTracker_destroy(celix_service_tracker_t *tracker) { } } +static celix_tracked_entry_t* celix_serviceTracker_findHighestRankingService(service_tracker_t *tracker, const char* serviceName) { + //precondition tracker->mutex locked + celix_tracked_entry_t* highest = NULL; + for (int i = 0; i < celix_arrayList_size(tracker->trackedServices); ++i) { + celix_tracked_entry_t* tracked = (celix_tracked_entry_t *) arrayList_get(tracker->trackedServices, i); + if (serviceName == NULL || (serviceName != NULL && tracked->serviceName != NULL && + celix_utils_stringEquals(tracked->serviceName, serviceName))) { + if (highest == NULL) { + highest = tracked; + } else { + int compare = celix_utils_compareServiceIdsAndRanking( + tracked->serviceId, tracked->serviceRanking, + highest->serviceId, highest->serviceRanking + ); + if (compare < 0) { + highest = tracked; + } + } + } + } + return highest; +} + bool celix_serviceTracker_useHighestRankingService(service_tracker_t *tracker, const char *serviceName /*sanity*/, double waitTimeoutInSeconds /*0 -> do not wait */, void *callbackHandle, void (*use)(void *handle, void *svc), void (*useWithProperties)(void *handle, void *svc, const celix_properties_t *props), void (*useWithOwner)(void *handle, void *svc, const celix_properties_t *props, const celix_bundle_t *owner)) { - bool called = false; - celix_tracked_entry_t *tracked = NULL; - celix_tracked_entry_t *highest = NULL; - unsigned int i; - struct timespec begin = celix_gettime(CLOCK_MONOTONIC); - double remaining = waitTimeoutInSeconds > INT_MAX ? INT_MAX : waitTimeoutInSeconds; - remaining = remaining < 0 ? 0 : remaining; - double elapsed = 0; - long seconds = remaining; - long nanoseconds = (remaining - seconds) * CELIX_NS_IN_SEC; - - //first lock tracker and get highest tracked entry + //first lock tracker and get highest ranking tracked entry celixThreadMutex_lock(&tracker->mutex); - while (highest == NULL) { - unsigned int size = arrayList_size(tracker->trackedServices); - - for (i = 0; i < size; i++) { - tracked = (celix_tracked_entry_t *) arrayList_get(tracker->trackedServices, i); - if (serviceName == NULL || (serviceName != NULL && tracked->serviceName != NULL && - celix_utils_stringEquals(tracked->serviceName, serviceName))) { - if (highest == NULL) { - highest = tracked; - } else { - int compare = celix_utils_compareServiceIdsAndRanking( - tracked->serviceId, tracked->serviceRanking, - highest->serviceId, highest->serviceRanking - ); - if (compare < 0) { - highest = tracked; - } - } - } - } - if(highest == NULL && (seconds > 0 || nanoseconds > 0)) { - celixThreadCondition_timedwaitRelative(&tracker->condTracked, &tracker->mutex, seconds, nanoseconds); - elapsed = celix_elapsedtime(CLOCK_MONOTONIC, begin); - remaining = remaining > elapsed ? (remaining - elapsed) : 0; - seconds = remaining; - nanoseconds = (remaining - seconds) * CELIX_NS_IN_SEC; - } else { - // highest found or timeout + struct timespec absTime = celixThreadCondition_getDelayedTime(waitTimeoutInSeconds); + celix_tracked_entry_t* highest = celix_serviceTracker_findHighestRankingService(tracker, serviceName); + while (highest == NULL && waitTimeoutInSeconds > 0) { Review Comment: Nice cleanup! ########## libs/utils/include/celix_threads.h: ########## @@ -122,13 +122,78 @@ CELIX_UTILS_EXPORT celix_status_t celixThreadRwlockAttr_destroy(celix_thread_rwl typedef pthread_cond_t celix_thread_cond_t; typedef pthread_condattr_t celix_thread_condattr_t; +/** + * @brief Initialize the given condition variable. + * + * For Linux the condition clock is set to CLOCK_MONOTONIC whether or not the attr is NULL. + * + * @param[in] condition The condition variable to initialize. + * @param[in] attr The condition variable attributes to use. Can be NULL for default attributes. + * @return CELIX_SUCCESS if the condition variable is initialized successfully. + */ CELIX_UTILS_EXPORT celix_status_t celixThreadCondition_init(celix_thread_cond_t *condition, celix_thread_condattr_t *attr); CELIX_UTILS_EXPORT celix_status_t celixThreadCondition_destroy(celix_thread_cond_t *condition); CELIX_UTILS_EXPORT celix_status_t celixThreadCondition_wait(celix_thread_cond_t *cond, celix_thread_mutex_t *mutex); -CELIX_UTILS_EXPORT celix_status_t celixThreadCondition_timedwaitRelative(celix_thread_cond_t *cond, celix_thread_mutex_t *mutex, long seconds, long nanoseconds); +/** + * @brief Wait until the given time. + * @deprecated use celixThreadCondition_waitUntil. + */ +CELIX_UTILS_EXPORT celix_status_t celixThreadCondition_timedwaitRelative(celix_thread_cond_t *cond, celix_thread_mutex_t *mutex, long seconds, long nanoseconds) CELIX_UTILS_DEPRECATED; + + +/** + * @brief Get the current time suitable for Celix thread conditions. + * + * This function returns the current time compatible with the Celix thread conditions, specifically for + * the function celixThreadCondition_waitUntil, as long as the condition is initialized with + * celixThreadCondition_init. + * + * Note: Do not use the returned time for logging or displaying the current time as the choice of clock + * varies based on the operating system. + * + * @return A struct timespec denoting the current time. + */ +CELIX_UTILS_EXPORT struct timespec celixThreadCondition_getTime(); + +/** + * @brief Calculate the current time incremented by a given delay, suitable for Celix thread conditions. + * + * This function provides the current time, increased by a specified delay (in seconds), compatible + * with Celix thread conditions. The resulting struct timespec can be used with the function + * celixThreadCondition_waitUntil, as long as the condition is initialized with celixThreadCondition_init. + * + * Note: Do not use the returned time for logging or displaying the current time as the choice of clock + * varies based on the operating system. + * + * @param[in] delayInSeconds The desired delay in seconds to be added to the current time. + * @return A struct timespec denoting the current time plus the provided delay. + */ +CELIX_UTILS_EXPORT struct timespec celixThreadCondition_getDelayedTime(double delayInSeconds); + + +/** + * @brief Wait for the condition to be signaled or until the given absolute time is reached. + * + * @section Errors + * - CELIX_SUCCESS if the condition is signaled before the delayInSeconds is reached. + * - CELIX_ILLEGAL_ARGUMENT if the absTime is negative. + * - ENOTRECOVERABLE if the state protected by the mutex is not recoverable. Review Comment: This only makes sense for `PTHREAD_MUTEX_ROBUST`. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@celix.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org