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