|
@@ -0,0 +1,252 @@
|
|
|
+
|
|
|
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
+ * file, You can obtain one at http:
|
|
|
+
|
|
|
+#include "ua_util.h"
|
|
|
+#include "ua_timer.h"
|
|
|
+
|
|
|
+#define MAXTIMEOUT 50
|
|
|
+
|
|
|
+
|
|
|
+ * with the event loop. All other threads may add and remove repeated jobs by
|
|
|
+ * adding entries to the beginning of the addRemoveJobs list (with atomic
|
|
|
+ * operations).
|
|
|
+ *
|
|
|
+ * Adding repeated jobs: Add an entry with the "nextTime" timestamp in the
|
|
|
+ * future. This will be picked up in the next traversal and inserted at the
|
|
|
+ * correct place. So that the next execution takes place ät "nextTime".
|
|
|
+ *
|
|
|
+ * Removing a repeated job: Add an entry with the "nextTime" timestamp set to
|
|
|
+ * UA_INT64_MAX. The next iteration picks this up and removes the repated job
|
|
|
+ * from the linked list. */
|
|
|
+
|
|
|
+struct UA_RepeatedJob;
|
|
|
+typedef struct UA_RepeatedJob UA_RepeatedJob;
|
|
|
+
|
|
|
+struct UA_RepeatedJob {
|
|
|
+ SLIST_ENTRY(UA_RepeatedJob) next;
|
|
|
+ UA_DateTime nextTime;
|
|
|
+ UA_UInt64 interval;
|
|
|
+ UA_Guid id;
|
|
|
+ UA_Job job;
|
|
|
+};
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+UA_RepeatedJobsList_addRepeatedJob(UA_RepeatedJobsList *rjl, const UA_Job job,
|
|
|
+ const UA_UInt32 interval, UA_Guid *jobId) {
|
|
|
+
|
|
|
+ if(interval < 5)
|
|
|
+ return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
+
|
|
|
+
|
|
|
+ UA_UInt64 interval_dt = (UA_UInt64)interval * (UA_UInt64)UA_MSEC_TO_DATETIME;
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob *rj = (UA_RepeatedJob*)UA_malloc(sizeof(UA_RepeatedJob));
|
|
|
+ if(!rj)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+
|
|
|
+
|
|
|
+ rj->interval = interval_dt;
|
|
|
+ rj->id = UA_Guid_random();
|
|
|
+ rj->job = job;
|
|
|
+ rj->nextTime = UA_DateTime_nowMonotonic() + (UA_DateTime)interval_dt;
|
|
|
+
|
|
|
+
|
|
|
+ if(jobId)
|
|
|
+ *jobId = rj->id;
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob *currentFirst;
|
|
|
+ do {
|
|
|
+ currentFirst = SLIST_FIRST(&rjl->addRemoveJobs);
|
|
|
+ SLIST_NEXT(rj, next) = currentFirst;
|
|
|
+ } while(UA_atomic_cmpxchg((void**)&SLIST_FIRST(&rjl->addRemoveJobs), currentFirst, rj) != currentFirst);
|
|
|
+
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+UA_RepeatedJobsList_removeRepeatedJob(UA_RepeatedJobsList *rjl, const UA_Guid jobId) {
|
|
|
+
|
|
|
+ UA_RepeatedJob *rj = (UA_RepeatedJob*)UA_malloc(sizeof(UA_RepeatedJob));
|
|
|
+ if(!rj)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+
|
|
|
+
|
|
|
+ rj->id = jobId;
|
|
|
+ rj->nextTime = UA_INT64_MAX;
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob *currentFirst;
|
|
|
+ do {
|
|
|
+ currentFirst = SLIST_FIRST(&rjl->addRemoveJobs);
|
|
|
+ SLIST_NEXT(rj, next) = currentFirst;
|
|
|
+ } while(UA_atomic_cmpxchg((void**)&SLIST_FIRST(&rjl->addRemoveJobs), currentFirst, rj) != currentFirst);
|
|
|
+
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+insertRepeatedJob(UA_RepeatedJobsList *rjl,
|
|
|
+ UA_RepeatedJob * UA_RESTRICT rj,
|
|
|
+ UA_DateTime nowMonotonic) {
|
|
|
+
|
|
|
+ rj->nextTime = nowMonotonic + (UA_Int64)rj->interval;
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob *tmpRj, *afterRj = NULL;
|
|
|
+ SLIST_FOREACH(tmpRj, &rjl->repeatedJobs, next) {
|
|
|
+ if(tmpRj->nextTime >= rj->nextTime)
|
|
|
+ break;
|
|
|
+ afterRj = tmpRj;
|
|
|
+
|
|
|
+
|
|
|
+ * interval in a "block" in order to reduce linear search for re-entry
|
|
|
+ * to the sorted list after processing. Allow the first execution to lie
|
|
|
+ * between "nextTime - 1s" and "nextTime" if this adjustment groups jobs
|
|
|
+ * with the same repetition interval. */
|
|
|
+ if(tmpRj->interval == rj->interval &&
|
|
|
+ tmpRj->nextTime > (rj->nextTime - UA_SEC_TO_DATETIME))
|
|
|
+ rj->nextTime = tmpRj->nextTime;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if(afterRj)
|
|
|
+ SLIST_INSERT_AFTER(afterRj, rj, next);
|
|
|
+ else
|
|
|
+ SLIST_INSERT_HEAD(&rjl->repeatedJobs, rj, next);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+removeRepeatedJob(UA_RepeatedJobsList *rjl, const UA_Guid *jobId) {
|
|
|
+ UA_RepeatedJob *rj, *prev = NULL;
|
|
|
+ SLIST_FOREACH(rj, &rjl->repeatedJobs, next) {
|
|
|
+ if(UA_Guid_equal(jobId, &rj->id)) {
|
|
|
+ if(prev)
|
|
|
+ SLIST_REMOVE_AFTER(prev, next);
|
|
|
+ else
|
|
|
+ SLIST_REMOVE_HEAD(&rjl->repeatedJobs, next);
|
|
|
+ UA_free(rj);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ prev = rj;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static UA_DateTime
|
|
|
+nextRepetition(const UA_RepeatedJobsList *rjl, UA_DateTime nowMonotonic) {
|
|
|
+
|
|
|
+ * Return the duration until the next job or the max interval. */
|
|
|
+ UA_RepeatedJob *first = SLIST_FIRST(&rjl->repeatedJobs);
|
|
|
+ UA_DateTime next = nowMonotonic + (MAXTIMEOUT * UA_MSEC_TO_DATETIME);
|
|
|
+ if(first && first->nextTime < next)
|
|
|
+ next = first->nextTime;
|
|
|
+ return next;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+processAddRemoveJobs(UA_RepeatedJobsList *rjl, UA_DateTime nowMonotonic) {
|
|
|
+ UA_RepeatedJob *current;
|
|
|
+ while((current = SLIST_FIRST(&rjl->addRemoveJobs))) {
|
|
|
+ SLIST_REMOVE_HEAD(&rjl->addRemoveJobs, next);
|
|
|
+ if(current->nextTime < UA_INT64_MAX) {
|
|
|
+ insertRepeatedJob(rjl, current, nowMonotonic);
|
|
|
+ } else {
|
|
|
+ removeRepeatedJob(rjl, ¤t->id);
|
|
|
+ UA_free(current);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+UA_DateTime
|
|
|
+UA_RepeatedJobsList_process(UA_RepeatedJobsList *rjl,
|
|
|
+ UA_DateTime nowMonotonic,
|
|
|
+ UA_Boolean *dispatched) {
|
|
|
+
|
|
|
+ processAddRemoveJobs(rjl, nowMonotonic);
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob *firstAfter, *lastNow = NULL;
|
|
|
+ SLIST_FOREACH(firstAfter, &rjl->repeatedJobs, next) {
|
|
|
+ if(firstAfter->nextTime > nowMonotonic)
|
|
|
+ break;
|
|
|
+ lastNow = firstAfter;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if(!lastNow)
|
|
|
+ return nextRepetition(rjl, nowMonotonic);
|
|
|
+
|
|
|
+
|
|
|
+ struct memberstruct(UA_RepeatedJobsList,RepeatedJobsSList) executedNowList;
|
|
|
+ executedNowList.slh_first = SLIST_FIRST(&rjl->repeatedJobs);
|
|
|
+ lastNow->next.sle_next = NULL;
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob tmp_first;
|
|
|
+ tmp_first.nextTime = nowMonotonic - 1;
|
|
|
+ tmp_first.next.sle_next = firstAfter;
|
|
|
+ UA_RepeatedJob *last_dispatched = &tmp_first;
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob *rj;
|
|
|
+ while((rj = SLIST_FIRST(&executedNowList))) {
|
|
|
+
|
|
|
+ SLIST_REMOVE_HEAD(&executedNowList, next);
|
|
|
+
|
|
|
+
|
|
|
+ rjl->processCallback(rjl->processContext, &rj->job);
|
|
|
+ *dispatched = true;
|
|
|
+
|
|
|
+
|
|
|
+ * forcing the next processing into the next iteration. */
|
|
|
+ rj->nextTime += (UA_Int64)rj->interval;
|
|
|
+ if(rj->nextTime < nowMonotonic)
|
|
|
+ rj->nextTime = nowMonotonic + 1;
|
|
|
+
|
|
|
+
|
|
|
+ UA_RepeatedJob *prev_rj;
|
|
|
+ if(last_dispatched->nextTime == rj->nextTime) {
|
|
|
+
|
|
|
+ * addRepeatedJobs. So this might occur quite often. */
|
|
|
+ UA_assert(last_dispatched != &tmp_first);
|
|
|
+ prev_rj = last_dispatched;
|
|
|
+ } else {
|
|
|
+
|
|
|
+ * starting at the first possible job */
|
|
|
+ prev_rj = &tmp_first;
|
|
|
+ while(true) {
|
|
|
+ UA_RepeatedJob *n = SLIST_NEXT(prev_rj, next);
|
|
|
+ if(!n || n->nextTime >= rj->nextTime)
|
|
|
+ break;
|
|
|
+ prev_rj = n;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ last_dispatched = rj;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ SLIST_INSERT_AFTER(prev_rj, rj, next);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ rjl->repeatedJobs.slh_first = tmp_first.next.sle_next;
|
|
|
+
|
|
|
+
|
|
|
+ * added a job. So we get the returned timeout right. */
|
|
|
+ processAddRemoveJobs(rjl, nowMonotonic);
|
|
|
+
|
|
|
+
|
|
|
+ return nextRepetition(rjl, nowMonotonic);
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+UA_RepeatedJobsList_deleteMembers(UA_RepeatedJobsList *rjl) {
|
|
|
+ UA_RepeatedJob *current;
|
|
|
+ while((current = SLIST_FIRST(&rjl->repeatedJobs))) {
|
|
|
+ SLIST_REMOVE_HEAD(&rjl->repeatedJobs, next);
|
|
|
+ UA_free(current);
|
|
|
+ }
|
|
|
+}
|