Coverage Report

Created: 2026-03-31 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541/arch/common/timer.c
Line
Count
Source
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
 *
5
 *    Copyright 2017, 2018, 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
6
 *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
7
 */
8
9
#include "timer.h"
10
11
static enum ZIP_CMP
12
0
cmpDateTime(const UA_DateTime *a, const UA_DateTime *b) {
13
0
    if(*a == *b)
14
0
        return ZIP_CMP_EQ;
15
0
    return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE;
16
0
}
17
18
static enum ZIP_CMP
19
0
cmpId(const UA_UInt64 *a, const UA_UInt64 *b) {
20
0
    if(*a == *b)
21
0
        return ZIP_CMP_EQ;
22
0
    return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE;
23
0
}
24
25
0
ZIP_FUNCTIONS(UA_TimerTree, UA_TimerEntry, treeEntry, UA_DateTime, nextTime, cmpDateTime)
Unexecuted instantiation: timer.c:UA_TimerTree_ZIP_INSERT
Unexecuted instantiation: timer.c:UA_TimerTree_ZIP_REMOVE
Unexecuted instantiation: timer.c:UA_TimerTree_ZIP_UNZIP
Unexecuted instantiation: timer.c:UA_TimerTree_ZIP_MIN
Unexecuted instantiation: timer.c:UA_TimerTree_ZIP_ITER
26
0
ZIP_FUNCTIONS(UA_TimerIdTree, UA_TimerEntry, idTreeEntry, UA_UInt64, id, cmpId)
Unexecuted instantiation: timer.c:UA_TimerIdTree_ZIP_INSERT
Unexecuted instantiation: timer.c:UA_TimerIdTree_ZIP_REMOVE
Unexecuted instantiation: timer.c:UA_TimerIdTree_ZIP_ITER
27
28
static UA_DateTime
29
calculateNextTime(UA_DateTime currentTime, UA_DateTime baseTime,
30
0
                  UA_DateTime interval) {
31
    /* Take the difference between current and base time */
32
0
    UA_DateTime diffCurrentTimeBaseTime = currentTime - baseTime;
33
34
    /* Take modulo of the diff time with the interval. This is the duration we
35
     * are already "into" the current interval. Subtract it from (current +
36
     * interval) to get the next execution time. */
37
0
    UA_DateTime cycleDelay = diffCurrentTimeBaseTime % interval;
38
39
    /* Handle the special case where the baseTime is in the future */
40
0
    if(UA_UNLIKELY(cycleDelay < 0))
41
0
        cycleDelay += interval;
42
43
0
    return currentTime + interval - cycleDelay;
44
0
}
45
46
void
47
0
UA_Timer_init(UA_Timer *t) {
48
0
    memset(t, 0, sizeof(UA_Timer));
49
0
    UA_LOCK_INIT(&t->timerMutex);
50
0
}
51
52
/* Global variables, only used behind the mutex */
53
static UA_DateTime earliest, latest, adjustedNextTime;
54
55
static void *
56
0
findTimer2Batch(void *context, UA_TimerEntry *compare) {
57
    /* Invariance of ZIP_ITER_KEY  */
58
0
    UA_assert(compare->nextTime >= earliest && compare->nextTime <= latest);
59
60
    /* One-shot timers have interval == 0.
61
     * They cannot participate in the modulo-based batching check. */
62
0
    UA_TimerEntry *te = (UA_TimerEntry*)context;
63
0
    if(te->interval == 0 || compare->interval == 0)
64
0
        return NULL;
65
66
    /* Check if one interval is a multiple of the other */
67
0
    if(te->interval < compare->interval && compare->interval % te->interval != 0)
68
0
        return NULL;
69
0
    if(te->interval > compare->interval && te->interval % compare->interval != 0)
70
0
        return NULL;
71
72
0
    adjustedNextTime = compare->nextTime; /* Candidate found */
73
74
    /* Abort when a perfect match is found */
75
0
    return (te->interval == compare->interval) ? te : NULL;
76
0
}
77
78
/* Window-based comparison for batching */
79
static enum ZIP_CMP
80
0
cmpBatchWindow(const UA_DateTime *start, const UA_DateTime *nextTime) {
81
0
    if(*nextTime < *start)
82
0
        return ZIP_CMP_LESS;
83
0
    if(*nextTime > latest)
84
0
        return ZIP_CMP_MORE;
85
0
    return ZIP_CMP_EQ;
86
0
}
87
88
typedef ZIP_HEAD(UA_TimerTreeWindow, UA_TimerEntry) UA_TimerTreeWindow;
89
90
0
ZIP_FUNCTIONS(UA_TimerTreeWindow, UA_TimerEntry, treeEntry,
91
              UA_DateTime, nextTime, cmpBatchWindow)
92
93
/* Adjust the nextTime to batch cyclic callbacks. Look in an interval around the
94
 * original nextTime. Deviate from the original nextTime by at most 1/4 of the
95
 * interval and at most by 1s. */
96
static void
97
0
batchTimerEntry(UA_Timer *t, UA_TimerEntry *te) {
98
0
    if(te->timerPolicy != UA_TIMERPOLICY_CURRENTTIME)
99
0
        return;
100
0
    UA_DateTime deviate = te->interval / 4;
101
0
    if(deviate > UA_DATETIME_SEC)
102
0
        deviate = UA_DATETIME_SEC;
103
0
    earliest = te->nextTime - deviate;
104
0
    latest = te->nextTime + deviate;
105
0
    adjustedNextTime = te->nextTime;
106
0
    ZIP_ITER_KEY(UA_TimerTreeWindow, (UA_TimerTreeWindow*)&t->tree,
107
0
                 &earliest, findTimer2Batch, te);
108
0
    te->nextTime = adjustedNextTime;
109
0
}
110
111
/* Adding repeated callbacks: Add an entry with the "nextTime" timestamp in the
112
 * future. This will be picked up in the next iteration and inserted at the
113
 * correct place. So that the next execution takes place ät "nextTime". */
114
UA_StatusCode
115
UA_Timer_add(UA_Timer *t, UA_Callback callback,
116
             void *application, void *data, UA_Double interval_ms,
117
             UA_DateTime now, UA_DateTime *baseTime,
118
0
             UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) {
119
    /* A callback method needs to be present */
120
0
    if(!callback)
121
0
        return UA_STATUSCODE_BADINTERNALERROR;
122
123
    /* The interval needs to be positive. The exception is for the "once" policy
124
     * where we allow baseTime + interval < now. Then the timer executes once in
125
     * the next processing iteration. */
126
0
    UA_DateTime interval = (UA_DateTime)(interval_ms * UA_DATETIME_MSEC);
127
0
    if(interval <= 0) {
128
0
        if(timerPolicy != UA_TIMERPOLICY_ONCE)
129
0
            return UA_STATUSCODE_BADINTERNALERROR;
130
        /* Ensure that (now + interval) == *baseTime for setting nextTime */
131
0
        if(baseTime) {
132
0
            interval = *baseTime - now;
133
0
            baseTime = NULL;
134
0
        }
135
0
    }
136
137
    /* Compute the first time for execution */
138
0
    UA_DateTime nextTime = (baseTime == NULL) ?
139
0
        now + interval : calculateNextTime(now, *baseTime, interval);
140
141
    /* Allocate the repeated callback structure */
142
0
    UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry));
143
0
    if(!te)
144
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
145
146
    /* Set the repeated callback */
147
0
    te->interval = interval;
148
0
    te->cb = callback;
149
0
    te->application = application;
150
0
    te->data = data;
151
0
    te->nextTime = nextTime;
152
0
    te->timerPolicy = timerPolicy;
153
154
    /* Insert into the timer */
155
0
    UA_LOCK(&t->timerMutex);
156
157
    /* Adjust the nextTime to batch cyclic callbacks */
158
0
    batchTimerEntry(t, te);
159
160
0
    te->id = ++t->idCounter;
161
0
    if(callbackId)
162
0
        *callbackId = te->id;
163
0
    ZIP_INSERT(UA_TimerTree, &t->tree, te);
164
0
    ZIP_INSERT(UA_TimerIdTree, &t->idTree, te);
165
0
    UA_UNLOCK(&t->timerMutex);
166
167
0
    return UA_STATUSCODE_GOOD;
168
0
}
169
170
UA_StatusCode
171
UA_Timer_modify(UA_Timer *t, UA_UInt64 callbackId,
172
                UA_Double interval_ms, UA_DateTime now,
173
0
                UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
174
    /* The interval needs to be positive. The exception is for the "once" policy
175
     * where we allow baseTime + interval < now. Then the timer executes once in
176
     * the next processing iteration. */
177
0
    UA_DateTime interval = (UA_DateTime)(interval_ms * UA_DATETIME_MSEC);
178
0
    if(interval <= 0) {
179
0
        if(timerPolicy != UA_TIMERPOLICY_ONCE)
180
0
            return UA_STATUSCODE_BADINTERNALERROR;
181
        /* Ensure that (now + interval) == *baseTime for setting nextTime */
182
0
        if(baseTime) {
183
0
            interval = *baseTime - now;
184
0
            baseTime = NULL;
185
0
        }
186
0
    }
187
188
0
    UA_LOCK(&t->timerMutex);
189
190
    /* Find timer entry based on id */
191
0
    UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId);
192
0
    if(!te) {
193
0
        UA_UNLOCK(&t->timerMutex);
194
0
        return UA_STATUSCODE_BADNOTFOUND;
195
0
    }
196
197
    /* The entry is either in the timer tree or current processed. If
198
     * in-process, the entry is re-added to the timer-tree right after. */
199
0
    UA_Boolean processing = (ZIP_REMOVE(UA_TimerTree, &t->tree, te) == NULL);
200
201
    /* The nextTime must only be modified after ZIP_REMOVE. The logic is
202
     * identical to the creation of a new timer. */
203
0
    te->nextTime = (baseTime == NULL) ?
204
0
        now + interval : calculateNextTime(now, *baseTime, interval);
205
0
    te->interval = interval;
206
0
    te->timerPolicy = timerPolicy;
207
208
    /* Adjust the nextTime to batch cyclic callbacks */
209
0
    batchTimerEntry(t, te);
210
211
0
    if(processing)
212
0
        te->nextTime -= interval; /* adjust for re-adding after processing */
213
0
    else
214
0
        ZIP_INSERT(UA_TimerTree, &t->tree, te);
215
216
0
    UA_UNLOCK(&t->timerMutex);
217
0
    return UA_STATUSCODE_GOOD;
218
0
}
219
220
void
221
0
UA_Timer_remove(UA_Timer *t, UA_UInt64 callbackId) {
222
0
    UA_LOCK(&t->timerMutex);
223
0
    UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId);
224
0
    if(!te) {
225
0
        UA_UNLOCK(&t->timerMutex);
226
0
        return;
227
0
    }
228
229
    /* The entry is either in the timer tree or in the process tree. If in the
230
     * process tree, leave a sentinel (callback == NULL) to delete it during
231
     * processing. Do not edit the process tree while iterating over it. */
232
0
    UA_Boolean processing = (ZIP_REMOVE(UA_TimerTree, &t->tree, te) == NULL);
233
0
    if(!processing) {
234
0
        ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te);
235
0
        UA_free(te);
236
0
    } else {
237
0
        te->cb = NULL;
238
0
    }
239
240
0
    UA_UNLOCK(&t->timerMutex);
241
0
}
242
243
struct TimerProcessContext {
244
    UA_Timer *t;
245
    UA_DateTime now;
246
};
247
248
static void *
249
0
processEntryCallback(void *context, UA_TimerEntry *te) {
250
0
    struct TimerProcessContext *tpc = (struct TimerProcessContext*)context;
251
0
    UA_Timer *t = tpc->t;
252
253
    /* Execute the callback */
254
0
    if(te->cb) {
255
0
        te->cb(te->application, te->data);
256
0
    }
257
258
    /* Remove the entry if marked for deletion or a "once" policy */
259
0
    if(!te->cb || te->timerPolicy == UA_TIMERPOLICY_ONCE) {
260
0
        ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te);
261
0
        UA_free(te);
262
0
        return NULL;
263
0
    }
264
265
    /* Set the time for the next regular execution */
266
0
    te->nextTime += te->interval;
267
268
    /* Handle the case where the execution "window" was missed. E.g. due to
269
     * congestion of the application or if the clock was shifted.
270
     *
271
     * If the timer policy is "CurrentTime", then there is at least the
272
     * interval between executions. This is used for Monitoreditems, for
273
     * which the spec says: The sampling interval indicates the fastest rate
274
     * at which the Server should sample its underlying source for data
275
     * changes. (Part 4, 5.12.1.2).
276
     *
277
     * Otherwise calculate the next execution time based on the original base
278
     * time. */
279
0
    if(te->nextTime < tpc->now) {
280
0
        te->nextTime = (te->timerPolicy == UA_TIMERPOLICY_CURRENTTIME) ?
281
0
            tpc->now + te->interval :
282
0
            calculateNextTime(tpc->now, te->nextTime, te->interval);
283
0
    }
284
285
    /* Insert back into the time-sorted tree */
286
0
    ZIP_INSERT(UA_TimerTree, &t->tree, te);
287
0
    return NULL;
288
0
}
289
290
UA_DateTime
291
0
UA_Timer_process(UA_Timer *t, UA_DateTime now) {
292
0
    UA_LOCK(&t->timerMutex);
293
294
    /* Move all entries <= now to the processTree */
295
0
    UA_TimerTree processTree;
296
0
    ZIP_INIT(&processTree);
297
0
    ZIP_UNZIP(UA_TimerTree, &t->tree, &now, &processTree, &t->tree);
298
299
    /* Consistency check. The smallest not-processed entry isn't ready. */
300
0
    UA_assert(!ZIP_MIN(UA_TimerTree, &t->tree) ||
301
0
              ZIP_MIN(UA_TimerTree, &t->tree)->nextTime > now);
302
        
303
    /* Iterate over the entries that need processing in-order. This also
304
     * moves them back to the regular time-ordered tree. */
305
0
    struct TimerProcessContext ctx;
306
0
    ctx.t = t;
307
0
    ctx.now = now;
308
0
    ZIP_ITER(UA_TimerTree, &processTree, processEntryCallback, &ctx);
309
        
310
    /* Compute the timestamp of the earliest next callback */
311
0
    UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree);
312
0
    UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX;
313
0
    UA_UNLOCK(&t->timerMutex);
314
0
    return next;
315
0
}
316
317
UA_DateTime
318
0
UA_Timer_next(UA_Timer *t) {
319
0
    UA_LOCK(&t->timerMutex);
320
0
    UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree);
321
0
    UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX;
322
0
    UA_UNLOCK(&t->timerMutex);
323
0
    return next;
324
0
}
325
326
static void *
327
0
freeEntryCallback(void *context, UA_TimerEntry *entry) {
328
0
    UA_free(entry);
329
0
    return NULL;
330
0
}
331
332
void
333
0
UA_Timer_clear(UA_Timer *t) {
334
0
    UA_LOCK(&t->timerMutex);
335
336
0
    ZIP_ITER(UA_TimerIdTree, &t->idTree, freeEntryCallback, NULL);
337
0
    t->tree.root = NULL;
338
0
    t->idTree.root = NULL;
339
0
    t->idCounter = 0;
340
341
0
    UA_UNLOCK(&t->timerMutex);
342
343
0
#if UA_MULTITHREADING >= 100
344
0
    UA_LOCK_DESTROY(&t->timerMutex);
345
0
#endif
346
0
}