Coverage Report

Created: 2026-09-28 07:11

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