Coverage Report

Created: 2026-11-05 06:54

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