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 
40 static io_ctx_t *io_user_timer_dev_get_ctx(const io_dev_t *dev);
41 static ev_exec_t *io_user_timer_dev_get_exec(const io_dev_t *dev);
42 static size_t io_user_timer_dev_cancel(io_dev_t *dev, struct ev_task *task);
43 static size_t io_user_timer_dev_abort(io_dev_t *dev, struct ev_task *task);
44 
45 // clang-format off
46 static 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 
54 static io_dev_t *io_user_timer_get_dev(const io_timer_t *timer);
55 static io_clock_t *io_user_timer_get_clock(const io_timer_t *timer);
56 static int io_user_timer_getoverrun(const io_timer_t *timer);
57 static int io_user_timer_gettime(
58  const io_timer_t *timer, struct itimerspec *value);
59 static int io_user_timer_settime(io_timer_t *timer, int flags,
60  const struct itimerspec *value, struct itimerspec *ovalue);
61 static void io_user_timer_submit_wait(
62  io_timer_t *timer, struct io_timer_wait *wait);
63 
64 // clang-format off
65 static 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 
75 static int io_user_timer_clock_getres(
76  const io_clock_t *clock, struct timespec *res);
77 static int io_user_timer_clock_gettime(
78  const io_clock_t *clock, struct timespec *tp);
79 static int io_user_timer_clock_settime(
80  io_clock_t *clock, const struct timespec *tp);
81 
82 // clang-format off
83 static 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 
90 static void io_user_timer_svc_shutdown(struct io_svc *svc);
91 
92 // clang-format off
93 static const struct io_svc_vtbl io_user_timer_svc_vtbl = {
94  NULL,
95  &io_user_timer_svc_shutdown
96 };
97 // clang-format on
98 
99 struct io_user_timer {
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 
120 static inline struct io_user_timer *io_user_timer_from_dev(const io_dev_t *dev);
121 static inline struct io_user_timer *io_user_timer_from_timer(
122  const io_timer_t *timer);
123 static inline struct io_user_timer *io_user_timer_from_clock(
124  const io_clock_t *clock);
125 static inline struct io_user_timer *io_user_timer_from_svc(
126  const struct io_svc *svc);
127 
128 static void io_user_timer_pop(struct io_user_timer *user, struct sllist *queue,
129  struct ev_task *task);
130 
131 static void io_user_timer_do_settime(struct io_user_timer *user);
132 
133 static inline int timespec_valid(const struct timespec *tp);
134 
135 void *
136 io_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 
151 void
152 io_user_timer_free(void *ptr)
153 {
154  if (ptr)
155  free(io_user_timer_from_timer(ptr));
156 }
157 
158 io_timer_t *
159 io_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 
198 void
199 io_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 
212 io_timer_t *
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 
233 error_init:
234  io_user_timer_free((void *)timer);
235 error_alloc:
236  set_errc(errc);
237  return NULL;
238 }
239 
240 void
242 {
243  if (timer) {
244  io_user_timer_fini(timer);
245  io_user_timer_free((void *)timer);
246  }
247 }
248 
249 static io_ctx_t *
250 io_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 
257 static ev_exec_t *
258 io_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 
265 static size_t
266 io_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 
278 static size_t
279 io_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 
291 static io_dev_t *
292 io_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 
299 static io_clock_t *
300 io_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 
307 static int
308 io_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 
322 static int
323 io_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 
341 static int
342 io_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 
374 static void
375 io_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;
383  ev_exec_on_task_init(task->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 
400 static int
401 io_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 
418 static int
419 io_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 
435 static int
436 io_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 
453 static void
454 io_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 
473 static inline struct io_user_timer *
474 io_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 
481 static inline struct io_user_timer *
482 io_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 
489 static inline struct io_user_timer *
490 io_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 
497 static inline struct io_user_timer *
498 io_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 
505 static void
506 io_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 
524 static void
525 io_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 
575 static inline int
576 timespec_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
void io_user_timer_destroy(io_timer_t *timer)
Destroys a user-defined timer.
Definition: timer.c:241
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
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