|
@@ -2,160 +2,118 @@
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
*
|
|
|
- * Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
|
|
+ * Copyright 2017, 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
|
|
* Copyright 2017 (c) Stefan Profanter, fortiss GmbH
|
|
|
*/
|
|
|
|
|
|
#include "ua_util_internal.h"
|
|
|
#include "ua_timer.h"
|
|
|
|
|
|
-/* Only one thread operates on the repeated jobs. This is usually the "main"
|
|
|
- * thread with the event loop. All other threads introduce changes via a
|
|
|
- * multi-producer single-consumer (MPSC) queue. The queue is based on a design
|
|
|
- * by Dmitry Vyukov.
|
|
|
- * http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
|
|
|
- *
|
|
|
- * The RepeatedCallback structure is used both in the sorted list of callbacks
|
|
|
- * and in the MPSC changes queue. For the changes queue, we differentiate
|
|
|
- * between three cases encoded in the callback pointer.
|
|
|
- *
|
|
|
- * callback > 0x01: add the new repeated callback to the sorted list
|
|
|
- * callback == 0x00: remove the callback with the same id
|
|
|
- * callback == 0x01: change the interval of the existing callback */
|
|
|
-
|
|
|
-#define REMOVE_SENTINEL 0x00
|
|
|
-#define CHANGE_SENTINEL 0x01
|
|
|
-
|
|
|
-struct UA_TimerCallbackEntry {
|
|
|
- SLIST_ENTRY(UA_TimerCallbackEntry) next; /* Next element in the list */
|
|
|
- UA_DateTime nextTime; /* The next time when the callbacks
|
|
|
- * are to be executed */
|
|
|
+struct UA_TimerEntry {
|
|
|
+ ZIP_ENTRY(UA_TimerEntry) zipfields;
|
|
|
+ UA_DateTime nextTime; /* The next time when the callback
|
|
|
+ * is to be executed */
|
|
|
UA_UInt64 interval; /* Interval in 100ns resolution */
|
|
|
- UA_UInt64 id; /* Id of the repeated callback */
|
|
|
+ UA_Boolean repeated; /* Repeated callback? */
|
|
|
|
|
|
UA_ApplicationCallback callback;
|
|
|
void *application;
|
|
|
void *data;
|
|
|
+
|
|
|
+ ZIP_ENTRY(UA_TimerEntry) idZipfields;
|
|
|
+ UA_UInt64 id; /* Id of the entry */
|
|
|
};
|
|
|
|
|
|
-void
|
|
|
-UA_Timer_init(UA_Timer *t) {
|
|
|
- SLIST_INIT(&t->repeatedCallbacks);
|
|
|
- t->changes_head = (UA_TimerCallbackEntry*)&t->changes_stub;
|
|
|
- t->changes_tail = (UA_TimerCallbackEntry*)&t->changes_stub;
|
|
|
- t->changes_stub = NULL;
|
|
|
- t->idCounter = 0;
|
|
|
+/* There may be several entries with the same nextTime in the tree. We give them
|
|
|
+ * an absolute order by considering the memory address to break ties. Because of
|
|
|
+ * this, the nextTime property cannot be used to lookup specific entries. */
|
|
|
+static enum ZIP_CMP
|
|
|
+cmpDateTime(const UA_DateTime *a, const UA_DateTime *b) {
|
|
|
+ if(*a < *b)
|
|
|
+ return ZIP_CMP_LESS;
|
|
|
+ if(*a > *b)
|
|
|
+ return ZIP_CMP_MORE;
|
|
|
+ if(a == b)
|
|
|
+ return ZIP_CMP_EQ;
|
|
|
+ if(a < b)
|
|
|
+ return ZIP_CMP_LESS;
|
|
|
+ return ZIP_CMP_MORE;
|
|
|
}
|
|
|
|
|
|
-static void
|
|
|
-enqueueChange(UA_Timer *t, UA_TimerCallbackEntry *tc) {
|
|
|
- tc->next.sle_next = NULL;
|
|
|
- UA_TimerCallbackEntry *prev = (UA_TimerCallbackEntry*)
|
|
|
- UA_atomic_xchg((void * volatile *)&t->changes_head, tc);
|
|
|
- /* Nothing can be dequeued while the producer is blocked here */
|
|
|
- prev->next.sle_next = tc; /* Once this change is visible in the consumer,
|
|
|
- * the node is dequeued in the following
|
|
|
- * iteration */
|
|
|
+ZIP_PROTTYPE(UA_TimerZip, UA_TimerEntry, UA_DateTime)
|
|
|
+ZIP_IMPL(UA_TimerZip, UA_TimerEntry, zipfields, UA_DateTime, nextTime, cmpDateTime)
|
|
|
+
|
|
|
+/* The identifiers of entries are unique */
|
|
|
+static enum ZIP_CMP
|
|
|
+cmpId(const UA_UInt64 *a, const UA_UInt64 *b) {
|
|
|
+ if(*a < *b)
|
|
|
+ return ZIP_CMP_LESS;
|
|
|
+ if(*a == *b)
|
|
|
+ return ZIP_CMP_EQ;
|
|
|
+ return ZIP_CMP_MORE;
|
|
|
}
|
|
|
|
|
|
-static UA_TimerCallbackEntry *
|
|
|
-dequeueChange(UA_Timer *t) {
|
|
|
- UA_TimerCallbackEntry *tail = t->changes_tail;
|
|
|
- UA_TimerCallbackEntry *next = tail->next.sle_next;
|
|
|
- if(tail == (UA_TimerCallbackEntry*)&t->changes_stub) {
|
|
|
- if(!next)
|
|
|
- return NULL;
|
|
|
- t->changes_tail = next;
|
|
|
- tail = next;
|
|
|
- next = next->next.sle_next;
|
|
|
- }
|
|
|
- if(next) {
|
|
|
- t->changes_tail = next;
|
|
|
- return tail;
|
|
|
- }
|
|
|
- UA_TimerCallbackEntry* head = t->changes_head;
|
|
|
- if(tail != head)
|
|
|
- return NULL;
|
|
|
- enqueueChange(t, (UA_TimerCallbackEntry*)&t->changes_stub);
|
|
|
- next = tail->next.sle_next;
|
|
|
- if(next) {
|
|
|
- t->changes_tail = next;
|
|
|
- return tail;
|
|
|
- }
|
|
|
- return NULL;
|
|
|
+ZIP_PROTTYPE(UA_TimerIdZip, UA_TimerEntry, UA_UInt64)
|
|
|
+ZIP_IMPL(UA_TimerIdZip, UA_TimerEntry, idZipfields, UA_UInt64, id, cmpId)
|
|
|
+
|
|
|
+void
|
|
|
+UA_Timer_init(UA_Timer *t) {
|
|
|
+ memset(t, 0, sizeof(UA_Timer));
|
|
|
}
|
|
|
|
|
|
-/* Adding repeated callbacks: Add an entry with the "nextTime" timestamp in the
|
|
|
- * future. This will be picked up in the next iteration and inserted at the
|
|
|
- * correct place. So that the next execution takes place ät "nextTime". */
|
|
|
-UA_StatusCode
|
|
|
-UA_Timer_addRepeatedCallback(UA_Timer *t, UA_ApplicationCallback callback,
|
|
|
- void *application, void *data, UA_Double interval_ms,
|
|
|
- UA_UInt64 *callbackId) {
|
|
|
+static UA_StatusCode
|
|
|
+addCallback(UA_Timer *t, UA_ApplicationCallback callback, void *application, void *data,
|
|
|
+ UA_DateTime nextTime, UA_UInt64 interval, UA_Boolean repeated,
|
|
|
+ UA_UInt64 *callbackId) {
|
|
|
/* A callback method needs to be present */
|
|
|
if(!callback)
|
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
|
|
|
- /* The interval needs to be positive */
|
|
|
- if(interval_ms <= 0.0)
|
|
|
- return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
-
|
|
|
/* Allocate the repeated callback structure */
|
|
|
- UA_TimerCallbackEntry *tc =
|
|
|
- (UA_TimerCallbackEntry*)UA_malloc(sizeof(UA_TimerCallbackEntry));
|
|
|
- if(!tc)
|
|
|
+ UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry));
|
|
|
+ if(!te)
|
|
|
return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
|
|
|
/* Set the repeated callback */
|
|
|
- tc->interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC); /* in 100ns resolution */
|
|
|
- tc->id = ++t->idCounter;
|
|
|
- tc->callback = callback;
|
|
|
- tc->application = application;
|
|
|
- tc->data = data;
|
|
|
- tc->nextTime = UA_DateTime_nowMonotonic() + (UA_DateTime)tc->interval;
|
|
|
+ te->interval = (UA_UInt64)interval;
|
|
|
+ te->id = ++t->idCounter;
|
|
|
+ te->callback = callback;
|
|
|
+ te->application = application;
|
|
|
+ te->data = data;
|
|
|
+ te->repeated = repeated;
|
|
|
+ te->nextTime = nextTime;
|
|
|
|
|
|
/* Set the output identifier */
|
|
|
if(callbackId)
|
|
|
- *callbackId = tc->id;
|
|
|
+ *callbackId = te->id;
|
|
|
|
|
|
- /* Enqueue the changes in the MPSC queue */
|
|
|
- enqueueChange(t, tc);
|
|
|
+ ZIP_INSERT(UA_TimerZip, &t->root, te, ZIP_FFS32(UA_UInt32_random()));
|
|
|
+ ZIP_INSERT(UA_TimerIdZip, &t->idRoot, te, ZIP_RANK(te, zipfields));
|
|
|
return UA_STATUSCODE_GOOD;
|
|
|
}
|
|
|
|
|
|
-static void
|
|
|
-addTimerCallbackEntry(UA_Timer *t, UA_TimerCallbackEntry * UA_RESTRICT tc) {
|
|
|
- /* Find the last entry before this callback */
|
|
|
- UA_TimerCallbackEntry *tmpTc, *afterTc = NULL;
|
|
|
- SLIST_FOREACH(tmpTc, &t->repeatedCallbacks, next) {
|
|
|
- if(tmpTc->nextTime >= tc->nextTime)
|
|
|
- break;
|
|
|
-
|
|
|
- /* The goal is to have many repeated callbacks with the same repetition
|
|
|
- * interval in a "block" in order to reduce linear search for re-entry
|
|
|
- * to the sorted list after processing. Allow the first execution to lie
|
|
|
- * between "nextTime - 1s" and "nextTime" if this adjustment groups
|
|
|
- * callbacks with the same repetition interval.
|
|
|
- * Callbacks of a block are added in reversed order. This design allows
|
|
|
- * the monitored items of a subscription (if created in a sequence with the
|
|
|
- * same publish/sample interval) to be executed before the subscription
|
|
|
- * publish the notifications */
|
|
|
- if(tmpTc->interval == tc->interval &&
|
|
|
- tmpTc->nextTime > (tc->nextTime - UA_DATETIME_SEC)) {
|
|
|
- tc->nextTime = tmpTc->nextTime;
|
|
|
- break;
|
|
|
- }
|
|
|
+UA_StatusCode
|
|
|
+UA_Timer_addTimedCallback(UA_Timer *t, UA_ApplicationCallback callback,
|
|
|
+ void *application, void *data, UA_DateTime date,
|
|
|
+ UA_UInt64 *callbackId) {
|
|
|
+ return addCallback(t, callback, application, data, date, 0, false, callbackId);
|
|
|
+}
|
|
|
|
|
|
- /* tc is neither in the same interval nor supposed to be executed sooner
|
|
|
- * than tmpTc. Update afterTc to push tc further back in the timer list. */
|
|
|
- afterTc = tmpTc;
|
|
|
- }
|
|
|
+/* Adding repeated callbacks: Add an entry with the "nextTime" timestamp in the
|
|
|
+ * future. This will be picked up in the next iteration and inserted at the
|
|
|
+ * correct place. So that the next execution takes place ät "nextTime". */
|
|
|
+UA_StatusCode
|
|
|
+UA_Timer_addRepeatedCallback(UA_Timer *t, UA_ApplicationCallback callback,
|
|
|
+ void *application, void *data, UA_Double interval_ms,
|
|
|
+ UA_UInt64 *callbackId) {
|
|
|
+ /* The interval needs to be positive */
|
|
|
+ if(interval_ms <= 0.0)
|
|
|
+ return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
|
|
|
- /* Add the repeated callback */
|
|
|
- if(afterTc)
|
|
|
- SLIST_INSERT_AFTER(afterTc, tc, next);
|
|
|
- else
|
|
|
- SLIST_INSERT_HEAD(&t->repeatedCallbacks, tc, next);
|
|
|
+ UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
|
|
|
+ UA_DateTime nextTime = UA_DateTime_nowMonotonic() + (UA_DateTime)interval;
|
|
|
+ return addCallback(t, callback, application, data, nextTime,
|
|
|
+ interval, true, callbackId);
|
|
|
}
|
|
|
|
|
|
UA_StatusCode
|
|
@@ -165,211 +123,74 @@ UA_Timer_changeRepeatedCallbackInterval(UA_Timer *t, UA_UInt64 callbackId,
|
|
|
if(interval_ms <= 0.0)
|
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
|
|
|
- /* Allocate the repeated callback structure */
|
|
|
- UA_TimerCallbackEntry *tc =
|
|
|
- (UA_TimerCallbackEntry*)UA_malloc(sizeof(UA_TimerCallbackEntry));
|
|
|
- if(!tc)
|
|
|
- return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+ /* Remove from the sorted list */
|
|
|
+ UA_TimerEntry *te = ZIP_FIND(UA_TimerIdZip, &t->idRoot, &callbackId);
|
|
|
+ if(!te)
|
|
|
+ return UA_STATUSCODE_BADNOTFOUND;
|
|
|
|
|
|
/* Set the repeated callback */
|
|
|
- tc->interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC); /* in 100ns resolution */
|
|
|
- tc->id = callbackId;
|
|
|
- tc->nextTime = UA_DateTime_nowMonotonic() + (UA_DateTime)tc->interval;
|
|
|
- tc->callback = (UA_ApplicationCallback)CHANGE_SENTINEL;
|
|
|
-
|
|
|
- /* Enqueue the changes in the MPSC queue */
|
|
|
- enqueueChange(t, tc);
|
|
|
+ ZIP_REMOVE(UA_TimerZip, &t->root, te);
|
|
|
+ te->interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC); /* in 100ns resolution */
|
|
|
+ te->nextTime = UA_DateTime_nowMonotonic() + (UA_DateTime)te->interval;
|
|
|
+ ZIP_INSERT(UA_TimerZip, &t->root, te, ZIP_RANK(te, zipfields));
|
|
|
return UA_STATUSCODE_GOOD;
|
|
|
}
|
|
|
|
|
|
-static void
|
|
|
-changeTimerCallbackEntryInterval(UA_Timer *t, UA_UInt64 callbackId,
|
|
|
- UA_UInt64 interval, UA_DateTime nextTime) {
|
|
|
- /* Remove from the sorted list */
|
|
|
- UA_TimerCallbackEntry *tc, *prev = NULL;
|
|
|
- SLIST_FOREACH(tc, &t->repeatedCallbacks, next) {
|
|
|
- if(callbackId == tc->id) {
|
|
|
- if(prev)
|
|
|
- SLIST_REMOVE_AFTER(prev, next);
|
|
|
- else
|
|
|
- SLIST_REMOVE_HEAD(&t->repeatedCallbacks, next);
|
|
|
- break;
|
|
|
- }
|
|
|
- prev = tc;
|
|
|
- }
|
|
|
- if(!tc)
|
|
|
+void
|
|
|
+UA_Timer_removeCallback(UA_Timer *t, UA_UInt64 callbackId) {
|
|
|
+ UA_TimerEntry *te = ZIP_FIND(UA_TimerIdZip, &t->idRoot, &callbackId);
|
|
|
+ if(!te)
|
|
|
return;
|
|
|
|
|
|
- /* Adjust settings */
|
|
|
- tc->interval = interval;
|
|
|
- tc->nextTime = nextTime;
|
|
|
-
|
|
|
- /* Reinsert at the new position */
|
|
|
- addTimerCallbackEntry(t, tc);
|
|
|
-}
|
|
|
-
|
|
|
-/* Removing a repeated callback: Add an entry with the "nextTime" timestamp set
|
|
|
- * to UA_INT64_MAX. The next iteration picks this up and removes the repated
|
|
|
- * callback from the linked list. */
|
|
|
-UA_StatusCode
|
|
|
-UA_Timer_removeRepeatedCallback(UA_Timer *t, UA_UInt64 callbackId) {
|
|
|
- /* Allocate the repeated callback structure */
|
|
|
- UA_TimerCallbackEntry *tc =
|
|
|
- (UA_TimerCallbackEntry*)UA_malloc(sizeof(UA_TimerCallbackEntry));
|
|
|
- if(!tc)
|
|
|
- return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
-
|
|
|
- /* Set the repeated callback with the sentinel nextTime */
|
|
|
- tc->id = callbackId;
|
|
|
- tc->callback = (UA_ApplicationCallback)REMOVE_SENTINEL;
|
|
|
-
|
|
|
- /* Enqueue the changes in the MPSC queue */
|
|
|
- enqueueChange(t, tc);
|
|
|
- return UA_STATUSCODE_GOOD;
|
|
|
-}
|
|
|
-
|
|
|
-static void
|
|
|
-removeRepeatedCallback(UA_Timer *t, UA_UInt64 callbackId) {
|
|
|
- UA_TimerCallbackEntry *tc, *prev = NULL;
|
|
|
- SLIST_FOREACH(tc, &t->repeatedCallbacks, next) {
|
|
|
- if(callbackId == tc->id) {
|
|
|
- if(prev)
|
|
|
- SLIST_REMOVE_AFTER(prev, next);
|
|
|
- else
|
|
|
- SLIST_REMOVE_HEAD(&t->repeatedCallbacks, next);
|
|
|
- UA_free(tc);
|
|
|
- break;
|
|
|
- }
|
|
|
- prev = tc;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/* Process the changes that were added to the MPSC queue (by other threads) */
|
|
|
-static void
|
|
|
-processChanges(UA_Timer *t) {
|
|
|
- UA_TimerCallbackEntry *change;
|
|
|
- while((change = dequeueChange(t))) {
|
|
|
- switch((uintptr_t)change->callback) {
|
|
|
- case REMOVE_SENTINEL:
|
|
|
- removeRepeatedCallback(t, change->id);
|
|
|
- UA_free(change);
|
|
|
- break;
|
|
|
- case CHANGE_SENTINEL:
|
|
|
- changeTimerCallbackEntryInterval(t, change->id, change->interval,
|
|
|
- change->nextTime);
|
|
|
- UA_free(change);
|
|
|
- break;
|
|
|
- default:
|
|
|
- addTimerCallbackEntry(t, change);
|
|
|
- }
|
|
|
- }
|
|
|
+ ZIP_REMOVE(UA_TimerZip, &t->root, te);
|
|
|
+ ZIP_REMOVE(UA_TimerIdZip, &t->idRoot, te);
|
|
|
+ UA_free(te);
|
|
|
}
|
|
|
|
|
|
UA_DateTime
|
|
|
UA_Timer_process(UA_Timer *t, UA_DateTime nowMonotonic,
|
|
|
UA_TimerExecutionCallback executionCallback,
|
|
|
void *executionApplication) {
|
|
|
- /* Insert and remove callbacks */
|
|
|
- processChanges(t);
|
|
|
-
|
|
|
- /* Find the last callback to be executed now */
|
|
|
- UA_TimerCallbackEntry *firstAfter, *lastNow = NULL;
|
|
|
- SLIST_FOREACH(firstAfter, &t->repeatedCallbacks, next) {
|
|
|
- if(firstAfter->nextTime > nowMonotonic)
|
|
|
- break;
|
|
|
- lastNow = firstAfter;
|
|
|
- }
|
|
|
-
|
|
|
- /* Nothing to do */
|
|
|
- if(!lastNow) {
|
|
|
- if(firstAfter)
|
|
|
- return firstAfter->nextTime;
|
|
|
- return UA_INT64_MAX;
|
|
|
- }
|
|
|
-
|
|
|
- /* Put the callbacks that are executed now in a separate list */
|
|
|
- UA_TimerCallbackList executedNowList;
|
|
|
- executedNowList.slh_first = SLIST_FIRST(&t->repeatedCallbacks);
|
|
|
- lastNow->next.sle_next = NULL;
|
|
|
-
|
|
|
- /* Fake entry to represent the first element in the newly-sorted list */
|
|
|
- UA_TimerCallbackEntry tmp_first;
|
|
|
- tmp_first.nextTime = nowMonotonic - 1; /* never matches for last_dispatched */
|
|
|
- tmp_first.next.sle_next = firstAfter;
|
|
|
- UA_TimerCallbackEntry *last_dispatched = &tmp_first;
|
|
|
-
|
|
|
- /* Iterate over the list of callbacks to process now */
|
|
|
- UA_TimerCallbackEntry *tc;
|
|
|
- while((tc = SLIST_FIRST(&executedNowList))) {
|
|
|
- /* Remove from the list */
|
|
|
- SLIST_REMOVE_HEAD(&executedNowList, next);
|
|
|
-
|
|
|
- /* Dispatch/process callback */
|
|
|
- executionCallback(executionApplication, tc->callback,
|
|
|
- tc->application, tc->data);
|
|
|
+ UA_TimerEntry *first;
|
|
|
+ while((first = ZIP_MIN(UA_TimerZip, &t->root)) &&
|
|
|
+ first->nextTime <= nowMonotonic) {
|
|
|
+ ZIP_REMOVE(UA_TimerZip, &t->root, first);
|
|
|
+
|
|
|
+ /* Reinsert / remove to their new position first. Because the callback
|
|
|
+ * can interact with the zip tree and expects the same entries in the
|
|
|
+ * root and idRoot trees. */
|
|
|
+
|
|
|
+ if(!first->repeated) {
|
|
|
+ ZIP_REMOVE(UA_TimerIdZip, &t->idRoot, first);
|
|
|
+ executionCallback(executionApplication, first->callback,
|
|
|
+ first->application, first->data);
|
|
|
+ UA_free(first);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
/* Set the time for the next execution. Prevent an infinite loop by
|
|
|
* forcing the next processing into the next iteration. */
|
|
|
- tc->nextTime += (UA_Int64)tc->interval;
|
|
|
- if(tc->nextTime < nowMonotonic)
|
|
|
- tc->nextTime = nowMonotonic + 1;
|
|
|
-
|
|
|
- /* Find the new position for tc to keep the list sorted */
|
|
|
- UA_TimerCallbackEntry *prev_tc;
|
|
|
- if(last_dispatched->nextTime == tc->nextTime) {
|
|
|
- /* We try to "batch" repeatedCallbacks with the same interval. This
|
|
|
- * saves a linear search when the last dispatched entry has the same
|
|
|
- * nextTime timestamp as this entry. */
|
|
|
- UA_assert(last_dispatched != &tmp_first);
|
|
|
- prev_tc = last_dispatched;
|
|
|
- } else {
|
|
|
- /* Find the position for the next execution by a linear search
|
|
|
- * starting at last_dispatched or the first element */
|
|
|
- if(last_dispatched->nextTime < tc->nextTime)
|
|
|
- prev_tc = last_dispatched;
|
|
|
- else
|
|
|
- prev_tc = &tmp_first;
|
|
|
-
|
|
|
- while(true) {
|
|
|
- UA_TimerCallbackEntry *n = SLIST_NEXT(prev_tc, next);
|
|
|
- if(!n || n->nextTime >= tc->nextTime)
|
|
|
- break;
|
|
|
- prev_tc = n;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /* Update last_dispatched to make sure batched callbacks are added in the
|
|
|
- * same sequence as before they were executed and to save some iterations
|
|
|
- * of the linear search for callbacks to be added further back in the list. */
|
|
|
- last_dispatched = tc;
|
|
|
-
|
|
|
- /* Add entry to the new position in the sorted list */
|
|
|
- SLIST_INSERT_AFTER(prev_tc, tc, next);
|
|
|
+ first->nextTime += (UA_Int64)first->interval;
|
|
|
+ if(first->nextTime < nowMonotonic)
|
|
|
+ first->nextTime = nowMonotonic + 1;
|
|
|
+ ZIP_INSERT(UA_TimerZip, &t->root, first, ZIP_RANK(first, zipfields));
|
|
|
+ executionCallback(executionApplication, first->callback,
|
|
|
+ first->application, first->data);
|
|
|
}
|
|
|
|
|
|
- /* Set the entry-point for the newly sorted list */
|
|
|
- t->repeatedCallbacks.slh_first = tmp_first.next.sle_next;
|
|
|
-
|
|
|
- /* Re-repeat processAddRemoved since one of the callbacks might have removed
|
|
|
- * or added a callback. So we return a correct timeout. */
|
|
|
- processChanges(t);
|
|
|
+ /* Return the timestamp of the earliest next callback */
|
|
|
+ first = ZIP_MIN(UA_TimerZip, &t->root);
|
|
|
+ return (first) ? first->nextTime : UA_INT64_MAX;
|
|
|
+}
|
|
|
|
|
|
- /* Return timestamp of next repetition */
|
|
|
- tc = SLIST_FIRST(&t->repeatedCallbacks);
|
|
|
- if(!tc)
|
|
|
- return UA_INT64_MAX; /* Main-loop has a max timeout / will continue earlier */
|
|
|
- return tc->nextTime;
|
|
|
+static void
|
|
|
+freeEntry(UA_TimerEntry *te, void *data) {
|
|
|
+ UA_free(te);
|
|
|
}
|
|
|
|
|
|
void
|
|
|
UA_Timer_deleteMembers(UA_Timer *t) {
|
|
|
- /* Process changes to empty the MPSC queue */
|
|
|
- processChanges(t);
|
|
|
-
|
|
|
- /* Remove repeated callbacks */
|
|
|
- UA_TimerCallbackEntry *current;
|
|
|
- while((current = SLIST_FIRST(&t->repeatedCallbacks))) {
|
|
|
- SLIST_REMOVE_HEAD(&t->repeatedCallbacks, next);
|
|
|
- UA_free(current);
|
|
|
- }
|
|
|
+ /* Free all nodes and reset the root */
|
|
|
+ ZIP_ITER(UA_TimerZip, &t->root, freeEntry, NULL);
|
|
|
+ ZIP_INIT(&t->root);
|
|
|
}
|