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