/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 | 125 | cmpId(const UA_UInt64 *a, const UA_UInt64 *b) { |
20 | 125 | if(*a == *b) |
21 | 125 | return ZIP_CMP_EQ; |
22 | 0 | return (*a < *b) ? ZIP_CMP_LESS : ZIP_CMP_MORE; |
23 | 125 | } |
24 | | |
25 | 9.43k | 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 | 2.54k | 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 | 2.29k | UA_Timer_init(UA_Timer *t) { |
48 | 2.29k | memset(t, 0, sizeof(UA_Timer)); |
49 | 2.29k | UA_LOCK_INIT(&t->timerMutex); |
50 | 2.29k | } |
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 | 125 | UA_DateTime earliest, UA_DateTime latest) { |
57 | 125 | if(!compare) |
58 | 125 | return false; |
59 | 0 | if(compare->nextTime < earliest) |
60 | 0 | return findTimer2Batch(ZIP_RIGHT(compare, treeEntry), te, earliest, latest); |
61 | 0 | if(compare->nextTime > latest) |
62 | 0 | return findTimer2Batch(ZIP_LEFT(compare, treeEntry), te, earliest, latest); |
63 | | |
64 | 0 | if(findTimer2Batch(ZIP_LEFT(compare, treeEntry), te, earliest, latest)) |
65 | 0 | return true; |
66 | | |
67 | | /* Ignore one-shot timers and require one interval to divide the other. */ |
68 | 0 | if(compare->interval > 0 && |
69 | 0 | (te->interval % compare->interval == 0 || |
70 | 0 | compare->interval % te->interval == 0)) { |
71 | 0 | te->nextTime = compare->nextTime; |
72 | 0 | if(te->interval == compare->interval) |
73 | 0 | return true; |
74 | 0 | } |
75 | 0 | return findTimer2Batch(ZIP_RIGHT(compare, treeEntry), te, earliest, latest); |
76 | 0 | } |
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 | 125 | batchTimerEntry(UA_Timer *t, UA_TimerEntry *te) { |
83 | 125 | if(te->timerPolicy != UA_TIMERPOLICY_CURRENTTIME || te->interval == 0) |
84 | 0 | return; |
85 | 125 | UA_DateTime deviate = te->interval / 4; |
86 | 125 | if(deviate > UA_DATETIME_SEC) |
87 | 34 | deviate = UA_DATETIME_SEC; |
88 | 125 | findTimer2Batch(ZIP_ROOT(&t->tree), te, |
89 | 125 | te->nextTime - deviate, te->nextTime + deviate); |
90 | 125 | } |
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 | 125 | UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) { |
100 | | /* A callback method needs to be present */ |
101 | 125 | 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 | 125 | UA_DateTime interval = (UA_DateTime)(interval_ms * UA_DATETIME_MSEC); |
108 | 125 | 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 | 125 | UA_DateTime nextTime = (baseTime == NULL) ? |
120 | 125 | now + interval : calculateNextTime(now, *baseTime, interval); |
121 | | |
122 | | /* Allocate the repeated callback structure */ |
123 | 125 | UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry)); |
124 | 125 | if(!te) |
125 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
126 | | |
127 | | /* Set the repeated callback */ |
128 | 125 | te->interval = interval; |
129 | 125 | te->cb = callback; |
130 | 125 | te->application = application; |
131 | 125 | te->data = data; |
132 | 125 | te->nextTime = nextTime; |
133 | 125 | te->timerPolicy = timerPolicy; |
134 | | |
135 | | /* Insert into the timer */ |
136 | 125 | UA_LOCK(&t->timerMutex); |
137 | | |
138 | | /* Adjust the nextTime to batch cyclic callbacks */ |
139 | 125 | batchTimerEntry(t, te); |
140 | | |
141 | 125 | te->id = ++t->idCounter; |
142 | 125 | if(callbackId) |
143 | 125 | *callbackId = te->id; |
144 | 125 | ZIP_INSERT(UA_TimerTree, &t->tree, te); |
145 | 125 | ZIP_INSERT(UA_TimerIdTree, &t->idTree, te); |
146 | 125 | UA_UNLOCK(&t->timerMutex); |
147 | | |
148 | 125 | return UA_STATUSCODE_GOOD; |
149 | 125 | } |
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 | | /* The entry is either in the timer tree or current processed. If |
179 | | * in-process, the entry is re-added to the timer-tree right after. */ |
180 | 0 | UA_Boolean processing = (ZIP_REMOVE(UA_TimerTree, &t->tree, te) == NULL); |
181 | | |
182 | | /* The nextTime must only be modified after ZIP_REMOVE. The logic is |
183 | | * identical to the creation of a new timer. */ |
184 | 0 | te->nextTime = (baseTime == NULL) ? |
185 | 0 | now + interval : calculateNextTime(now, *baseTime, interval); |
186 | 0 | te->interval = interval; |
187 | 0 | te->timerPolicy = timerPolicy; |
188 | | |
189 | | /* Adjust the nextTime to batch cyclic callbacks */ |
190 | 0 | batchTimerEntry(t, te); |
191 | |
|
192 | 0 | if(processing) |
193 | 0 | te->nextTime -= interval; /* adjust for re-adding after processing */ |
194 | 0 | else |
195 | 0 | ZIP_INSERT(UA_TimerTree, &t->tree, te); |
196 | |
|
197 | 0 | UA_UNLOCK(&t->timerMutex); |
198 | 0 | return UA_STATUSCODE_GOOD; |
199 | 0 | } |
200 | | |
201 | | void |
202 | 125 | UA_Timer_remove(UA_Timer *t, UA_UInt64 callbackId) { |
203 | 125 | UA_LOCK(&t->timerMutex); |
204 | 125 | UA_TimerEntry *te = ZIP_FIND(UA_TimerIdTree, &t->idTree, &callbackId); |
205 | 125 | if(!te) { |
206 | 0 | UA_UNLOCK(&t->timerMutex); |
207 | 0 | return; |
208 | 0 | } |
209 | | |
210 | | /* The entry is either in the timer tree or in the process tree. If in the |
211 | | * process tree, leave a sentinel (callback == NULL) to delete it during |
212 | | * processing. Do not edit the process tree while iterating over it. */ |
213 | 125 | UA_Boolean processing = (ZIP_REMOVE(UA_TimerTree, &t->tree, te) == NULL); |
214 | 125 | if(!processing) { |
215 | 125 | ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te); |
216 | 125 | UA_free(te); |
217 | 125 | } else { |
218 | 0 | te->cb = NULL; |
219 | 0 | } |
220 | | |
221 | 125 | UA_UNLOCK(&t->timerMutex); |
222 | 125 | } |
223 | | |
224 | | struct TimerProcessContext { |
225 | | UA_Timer *t; |
226 | | UA_DateTime now; |
227 | | }; |
228 | | |
229 | | static void * |
230 | 0 | processEntryCallback(void *context, UA_TimerEntry *te) { |
231 | 0 | struct TimerProcessContext *tpc = (struct TimerProcessContext*)context; |
232 | 0 | UA_Timer *t = tpc->t; |
233 | | |
234 | | /* Execute the callback */ |
235 | 0 | if(te->cb) { |
236 | 0 | te->cb(te->application, te->data); |
237 | 0 | } |
238 | | |
239 | | /* Remove the entry if marked for deletion or a "once" policy */ |
240 | 0 | if(!te->cb || te->timerPolicy == UA_TIMERPOLICY_ONCE) { |
241 | 0 | ZIP_REMOVE(UA_TimerIdTree, &t->idTree, te); |
242 | 0 | UA_free(te); |
243 | 0 | return NULL; |
244 | 0 | } |
245 | | |
246 | | /* Set the time for the next regular execution */ |
247 | 0 | te->nextTime += te->interval; |
248 | | |
249 | | /* Handle the case where the execution "window" was missed. E.g. due to |
250 | | * congestion of the application or if the clock was shifted. |
251 | | * |
252 | | * If the timer policy is "CurrentTime", then there is at least the |
253 | | * interval between executions. This is used for Monitoreditems, for |
254 | | * which the spec says: The sampling interval indicates the fastest rate |
255 | | * at which the Server should sample its underlying source for data |
256 | | * changes. (Part 4, 5.12.1.2). |
257 | | * |
258 | | * Otherwise calculate the next execution time based on the original base |
259 | | * time. */ |
260 | 0 | if(te->nextTime < tpc->now) { |
261 | 0 | te->nextTime = (te->timerPolicy == UA_TIMERPOLICY_CURRENTTIME) ? |
262 | 0 | tpc->now + te->interval : |
263 | 0 | calculateNextTime(tpc->now, te->nextTime, te->interval); |
264 | 0 | } |
265 | | |
266 | | /* Insert back into the time-sorted tree */ |
267 | 0 | ZIP_INSERT(UA_TimerTree, &t->tree, te); |
268 | 0 | return NULL; |
269 | 0 | } |
270 | | |
271 | | UA_DateTime |
272 | 2.29k | UA_Timer_process(UA_Timer *t, UA_DateTime now) { |
273 | 2.29k | UA_LOCK(&t->timerMutex); |
274 | | |
275 | | /* Move all entries <= now to the processTree */ |
276 | 2.29k | UA_TimerTree processTree; |
277 | 2.29k | ZIP_INIT(&processTree); |
278 | 2.29k | ZIP_UNZIP(UA_TimerTree, &t->tree, &now, &processTree, &t->tree); |
279 | | |
280 | | /* Consistency check. The smallest not-processed entry isn't ready. */ |
281 | 2.29k | UA_assert(!ZIP_MIN(UA_TimerTree, &t->tree) || |
282 | 2.29k | ZIP_MIN(UA_TimerTree, &t->tree)->nextTime > now); |
283 | | |
284 | | /* Iterate over the entries that need processing in-order. This also |
285 | | * moves them back to the regular time-ordered tree. */ |
286 | 2.29k | struct TimerProcessContext ctx; |
287 | 2.29k | ctx.t = t; |
288 | 2.29k | ctx.now = now; |
289 | 2.29k | ZIP_ITER(UA_TimerTree, &processTree, processEntryCallback, &ctx); |
290 | | |
291 | | /* Compute the timestamp of the earliest next callback */ |
292 | 2.29k | UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree); |
293 | 2.29k | UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX; |
294 | 2.29k | UA_UNLOCK(&t->timerMutex); |
295 | 2.29k | return next; |
296 | 2.29k | } |
297 | | |
298 | | UA_DateTime |
299 | 0 | UA_Timer_next(UA_Timer *t) { |
300 | 0 | UA_LOCK(&t->timerMutex); |
301 | 0 | UA_TimerEntry *first = ZIP_MIN(UA_TimerTree, &t->tree); |
302 | 0 | UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX; |
303 | 0 | UA_UNLOCK(&t->timerMutex); |
304 | 0 | return next; |
305 | 0 | } |
306 | | |
307 | | static void * |
308 | 0 | freeEntryCallback(void *context, UA_TimerEntry *entry) { |
309 | 0 | UA_free(entry); |
310 | 0 | return NULL; |
311 | 0 | } |
312 | | |
313 | | void |
314 | 2.29k | UA_Timer_clear(UA_Timer *t) { |
315 | 2.29k | UA_LOCK(&t->timerMutex); |
316 | | |
317 | 2.29k | ZIP_ITER(UA_TimerIdTree, &t->idTree, freeEntryCallback, NULL); |
318 | 2.29k | t->tree.root = NULL; |
319 | 2.29k | t->idTree.root = NULL; |
320 | 2.29k | t->idCounter = 0; |
321 | | |
322 | 2.29k | UA_UNLOCK(&t->timerMutex); |
323 | | |
324 | 2.29k | #if UA_MULTITHREADING >= 100 |
325 | 2.29k | UA_LOCK_DESTROY(&t->timerMutex); |
326 | 2.29k | #endif |
327 | 2.29k | } |