/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 | } |