Coverage Report

Created: 2023-06-07 06:06

/src/open62541/arch/common/ua_timer.c
Line
Count
Source (jump to first uncovered line)
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 "ua_timer.h"
10
11
static enum ZIP_CMP
12
3.63k
cmpDateTime(const UA_DateTime *a, const UA_DateTime *b) {
13
3.63k
    if(*a == *b)
14
773
        return ZIP_CMP_EQ;
15
2.86k
    return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE;
16
3.63k
}
17
18
static enum ZIP_CMP
19
4.96k
cmpId(const UA_UInt64 *a, const UA_UInt64 *b) {
20
4.96k
    if(*a == *b)
21
1.90k
        return ZIP_CMP_EQ;
22
3.05k
    return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE;
23
4.96k
}
24
25
ZIP_FUNCTIONS(UA_TimerTree, UA_TimerEntry, treeEntry, UA_DateTime, nextTime, cmpDateTime)
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
1.20k
UA_Timer_init(UA_Timer *t) {
48
1.20k
    memset(t, 0, sizeof(UA_Timer));
49
1.20k
    UA_LOCK_INIT(&t->timerMutex);
50
1.20k
}
51
52
static UA_StatusCode
53
addCallback(UA_Timer *t, UA_ApplicationCallback callback, void *application,
54
            void *data, UA_DateTime nextTime, UA_UInt64 interval,
55
1.90k
            UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) {
56
    /* A callback method needs to be present */
57
1.90k
    if(!callback)
58
0
        return UA_STATUSCODE_BADINTERNALERROR;
59
60
    /* Allocate the repeated callback structure */
61
1.90k
    UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry));
62
1.90k
    if(!te)
63
1
        return UA_STATUSCODE_BADOUTOFMEMORY;
64
65
    /* Set the repeated callback */
66
1.90k
    te->interval = (UA_UInt64)interval;
67
1.90k
    te->id = ++t->idCounter;
68
1.90k
    te->callback = callback;
69
1.90k
    te->application = application;
70
1.90k
    te->data = data;
71
1.90k
    te->nextTime = nextTime;
72
1.90k
    te->timerPolicy = timerPolicy;
73
74
    /* Set the output identifier */
75
1.90k
    if(callbackId)
76
1.90k
        *callbackId = te->id;
77
78
1.90k
    ZIP_INSERT(UA_TimerTree, &t->tree, te);
79
1.90k
    ZIP_INSERT(UA_TimerIdTree, &t->idTree, te);
80
1.90k
    return UA_STATUSCODE_GOOD;
81
1.90k
}
82
83
UA_StatusCode
84
UA_Timer_addTimedCallback(UA_Timer *t, UA_ApplicationCallback callback,
85
                          void *application, void *data, UA_DateTime date,
86
0
                          UA_UInt64 *callbackId) {
87
0
    UA_LOCK(&t->timerMutex);
88
0
    UA_StatusCode res = addCallback(t, callback, application, data, date,
89
0
                                    0, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME,
90
0
                                    callbackId);
91
0
    UA_UNLOCK(&t->timerMutex);
92
0
    return res;
93
0
}
94
95
/* Adding repeated callbacks: Add an entry with the "nextTime" timestamp in the
96
 * future. This will be picked up in the next iteration and inserted at the
97
 * correct place. So that the next execution takes place ät "nextTime". */
98
UA_StatusCode
99
UA_Timer_addRepeatedCallback(UA_Timer *t, UA_ApplicationCallback callback,
100
                             void *application, void *data, UA_Double interval_ms,
101
                             UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
102
1.90k
                             UA_UInt64 *callbackId) {
103
    /* The interval needs to be positive */
104
1.90k
    if(interval_ms <= 0.0)
105
0
        return UA_STATUSCODE_BADINTERNALERROR;
106
1.90k
    UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
107
1.90k
    if(interval == 0)
108
0
        return UA_STATUSCODE_BADINTERNALERROR;
109
110
    /* Compute the first time for execution */
111
1.90k
    UA_DateTime currentTime = UA_DateTime_nowMonotonic();
112
1.90k
    UA_DateTime nextTime;
113
1.90k
    if(baseTime == NULL) {
114
        /* Use "now" as the basetime */
115
1.90k
        nextTime = currentTime + (UA_DateTime)interval;
116
1.90k
    } else {
117
0
        nextTime = calculateNextTime(currentTime, *baseTime, (UA_DateTime)interval);
118
0
    }
119
120
1.90k
    UA_LOCK(&t->timerMutex);
121
1.90k
    UA_StatusCode res = addCallback(t, callback, application, data, nextTime,
122
1.90k
                                    interval, timerPolicy, callbackId);
123
1.90k
    UA_UNLOCK(&t->timerMutex);
124
1.90k
    return res;
125
1.90k
}
126
127
UA_StatusCode
128
UA_Timer_changeRepeatedCallback(UA_Timer *t, UA_UInt64 callbackId,
129
                                UA_Double interval_ms, UA_DateTime *baseTime,
130
0
                                UA_TimerPolicy timerPolicy) {
131
    /* The interval needs to be positive */
132
0
    if(interval_ms <= 0.0)
133
0
        return UA_STATUSCODE_BADINTERNALERROR;
134
0
    UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
135
0
    if(interval == 0)
136
0
        return UA_STATUSCODE_BADINTERNALERROR;
137
138
0
    UA_LOCK(&t->timerMutex);
139
140
    /* Remove from the sorted tree */
141
0
    UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId);
142
0
    if(!te) {
143
0
        UA_UNLOCK(&t->timerMutex);
144
0
        return UA_STATUSCODE_BADNOTFOUND;
145
0
    }
146
0
    ZIP_REMOVE(UA_TimerTree, &t->tree, te);
147
148
    /* Compute the next time for execution. The logic is identical to the
149
     * creation of a new repeated callback. */
150
0
    UA_DateTime currentTime = UA_DateTime_nowMonotonic();
151
0
    if(baseTime == NULL) {
152
        /* Use "now" as the basetime */
153
0
        te->nextTime = currentTime + (UA_DateTime)interval;
154
0
    } else {
155
0
        te->nextTime = calculateNextTime(currentTime, *baseTime, (UA_DateTime)interval);
156
0
    }
157
158
    /* Update the remaining parameters and re-insert */
159
0
    te->interval = interval;
160
0
    te->timerPolicy = timerPolicy;
161
0
    ZIP_INSERT(UA_TimerTree, &t->tree, te);
162
163
0
    UA_UNLOCK(&t->timerMutex);
164
0
    return UA_STATUSCODE_GOOD;
165
0
}
166
167
void
168
1.90k
UA_Timer_removeCallback(UA_Timer *t, UA_UInt64 callbackId) {
169
1.90k
    UA_LOCK(&t->timerMutex);
170
1.90k
    UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId);
171
1.90k
    if(UA_LIKELY(te != NULL)) {
172
1.90k
        if(t->processTree.root == NULL) {
173
            /* Remove/free the entry */
174
1.90k
            ZIP_REMOVE(UA_TimerTree, &t->tree, te);
175
1.90k
            ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te);
176
1.90k
            UA_free(te);
177
1.90k
        } else {
178
            /* We are currently processing. Only mark the entry to be deleted.
179
             * Will be removed/freed the next time we reach it in the processing
180
             * callback. */
181
0
            te->callback = NULL;
182
0
        }
183
1.90k
    }
184
1.90k
    UA_UNLOCK(&t->timerMutex);
185
1.90k
}
186
187
struct TimerProcessContext {
188
    UA_Timer *t;
189
    UA_DateTime nowMonotonic;
190
};
191
192
static void *
193
0
processEntryCallback(void *context, UA_TimerEntry *te) {
194
0
    struct TimerProcessContext *tpc = (struct TimerProcessContext*)context;
195
0
    UA_Timer *t = tpc->t;
196
197
    /* Execute the callback. The memory is not freed during the callback.
198
     * Instead, whenever t->processTree != NULL, the entries are only marked for
199
     * deletion by setting elm->callback to NULL. */
200
0
    if(te->callback) {
201
0
        UA_UNLOCK(&t->timerMutex);
202
0
        te->callback(te->application, te->data);
203
0
        UA_LOCK(&t->timerMutex);
204
0
    }
205
206
    /* Remove and free the entry if marked for deletion or a one-time timed
207
     * callback */
208
0
    if(!te->callback || te->interval == 0) {
209
0
        ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te);
210
0
        UA_free(te);
211
0
        return NULL;
212
0
    }
213
214
    /* Set the time for the next regular execution */
215
0
    te->nextTime += (UA_DateTime)te->interval;
216
217
    /* Handle the case where the "window" was missed. E.g. due to congestion of
218
     * the application or if the clock was shifted.
219
     *
220
     * If the timer policy is "CurrentTime", then there is at least the
221
     * interval between executions. This is used for Monitoreditems, for
222
     * which the spec says: The sampling interval indicates the fastest rate
223
     * at which the Server should sample its underlying source for data
224
     * changes. (Part 4, 5.12.1.2) */
225
0
    if(te->nextTime < tpc->nowMonotonic) {
226
0
        if(te->timerPolicy == UA_TIMER_HANDLE_CYCLEMISS_WITH_BASETIME)
227
0
            te->nextTime = calculateNextTime(tpc->nowMonotonic, te->nextTime,
228
0
                                              (UA_DateTime)te->interval);
229
0
        else
230
0
            te->nextTime = tpc->nowMonotonic + (UA_DateTime)te->interval;
231
0
    }
232
233
    /* Insert back into the time-sorted tree */
234
0
    ZIP_INSERT(UA_TimerTree, &t->tree, te);
235
0
    return NULL;
236
0
}
237
238
UA_DateTime
239
584
UA_Timer_process(UA_Timer *t, UA_DateTime nowMonotonic) {
240
584
    UA_LOCK(&t->timerMutex);
241
242
    /* Not reentrant. Don't call _process from within _process. */
243
584
    if(!t->processTree.root) {
244
        /* Move all entries <= nowMonotonic to processTree */
245
584
        ZIP_UNZIP(UA_TimerTree, &t->tree, &nowMonotonic,
246
584
                  &t->processTree, &t->tree);
247
248
        /* Consistency check. The smallest not-processed entry isn't ready. */
249
584
        UA_assert(!ZIP_MIN(UA_TimerTree, &t->tree) ||
250
0
                  ZIP_MIN(UA_TimerTree, &t->tree)->nextTime > nowMonotonic);
251
        
252
        /* Iterate over the entries that need processing in-order. This also
253
         * moves them back to the regular time-ordered tree. */
254
0
        struct TimerProcessContext ctx;
255
584
        ctx.t = t;
256
584
        ctx.nowMonotonic = nowMonotonic;
257
584
        ZIP_ITER(UA_TimerTree, &t->processTree, processEntryCallback, &ctx);
258
        
259
        /* Reset processTree. All entries are already moved to the normal tree. */
260
584
        t->processTree.root = NULL;
261
584
    }
262
263
    /* Compute the timestamp of the earliest next callback */
264
584
    UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree);
265
584
    UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX;
266
584
    if(next < nowMonotonic)
267
0
        next = nowMonotonic;
268
269
584
    UA_UNLOCK(&t->timerMutex);
270
584
    return next;
271
584
}
272
273
UA_DateTime
274
434
UA_Timer_nextRepeatedTime(UA_Timer *t) {
275
434
    UA_LOCK(&t->timerMutex);
276
434
    UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree);
277
434
    UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX;
278
434
    UA_UNLOCK(&t->timerMutex);
279
434
    return next;
280
434
}
281
282
static void *
283
0
freeEntryCallback(void *context, UA_TimerEntry *entry) {
284
0
    UA_free(entry);
285
0
    return NULL;
286
0
}
287
288
void
289
1.20k
UA_Timer_clear(UA_Timer *t) {
290
1.20k
    UA_LOCK(&t->timerMutex);
291
292
1.20k
    ZIP_ITER(UA_TimerIdTree, &t->idTree, freeEntryCallback, NULL);
293
1.20k
    t->tree.root = NULL;
294
1.20k
    t->idTree.root = NULL;
295
1.20k
    t->idCounter = 0;
296
297
1.20k
    UA_UNLOCK(&t->timerMutex);
298
299
1.20k
#if UA_MULTITHREADING >= 100
300
1.20k
    UA_LOCK_DESTROY(&t->timerMutex);
301
1.20k
#endif
302
1.20k
}