/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.42k | cmpDateTime(const UA_DateTime *a, const UA_DateTime *b) { |
13 | 7.42k | if(*a == *b) |
14 | 5.11k | return ZIP_CMP_EQ; |
15 | 2.30k | return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE; |
16 | 7.42k | } |
17 | | |
18 | | static enum ZIP_CMP |
19 | 9.52k | cmpId(const UA_UInt64 *a, const UA_UInt64 *b) { |
20 | 9.52k | if(*a == *b) |
21 | 2.64k | return ZIP_CMP_EQ; |
22 | 6.87k | return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE; |
23 | 9.52k | } |
24 | | |
25 | 91.1k | 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 | 25.9k | 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.6k | UA_Timer_init(UA_Timer *t) { |
48 | 20.6k | memset(t, 0, sizeof(UA_Timer)); |
49 | 20.6k | UA_LOCK_INIT(&t->timerMutex); |
50 | 20.6k | } |
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 | 37 | 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.64k | UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) { |
137 | | /* A callback method needs to be present */ |
138 | 2.64k | 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.64k | UA_DateTime interval = (UA_DateTime)(interval_ms * UA_DATETIME_MSEC); |
145 | 2.64k | 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.64k | UA_DateTime nextTime = (baseTime == NULL) ? |
157 | 2.64k | now + interval : calculateNextTime(now, *baseTime, interval); |
158 | | |
159 | | /* Allocate the repeated callback structure */ |
160 | 2.64k | UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry)); |
161 | 2.64k | if(!te) |
162 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
163 | | |
164 | | /* Set the repeated callback */ |
165 | 2.64k | te->interval = interval; |
166 | 2.64k | te->cb = callback; |
167 | 2.64k | te->application = application; |
168 | 2.64k | te->data = data; |
169 | 2.64k | te->nextTime = nextTime; |
170 | 2.64k | te->timerPolicy = timerPolicy; |
171 | | |
172 | | /* Insert into the timer */ |
173 | 2.64k | UA_LOCK(&t->timerMutex); |
174 | | |
175 | | /* Adjust the nextTime to batch cyclic callbacks */ |
176 | 2.64k | batchTimerEntry(t, te); |
177 | | |
178 | 2.64k | te->id = ++t->idCounter; |
179 | 2.64k | if(callbackId) |
180 | 2.64k | *callbackId = te->id; |
181 | 2.64k | ZIP_INSERT(UA_TimerTree, &t->tree, te); |
182 | 2.64k | ZIP_INSERT(UA_TimerIdTree, &t->idTree, te); |
183 | 2.64k | UA_UNLOCK(&t->timerMutex); |
184 | | |
185 | 2.64k | return UA_STATUSCODE_GOOD; |
186 | 2.64k | } |
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.68k | UA_Timer_remove(UA_Timer *t, UA_UInt64 callbackId) { |
240 | 2.68k | UA_LOCK(&t->timerMutex); |
241 | 2.68k | UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId); |
242 | 2.68k | 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.64k | UA_Boolean processing = (ZIP_REMOVE(UA_TimerTree, &t->tree, te) == NULL); |
251 | 2.64k | if(!processing) { |
252 | 2.64k | ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te); |
253 | 2.64k | UA_free(te); |
254 | 2.64k | } else { |
255 | 0 | te->cb = NULL; |
256 | 0 | } |
257 | | |
258 | 2.64k | UA_UNLOCK(&t->timerMutex); |
259 | 2.64k | } |
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.7k | UA_Timer_process(UA_Timer *t, UA_DateTime now) { |
310 | 20.7k | UA_LOCK(&t->timerMutex); |
311 | | |
312 | | /* Move all entries <= now to the processTree */ |
313 | 20.7k | UA_TimerTree processTree; |
314 | 20.7k | ZIP_INIT(&processTree); |
315 | 20.7k | ZIP_UNZIP(UA_TimerTree, &t->tree, &now, &processTree, &t->tree); |
316 | | |
317 | | /* Consistency check. The smallest not-processed entry isn't ready. */ |
318 | 20.7k | UA_assert(!ZIP_MIN(UA_TimerTree, &t->tree) || |
319 | 20.7k | 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.7k | struct TimerProcessContext ctx; |
324 | 20.7k | ctx.t = t; |
325 | 20.7k | ctx.now = now; |
326 | 20.7k | ZIP_ITER(UA_TimerTree, &processTree, processEntryCallback, &ctx); |
327 | | |
328 | | /* Compute the timestamp of the earliest next callback */ |
329 | 20.7k | UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree); |
330 | 20.7k | UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX; |
331 | 20.7k | UA_UNLOCK(&t->timerMutex); |
332 | 20.7k | return next; |
333 | 20.7k | } |
334 | | |
335 | | UA_DateTime |
336 | 1.45k | UA_Timer_next(UA_Timer *t) { |
337 | 1.45k | UA_LOCK(&t->timerMutex); |
338 | 1.45k | UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree); |
339 | 1.45k | UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX; |
340 | 1.45k | UA_UNLOCK(&t->timerMutex); |
341 | 1.45k | return next; |
342 | 1.45k | } |
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.6k | UA_Timer_clear(UA_Timer *t) { |
352 | 20.6k | UA_LOCK(&t->timerMutex); |
353 | | |
354 | 20.6k | ZIP_ITER(UA_TimerIdTree, &t->idTree, freeEntryCallback, NULL); |
355 | 20.6k | t->tree.root = NULL; |
356 | 20.6k | t->idTree.root = NULL; |
357 | 20.6k | t->idCounter = 0; |
358 | | |
359 | 20.6k | UA_UNLOCK(&t->timerMutex); |
360 | | |
361 | 20.6k | #if UA_MULTITHREADING >= 100 |
362 | 20.6k | UA_LOCK_DESTROY(&t->timerMutex); |
363 | 20.6k | #endif |
364 | 20.6k | } |