Files
softbus_OPC/third_party/open62541/arch/common/ua_timer.c
2026-06-09 17:27:24 +08:00

309 lines
11 KiB
C

/* This Source Code Form is subject to the terms of the Mozilla Public
* 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, 2018, 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2017 (c) Stefan Profanter, fortiss GmbH
*/
#include "ua_timer.h"
static enum ZIP_CMP
cmpDateTime(const UA_DateTime *a, const UA_DateTime *b) {
if(*a == *b)
return ZIP_CMP_EQ;
return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE;
}
static enum ZIP_CMP
cmpId(const UA_UInt64 *a, const UA_UInt64 *b) {
if(*a == *b)
return ZIP_CMP_EQ;
return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE;
}
ZIP_FUNCTIONS(UA_TimerTree, UA_TimerEntry, treeEntry, UA_DateTime, nextTime, cmpDateTime)
ZIP_FUNCTIONS(UA_TimerIdTree, UA_TimerEntry, idTreeEntry, UA_UInt64, id, cmpId)
static UA_DateTime
calculateNextTime(UA_DateTime currentTime, UA_DateTime baseTime,
UA_DateTime interval) {
/* Take the difference between current and base time */
UA_DateTime diffCurrentTimeBaseTime = currentTime - baseTime;
/* Take modulo of the diff time with the interval. This is the duration we
* are already "into" the current interval. Subtract it from (current +
* interval) to get the next execution time. */
UA_DateTime cycleDelay = diffCurrentTimeBaseTime % interval;
/* Handle the special case where the baseTime is in the future */
if(UA_UNLIKELY(cycleDelay < 0))
cycleDelay += interval;
return currentTime + interval - cycleDelay;
}
void
UA_Timer_init(UA_Timer *t) {
memset(t, 0, sizeof(UA_Timer));
UA_LOCK_INIT(&t->timerMutex);
}
static UA_StatusCode
addCallback(UA_Timer *t, UA_ApplicationCallback callback, void *application,
void *data, UA_DateTime nextTime, UA_UInt64 interval,
UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) {
/* A callback method needs to be present */
if(!callback)
return UA_STATUSCODE_BADINTERNALERROR;
/* Allocate the repeated callback structure */
UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry));
if(!te)
return UA_STATUSCODE_BADOUTOFMEMORY;
/* Set the repeated callback */
te->interval = (UA_UInt64)interval;
te->id = ++t->idCounter;
te->callback = callback;
te->application = application;
te->data = data;
te->nextTime = nextTime;
te->timerPolicy = timerPolicy;
/* Set the output identifier */
if(callbackId)
*callbackId = te->id;
ZIP_INSERT(UA_TimerTree, &t->tree, te);
ZIP_INSERT(UA_TimerIdTree, &t->idTree, te);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_Timer_addTimedCallback(UA_Timer *t, UA_ApplicationCallback callback,
void *application, void *data, UA_DateTime date,
UA_UInt64 *callbackId) {
UA_LOCK(&t->timerMutex);
UA_StatusCode res = addCallback(t, callback, application, data, date,
0, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME,
callbackId);
UA_UNLOCK(&t->timerMutex);
return res;
}
/* 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_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId) {
/* The interval needs to be positive */
if(interval_ms <= 0.0)
return UA_STATUSCODE_BADINTERNALERROR;
UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
if(interval == 0)
return UA_STATUSCODE_BADINTERNALERROR;
/* Compute the first time for execution */
UA_DateTime currentTime = UA_DateTime_nowMonotonic();
UA_DateTime nextTime;
if(baseTime == NULL) {
/* Use "now" as the basetime */
nextTime = currentTime + (UA_DateTime)interval;
} else {
nextTime = calculateNextTime(currentTime, *baseTime, (UA_DateTime)interval);
}
UA_LOCK(&t->timerMutex);
UA_StatusCode res = addCallback(t, callback, application, data, nextTime,
interval, timerPolicy, callbackId);
UA_UNLOCK(&t->timerMutex);
return res;
}
UA_StatusCode
UA_Timer_changeRepeatedCallback(UA_Timer *t, UA_UInt64 callbackId,
UA_Double interval_ms, UA_DateTime *baseTime,
UA_TimerPolicy timerPolicy) {
/* The interval needs to be positive */
if(interval_ms <= 0.0)
return UA_STATUSCODE_BADINTERNALERROR;
UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
if(interval == 0)
return UA_STATUSCODE_BADINTERNALERROR;
UA_LOCK(&t->timerMutex);
/* Find according to the id */
UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId);
if(!te) {
UA_UNLOCK(&t->timerMutex);
return UA_STATUSCODE_BADNOTFOUND;
}
/* Try to remove from the time-sorted tree. If not found, then the entry is
* in the processTree. If that is the case, leave it there and only adjust
* the interval and nextTime (if the TimerPolicy uses a basetime). */
UA_Boolean normalTree = (ZIP_REMOVE(UA_TimerTree, &t->tree, te) != NULL);
/* Compute the next time for execution. The logic is identical to the
* creation of a new repeated callback. */
UA_DateTime currentTime = UA_DateTime_nowMonotonic();
if(baseTime == NULL) {
/* Use "now" as the basetime */
te->nextTime = currentTime + (UA_DateTime)interval;
} else {
te->nextTime = calculateNextTime(currentTime, *baseTime, (UA_DateTime)interval);
}
/* Update the remaining parameters and re-insert */
te->interval = interval;
te->timerPolicy = timerPolicy;
if(normalTree)
ZIP_INSERT(UA_TimerTree, &t->tree, te);
UA_UNLOCK(&t->timerMutex);
return UA_STATUSCODE_GOOD;
}
void
UA_Timer_removeCallback(UA_Timer *t, UA_UInt64 callbackId) {
UA_LOCK(&t->timerMutex);
UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId);
if(UA_LIKELY(te != NULL)) {
if(t->processTree.root == NULL) {
/* Remove/free the entry */
ZIP_REMOVE(UA_TimerTree, &t->tree, te);
ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te);
UA_free(te);
} else {
/* We are currently processing. Only mark the entry to be deleted.
* Will be removed/freed the next time we reach it in the processing
* callback. */
te->callback = NULL;
}
}
UA_UNLOCK(&t->timerMutex);
}
struct TimerProcessContext {
UA_Timer *t;
UA_DateTime nowMonotonic;
};
static void *
processEntryCallback(void *context, UA_TimerEntry *te) {
struct TimerProcessContext *tpc = (struct TimerProcessContext*)context;
UA_Timer *t = tpc->t;
/* Execute the callback. The memory is not freed during the callback.
* Instead, whenever t->processTree != NULL, the entries are only marked for
* deletion by setting elm->callback to NULL. */
if(te->callback) {
UA_UNLOCK(&t->timerMutex);
te->callback(te->application, te->data);
UA_LOCK(&t->timerMutex);
}
/* Remove and free the entry if marked for deletion or a one-time timed
* callback */
if(!te->callback || te->interval == 0) {
ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te);
UA_free(te);
return NULL;
}
/* Set the time for the next regular execution */
te->nextTime += (UA_DateTime)te->interval;
/* Handle the case where the "window" was missed. E.g. due to congestion of
* the application or if the clock was shifted.
*
* If the timer policy is "CurrentTime", then there is at least the
* interval between executions. This is used for Monitoreditems, for
* which the spec says: The sampling interval indicates the fastest rate
* at which the Server should sample its underlying source for data
* changes. (Part 4, 5.12.1.2) */
if(te->nextTime < tpc->nowMonotonic) {
if(te->timerPolicy == UA_TIMER_HANDLE_CYCLEMISS_WITH_BASETIME)
te->nextTime = calculateNextTime(tpc->nowMonotonic, te->nextTime,
(UA_DateTime)te->interval);
else
te->nextTime = tpc->nowMonotonic + (UA_DateTime)te->interval;
}
/* Insert back into the time-sorted tree */
ZIP_INSERT(UA_TimerTree, &t->tree, te);
return NULL;
}
UA_DateTime
UA_Timer_process(UA_Timer *t, UA_DateTime nowMonotonic) {
UA_LOCK(&t->timerMutex);
/* Not reentrant. Don't call _process from within _process. */
if(!t->processTree.root) {
/* Move all entries <= nowMonotonic to processTree */
ZIP_UNZIP(UA_TimerTree, &t->tree, &nowMonotonic,
&t->processTree, &t->tree);
/* Consistency check. The smallest not-processed entry isn't ready. */
UA_assert(!ZIP_MIN(UA_TimerTree, &t->tree) ||
ZIP_MIN(UA_TimerTree, &t->tree)->nextTime > nowMonotonic);
/* Iterate over the entries that need processing in-order. This also
* moves them back to the regular time-ordered tree. */
struct TimerProcessContext ctx;
ctx.t = t;
ctx.nowMonotonic = nowMonotonic;
ZIP_ITER(UA_TimerTree, &t->processTree, processEntryCallback, &ctx);
/* Reset processTree. All entries are already moved to the normal tree. */
t->processTree.root = NULL;
}
/* Compute the timestamp of the earliest next callback */
UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree);
UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX;
if(next < nowMonotonic)
next = nowMonotonic;
UA_UNLOCK(&t->timerMutex);
return next;
}
UA_DateTime
UA_Timer_nextRepeatedTime(UA_Timer *t) {
UA_LOCK(&t->timerMutex);
UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree);
UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX;
UA_UNLOCK(&t->timerMutex);
return next;
}
static void *
freeEntryCallback(void *context, UA_TimerEntry *entry) {
UA_free(entry);
return NULL;
}
void
UA_Timer_clear(UA_Timer *t) {
UA_LOCK(&t->timerMutex);
ZIP_ITER(UA_TimerIdTree, &t->idTree, freeEntryCallback, NULL);
t->tree.root = NULL;
t->idTree.root = NULL;
t->idCounter = 0;
UA_UNLOCK(&t->timerMutex);
#if UA_MULTITHREADING >= 100
UA_LOCK_DESTROY(&t->timerMutex);
#endif
}