Lely core libraries 2.3.4
timer.c
Go to the documentation of this file.
1
24#include "../timer.h"
25
26#if !LELY_NO_MALLOC
27
28#if !LELY_NO_THREADS
29#include <lely/libc/threads.h>
30#endif
31#include <lely/io2/ctx.h>
32#include <lely/io2/user/timer.h>
33#include <lely/util/errnum.h>
34#include <lely/util/time.h>
35#include <lely/util/util.h>
36
37#include <assert.h>
38#include <stdlib.h>
39
40static io_ctx_t *io_user_timer_dev_get_ctx(const io_dev_t *dev);
41static ev_exec_t *io_user_timer_dev_get_exec(const io_dev_t *dev);
42static size_t io_user_timer_dev_cancel(io_dev_t *dev, struct ev_task *task);
43static size_t io_user_timer_dev_abort(io_dev_t *dev, struct ev_task *task);
44
45// clang-format off
46static const struct io_dev_vtbl io_user_timer_dev_vtbl = {
47 &io_user_timer_dev_get_ctx,
48 &io_user_timer_dev_get_exec,
49 &io_user_timer_dev_cancel,
50 &io_user_timer_dev_abort
51};
52// clang-format on
53
54static io_dev_t *io_user_timer_get_dev(const io_timer_t *timer);
55static io_clock_t *io_user_timer_get_clock(const io_timer_t *timer);
56static int io_user_timer_getoverrun(const io_timer_t *timer);
57static int io_user_timer_gettime(
58 const io_timer_t *timer, struct itimerspec *value);
59static int io_user_timer_settime(io_timer_t *timer, int flags,
60 const struct itimerspec *value, struct itimerspec *ovalue);
61static void io_user_timer_submit_wait(
62 io_timer_t *timer, struct io_timer_wait *wait);
63
64// clang-format off
65static const struct io_timer_vtbl io_user_timer_vtbl = {
66 &io_user_timer_get_dev,
67 &io_user_timer_get_clock,
68 &io_user_timer_getoverrun,
69 &io_user_timer_gettime,
70 &io_user_timer_settime,
71 &io_user_timer_submit_wait
72};
73// clang-format on
74
75static int io_user_timer_clock_getres(
76 const io_clock_t *clock, struct timespec *res);
77static int io_user_timer_clock_gettime(
78 const io_clock_t *clock, struct timespec *tp);
79static int io_user_timer_clock_settime(
80 io_clock_t *clock, const struct timespec *tp);
81
82// clang-format off
83static const struct io_clock_vtbl io_user_timer_clock_vtbl = {
84 &io_user_timer_clock_getres,
85 &io_user_timer_clock_gettime,
86 &io_user_timer_clock_settime
87};
88// clang-format on
89
90static void io_user_timer_svc_shutdown(struct io_svc *svc);
91
92// clang-format off
93static const struct io_svc_vtbl io_user_timer_svc_vtbl = {
94 NULL,
95 &io_user_timer_svc_shutdown
96};
97// clang-format on
98
100 const struct io_dev_vtbl *dev_vptr;
101 const struct io_timer_vtbl *timer_vptr;
102 const struct io_clock_vtbl *clock_vptr;
103 struct io_svc svc;
104 io_ctx_t *ctx;
105 ev_exec_t *exec;
107 void *arg;
108#if !LELY_NO_THREADS
109 mtx_t mtx;
110#endif
111 int shutdown;
112 struct sllist queue;
113 struct timespec now;
114 struct timespec prev;
115 struct timespec next;
116 struct itimerspec value;
117 int overrun;
118};
119
120static inline struct io_user_timer *io_user_timer_from_dev(const io_dev_t *dev);
121static inline struct io_user_timer *io_user_timer_from_timer(
122 const io_timer_t *timer);
123static inline struct io_user_timer *io_user_timer_from_clock(
124 const io_clock_t *clock);
125static inline struct io_user_timer *io_user_timer_from_svc(
126 const struct io_svc *svc);
127
128static void io_user_timer_pop(struct io_user_timer *user, struct sllist *queue,
129 struct ev_task *task);
130
131static void io_user_timer_do_settime(struct io_user_timer *user);
132
133static inline int timespec_valid(const struct timespec *tp);
134
135void *
136io_user_timer_alloc(void)
137{
138 struct io_user_timer *user = malloc(sizeof(*user));
139 if (!user) {
140#if !LELY_NO_ERRNO
141 set_errc(errno2c(errno));
142#endif
143 return NULL;
144 }
145 // Suppress a GCC maybe-uninitialized warning.
146 user->timer_vptr = NULL;
147 // cppcheck-suppress memleak symbolName=user
148 return &user->timer_vptr;
149}
150
151void
152io_user_timer_free(void *ptr)
153{
154 if (ptr)
155 free(io_user_timer_from_timer(ptr));
156}
157
159io_user_timer_init(io_timer_t *timer, io_ctx_t *ctx, ev_exec_t *exec,
160 io_user_timer_setnext_t *func, void *arg)
161{
162 struct io_user_timer *user = io_user_timer_from_timer(timer);
163 assert(ctx);
164 assert(exec);
165
166 user->dev_vptr = &io_user_timer_dev_vtbl;
167 user->timer_vptr = &io_user_timer_vtbl;
168 user->clock_vptr = &io_user_timer_clock_vtbl;
169
170 user->svc = (struct io_svc)IO_SVC_INIT(&io_user_timer_svc_vtbl);
171 user->ctx = ctx;
172
173 user->exec = exec;
174
175 user->func = func;
176 user->arg = arg;
177
178#if !LELY_NO_THREADS
179 if (mtx_init(&user->mtx, mtx_plain) != thrd_success)
180 return NULL;
181#endif
182
183 user->shutdown = 0;
184
185 sllist_init(&user->queue);
186
187 user->now = (struct timespec){ 0, 0 };
188 user->prev = (struct timespec){ 0, 0 };
189 user->next = (struct timespec){ 0, 0 };
190 user->value = (struct itimerspec){ { 0, 0 }, { 0, 0 } };
191 user->overrun = 0;
192
193 io_ctx_insert(user->ctx, &user->svc);
194
195 return timer;
196}
197
198void
199io_user_timer_fini(io_timer_t *timer)
200{
201 struct io_user_timer *user = io_user_timer_from_timer(timer);
202
203 io_ctx_remove(user->ctx, &user->svc);
204 // Cancel all pending tasks.
205 io_user_timer_svc_shutdown(&user->svc);
206
207#if !LELY_NO_THREADS
208 mtx_destroy(&user->mtx);
209#endif
210}
211
214 io_user_timer_setnext_t *func, void *arg)
215{
216 int errc = 0;
217
218 io_timer_t *timer = io_user_timer_alloc();
219 if (!timer) {
220 errc = get_errc();
221 goto error_alloc;
222 }
223
224 io_timer_t *tmp = io_user_timer_init(timer, ctx, exec, func, arg);
225 if (!tmp) {
226 errc = get_errc();
227 goto error_init;
228 }
229 timer = tmp;
230
231 return timer;
232
233error_init:
234 io_user_timer_free((void *)timer);
235error_alloc:
236 set_errc(errc);
237 return NULL;
238}
239
240void
242{
243 if (timer) {
244 io_user_timer_fini(timer);
245 io_user_timer_free((void *)timer);
246 }
247}
248
249static io_ctx_t *
250io_user_timer_dev_get_ctx(const io_dev_t *dev)
251{
252 const struct io_user_timer *user = io_user_timer_from_dev(dev);
253
254 return user->ctx;
255}
256
257static ev_exec_t *
258io_user_timer_dev_get_exec(const io_dev_t *dev)
259{
260 const struct io_user_timer *user = io_user_timer_from_dev(dev);
261
262 return user->exec;
263}
264
265static size_t
266io_user_timer_dev_cancel(io_dev_t *dev, struct ev_task *task)
267{
268 struct io_user_timer *user = io_user_timer_from_dev(dev);
269
270 struct sllist queue;
271 sllist_init(&queue);
272
273 io_user_timer_pop(user, &queue, task);
274
275 return io_timer_wait_queue_post(&queue, -1, errnum2c(ERRNUM_CANCELED));
276}
277
278static size_t
279io_user_timer_dev_abort(io_dev_t *dev, struct ev_task *task)
280{
281 struct io_user_timer *user = io_user_timer_from_dev(dev);
282
283 struct sllist queue;
284 sllist_init(&queue);
285
286 io_user_timer_pop(user, &queue, task);
287
288 return ev_task_queue_abort(&queue);
289}
290
291static io_dev_t *
292io_user_timer_get_dev(const io_timer_t *timer)
293{
294 const struct io_user_timer *user = io_user_timer_from_timer(timer);
295
296 return &user->dev_vptr;
297}
298
299static io_clock_t *
300io_user_timer_get_clock(const io_timer_t *timer)
301{
302 const struct io_user_timer *user = io_user_timer_from_timer(timer);
303
304 return &user->clock_vptr;
305}
306
307static int
308io_user_timer_getoverrun(const io_timer_t *timer)
309{
310 const struct io_user_timer *user = io_user_timer_from_timer(timer);
311
312#if !LELY_NO_THREADS
313 mtx_lock((mtx_t *)&user->mtx);
314#endif
315 int overrun = user->overrun;
316#if !LELY_NO_THREADS
317 mtx_unlock((mtx_t *)&user->mtx);
318#endif
319 return overrun;
320}
321
322static int
323io_user_timer_gettime(const io_timer_t *timer, struct itimerspec *value)
324{
325 const struct io_user_timer *user = io_user_timer_from_timer(timer);
326
327 if (value) {
328#if !LELY_NO_THREADS
329 mtx_lock((mtx_t *)&user->mtx);
330#endif
331 *value = user->value;
332 if (value->it_value.tv_sec || value->it_value.tv_nsec)
333 timespec_sub(&value->it_value, &user->now);
334#if !LELY_NO_THREADS
335 mtx_unlock((mtx_t *)&user->mtx);
336#endif
337 }
338 return 0;
339}
340
341static int
342io_user_timer_settime(io_timer_t *timer, int flags,
343 const struct itimerspec *value, struct itimerspec *ovalue)
344{
345 struct io_user_timer *user = io_user_timer_from_timer(timer);
346 assert(value);
347
348 struct timespec expiry = value->it_value;
349 if (!timespec_valid(&value->it_interval) || !timespec_valid(&expiry)) {
351 return -1;
352 }
353
354#if !LELY_NO_THREADS
355 // NOTE: mtx_unlock() is called by io_user_timer_do_settime().
356 mtx_lock(&user->mtx);
357#endif
358
359 if ((expiry.tv_sec || expiry.tv_nsec) && !(flags & TIMER_ABSTIME))
360 timespec_add(&expiry, &user->now);
361
362 if (ovalue) {
363 *ovalue = user->value;
364 if (ovalue->it_value.tv_sec || ovalue->it_value.tv_nsec)
365 timespec_sub(&ovalue->it_value, &user->now);
366 }
367 user->value = (struct itimerspec){ value->it_interval, expiry };
368 user->overrun = 0;
369
370 io_user_timer_do_settime(user);
371 return 0;
372}
373
374static void
375io_user_timer_submit_wait(io_timer_t *timer, struct io_timer_wait *wait)
376{
377 struct io_user_timer *user = io_user_timer_from_timer(timer);
378 assert(wait);
379 struct ev_task *task = &wait->task;
380
381 if (!task->exec)
382 task->exec = user->exec;
384
385#if !LELY_NO_THREADS
386 mtx_lock(&user->mtx);
387#endif
388 if (user->shutdown) {
389#if !LELY_NO_THREADS
390 mtx_unlock(&user->mtx);
391#endif
392 io_timer_wait_post(wait, -1, errnum2c(ERRNUM_CANCELED));
393 } else {
394 sllist_push_back(&user->queue, &task->_node);
395 // NOTE: mtx_unlock() is called by io_user_timer_do_settime().
396 io_user_timer_do_settime(user);
397 }
398}
399
400static int
401io_user_timer_clock_getres(const io_clock_t *clock, struct timespec *res)
402{
403 const struct io_user_timer *user = io_user_timer_from_clock(clock);
404
405 if (res) {
406#if !LELY_NO_THREADS
407 mtx_lock((mtx_t *)&user->mtx);
408#endif
409 *res = user->now;
410 timespec_sub(res, &user->prev);
411#if !LELY_NO_THREADS
412 mtx_unlock((mtx_t *)&user->mtx);
413#endif
414 }
415 return 0;
416}
417
418static int
419io_user_timer_clock_gettime(const io_clock_t *clock, struct timespec *tp)
420{
421 const struct io_user_timer *user = io_user_timer_from_clock(clock);
422
423 if (tp) {
424#if !LELY_NO_THREADS
425 mtx_lock((mtx_t *)&user->mtx);
426#endif
427 *tp = user->now;
428#if !LELY_NO_THREADS
429 mtx_unlock((mtx_t *)&user->mtx);
430#endif
431 }
432 return 0;
433}
434
435static int
436io_user_timer_clock_settime(io_clock_t *clock, const struct timespec *tp)
437{
438 struct io_user_timer *user = io_user_timer_from_clock(clock);
439 assert(tp);
440
441#if !LELY_NO_THREADS
442 // NOTE: mtx_unlock() is called by io_user_timer_do_settime().
443 mtx_lock(&user->mtx);
444#endif
445
446 user->prev = user->now;
447 user->now = *tp;
448
449 io_user_timer_do_settime(user);
450 return 0;
451}
452
453static void
454io_user_timer_svc_shutdown(struct io_svc *svc)
455{
456 struct io_user_timer *user = io_user_timer_from_svc(svc);
457 io_dev_t *dev = &user->dev_vptr;
458
459#if !LELY_NO_THREADS
460 mtx_lock(&user->mtx);
461#endif
462 int shutdown = !user->shutdown;
463 user->shutdown = 1;
464#if !LELY_NO_THREADS
465 mtx_unlock(&user->mtx);
466#endif
467
468 if (shutdown)
469 // Cancel all pending tasks.
470 io_user_timer_dev_cancel(dev, NULL);
471}
472
473static inline struct io_user_timer *
474io_user_timer_from_dev(const io_dev_t *dev)
475{
476 assert(dev);
477
478 return structof(dev, struct io_user_timer, dev_vptr);
479}
480
481static inline struct io_user_timer *
482io_user_timer_from_timer(const io_timer_t *timer)
483{
484 assert(timer);
485
486 return structof(timer, struct io_user_timer, timer_vptr);
487}
488
489static inline struct io_user_timer *
490io_user_timer_from_clock(const io_clock_t *clock)
491{
492 assert(clock);
493
494 return structof(clock, struct io_user_timer, clock_vptr);
495}
496
497static inline struct io_user_timer *
498io_user_timer_from_svc(const struct io_svc *svc)
499{
500 assert(svc);
501
502 return structof(svc, struct io_user_timer, svc);
503}
504
505static void
506io_user_timer_pop(struct io_user_timer *user, struct sllist *queue,
507 struct ev_task *task)
508{
509 assert(user);
510 assert(queue);
511
512#if !LELY_NO_THREADS
513 mtx_lock(&user->mtx);
514#endif
515 if (!task)
516 sllist_append(queue, &user->queue);
517 else if (sllist_remove(&user->queue, &task->_node))
518 sllist_push_back(queue, &task->_node);
519#if !LELY_NO_THREADS
520 mtx_unlock(&user->mtx);
521#endif
522}
523
524static void
525io_user_timer_do_settime(struct io_user_timer *user)
526{
527 assert(user);
528
529 struct sllist queue;
530 sllist_init(&queue);
531
532 struct timespec *expiry = &user->value.it_value;
533 assert(timespec_valid(expiry));
534
535 if ((expiry->tv_sec || expiry->tv_nsec)
536 && timespec_cmp(expiry, &user->now) <= 0) {
537 const struct timespec *period = &user->value.it_interval;
538 assert(timespec_valid(period));
539
540 if (period->tv_sec || period->tv_nsec) {
541 user->overrun = -1;
542 while (timespec_cmp(expiry, &user->now) < 0) {
543 timespec_add(expiry, period);
544 user->overrun++;
545 }
546 } else {
547 *expiry = (struct timespec){ 0, 0 };
548 user->overrun = 0;
549 }
550
551 sllist_append(&queue, &user->queue);
552 }
553
554 struct timespec next = { 0, 0 };
555 io_user_timer_setnext_t *func = NULL;
556 void *arg = NULL;
557 if (timespec_cmp(&user->next, expiry)) {
558 next = user->next = *expiry;
559 func = user->func;
560 arg = user->arg;
561 }
562
563 int overrun = user->overrun;
564
565#if !LELY_NO_THREADS
566 mtx_unlock(&user->mtx);
567#endif
568
569 if (func)
570 func(&next, arg);
571
572 io_timer_wait_queue_post(&queue, overrun, 0);
573}
574
575static inline int
576timespec_valid(const struct timespec *tp)
577{
578 return tp->tv_sec >= 0 && tp->tv_nsec >= 0 && tp->tv_nsec < 1000000000l;
579}
580
581#endif // !LELY_NO_MALLOC
const struct io_clock_vtbl *const io_clock_t
An abstract clock.
Definition: clock.h:36
This header file is part of the I/O library; it contains the I/O context and service declarations.
void io_ctx_insert(io_ctx_t *ctx, struct io_svc *svc)
Registers an I/O service with an I/O context.
Definition: ctx.c:126
#define IO_SVC_INIT(vptr)
The static initializer for io_svc.
Definition: ctx.h:57
void io_ctx_remove(io_ctx_t *ctx, struct io_svc *svc)
Unregisters an I/O service with an I/O context.
Definition: ctx.c:141
This header file is part of the utilities library; it contains the native and platform-independent er...
int errnum2c(errnum_t errnum)
Transforms a platform-independent error number to a native error code.
Definition: errnum.c:810
@ ERRNUM_INVAL
Invalid argument.
Definition: errnum.h:132
@ ERRNUM_CANCELED
Operation canceled.
Definition: errnum.h:99
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
Definition: errnum.c:932
void set_errc(int errc)
Sets the current (thread-specific) native error code to errc.
Definition: errnum.c:944
int errno2c(int errnum)
Transforms a standard C error number to a native error code.
Definition: errnum.c:46
void set_errnum(errnum_t errnum)
Sets the current (thread-specific) platform-independent error number to errnum.
Definition: errnum.h:424
void ev_exec_on_task_init(ev_exec_t *exec)
Indicates to the specified executor that a task will be submitted for execution in the future.
Definition: exec.h:106
const struct ev_exec_vtbl *const ev_exec_t
An abstract task executor.
Definition: ev.h:29
const struct io_timer_vtbl *const io_timer_t
An abstract timer.
Definition: timer.h:38
This header file is part of the I/O library; it contains the user-defined timer declarations.
void io_user_timer_setnext_t(const struct timespec *tp, void *arg)
The type of function invoked by a user-defined timer when the expiration time is updated with io_time...
Definition: timer.h:45
This is the public header file of the utilities library.
#define structof(ptr, type, member)
Obtains the address of a structure from the address of one of its members.
Definition: util.h:93
const struct io_dev_vtbl *const io_dev_t
An abstract I/O device.
Definition: dev.h:35
void sllist_init(struct sllist *list)
Initializes a singly-linked list.
Definition: sllist.h:194
struct sllist * sllist_append(struct sllist *dst, struct sllist *src)
Appends the singly-linked list at src to the one at dst.
Definition: sllist.h:257
struct slnode * sllist_remove(struct sllist *list, struct slnode *node)
Removes a node from a singly-linked list.
Definition: sllist.c:46
void sllist_push_back(struct sllist *list, struct slnode *node)
Pushes a node to the back of a singly-linked list.
Definition: sllist.h:232
This header file is part of the C11 and POSIX compatibility library; it includes <stdlib....
An executable task.
Definition: task.h:41
ev_exec_t * exec
A pointer to the executor to which the task is (to be) submitted.
Definition: task.h:43
Definition: ctx.c:38
The virtual table of an I/O service.
Definition: ctx.h:67
An I/O service.
Definition: ctx.h:49
A wait operation suitable for use with an I/O timer.
Definition: timer.h:54
struct ev_task task
The task (to be) submitted upon completion (or cancellation) of the wait operation.
Definition: timer.h:59
A singly-linked list.
Definition: sllist.h:52
size_t ev_task_queue_abort(struct sllist *queue)
Aborts the tasks in queue by invoking ev_exec_on_task_fini() for each of them.
Definition: task.c:55
This header file is part of the C11 and POSIX compatibility library; it includes <threads....
int mtx_init(mtx_t *mtx, int type)
Creates a mutex object with properties indicated by type, which must have one of the four values:
int mtx_lock(mtx_t *mtx)
Blocks until it locks the mutex at mtx.
@ thrd_success
Indicates that the requested operation succeeded.
Definition: threads.h:121
int mtx_unlock(mtx_t *mtx)
Unlocks the mutex at mtx.
pthread_mutex_t mtx_t
A complete object type that holds an identifier for a mutex.
Definition: threads.h:102
void mtx_destroy(mtx_t *mtx)
Releases any resources used by the mutex at mtx.
@ mtx_plain
A mutex type that supports neither timeout nor test and return.
Definition: threads.h:109
io_timer_t * io_user_timer_create(io_ctx_t *ctx, ev_exec_t *exec, io_user_timer_setnext_t *func, void *arg)
Creates a new user-defined timer.
Definition: timer.c:213
void io_user_timer_destroy(io_timer_t *timer)
Destroys a user-defined timer.
Definition: timer.c:241
This header file is part of the utilities library; it contains the time function declarations.
int timespec_cmp(const void *p1, const void *p2)
Compares two times.
Definition: time.h:251
void timespec_add(struct timespec *tp, const struct timespec *inc)
Adds the time interval *inc to the time at tp.
Definition: time.h:118
void timespec_sub(struct timespec *tp, const struct timespec *dec)
Subtracts the time interval *dec from the time at tp.
Definition: time.h:165