mirror of
https://github.com/troglobit/libuev.git
synced 2026-10-11 16:52:26 +07:00
cron: New API for absolute timers
This patch adds support for at/cron-like jobs that need an absolute timer. The API supports waking up with an UEV_HUP callback event if the wall clock changed due to NTP or user input while waiting. - uev_cron_init() - uev_cron_set() - uev_cron_start() - uev_cron_stop() A very simple cronrun example/test program is also included. Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
@@ -9,8 +9,6 @@
|
||||
*.html
|
||||
.deps
|
||||
.libs
|
||||
examples/joystick
|
||||
examples/signal
|
||||
compile
|
||||
config.*
|
||||
configure
|
||||
@@ -26,7 +24,6 @@ aclocal.m4
|
||||
ar-lib
|
||||
autom4te.cache
|
||||
bench
|
||||
test
|
||||
libuev*.tar.*
|
||||
libuev.pc
|
||||
!libuev.pc.in
|
||||
|
||||
@@ -35,17 +35,25 @@ For details, see [this article][4] at [lwn.net](http://lwn.net).
|
||||
API
|
||||
---
|
||||
|
||||
The C interface to [libuEv][] is listed in `uev.h`. It handles three
|
||||
different types of events: I/O (files, sockets, message queues, etc.),
|
||||
timers, and signals. With a slight caveat on signals detailed below
|
||||
in the [Summary](#summary).
|
||||
The C API to [libuEv][], listed in `uev/uev.h`, handles three different
|
||||
types of events: I/O (files, sockets, message queues, etc.), timers, and
|
||||
signals. The [Summary](#summary) details a slight caveat on signals.
|
||||
|
||||
Timers can be either relative, timeout in milliseconds, or absolute with
|
||||
a time given in `time_t`, see `mktime()` et al. Absolute timers are
|
||||
called cron timers and their callbacks get an `UEV_HUP` error event if
|
||||
the wall clock changes, either via NTP or user input.
|
||||
|
||||
**NOTE:** On some systems, embedded systems in particular, `time_t` is a
|
||||
32-bit integer that wraps around in the year 2038. [libuEv][] cannot
|
||||
protect you against this problem, unfortunately.
|
||||
|
||||
```C
|
||||
|
||||
/*
|
||||
* Callback example, arg is passed from watcher's *_init()
|
||||
* w->fd holds the file descriptor, events is set by libuEv
|
||||
* to indicate if any of UEV_READ and/or UEV_WRITE is ready.
|
||||
* Callback example, arg comes from the watcher's *_init() function,
|
||||
* w->fd holds the file descriptor or socket, and events is set by
|
||||
* libuEv to indicate status: UEV_HUP, UEV_READ and/or UEV_WRITE
|
||||
*/
|
||||
void callback (uev_t *w, void *arg, int events);
|
||||
|
||||
@@ -60,12 +68,18 @@ in the [Summary](#summary).
|
||||
int uev_io_start (uev_t *w);
|
||||
int uev_io_stop (uev_t *w);
|
||||
|
||||
/* Timer watcher: timeout and period in milliseconds */
|
||||
/* Timer watcher: schedule a relative timer, timeout and period in milliseconds */
|
||||
int uev_timer_init (uev_ctx_t *ctx, uev_t *w, uev_cb_t *cb, void *arg, int timeout, int period);
|
||||
int uev_timer_set (uev_t *w, int timeout, int period); /* Change timeout or period */
|
||||
int uev_timer_start (uev_t *w); /* Restart a stopped timer */
|
||||
int uev_timer_stop (uev_t *w); /* Stop a timer */
|
||||
|
||||
/* Cron watcher: schedule an absolute timer, when and period in time_t seconds */
|
||||
int uev_cron_init (uev_ctx_t *ctx, uev_t *w, uev_cb_t *cb, void *arg, time_t when, time_t period);
|
||||
int uev_cron_set (uev_t *w, int when, time_t period); /* Change when or period */
|
||||
int uev_cron_start (uev_t *w); /* Restart a stopped cron */
|
||||
int uev_cron_stop (uev_t *w); /* Stop a cron */
|
||||
|
||||
/* Signal watcher: signo is the signal to wait for, e.g., SIGTERM */
|
||||
int uev_signal_init (uev_ctx_t *ctx, uev_t *w, uev_cb_t *cb, void *arg, int signo);
|
||||
int uev_signal_set (uev_t *w, int signo); /* Change signal to wait for */
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/* libuEv - Micro event loop library
|
||||
*
|
||||
* Copyright (c) 2012 Flemming Madsen <flemming!madsen()madsensoft!dk>
|
||||
* Copyright (c) 2013-2016 Joachim Nilsson <troglobit()gmail!com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h> /* memset() */
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h> /* close(), read() */
|
||||
|
||||
#include "uev.h"
|
||||
|
||||
/* Missing defines in GLIBC <= 2.24 */
|
||||
#ifndef TFD_TIMER_CANCEL_ON_SET
|
||||
#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
|
||||
#endif
|
||||
#ifndef TFD_SETTIME_FLAGS
|
||||
#define TFD_SETTIME_FLAGS (TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET)
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Create and start an at/cron job watcher
|
||||
* @param ctx A valid libuEv context
|
||||
* @param w Pointer to an uev_t watcher
|
||||
* @param cb Callback function for cron job
|
||||
* @param arg Optional callback argument
|
||||
* @param when First point in time to call @param cb
|
||||
* @param interval For an at job this is zero, for cron the offset interval
|
||||
*
|
||||
* For at jobs set @param interval to zero and only use @param when. For
|
||||
* cron jobs, set @param interval to the offset. E.g., if the job should
|
||||
* run every five minutes set the @param tm_min of struct tm to five.
|
||||
*
|
||||
* Use mktime() to create the time_t arguments. The special value zero
|
||||
* may be used for @param when to denote 'now', where 'now' is when the
|
||||
* event loop is started. You can also treat time_t simply as a signed
|
||||
* integer. E.g., set @param interval to 3600 to create a cron job that
|
||||
* runs every hour.
|
||||
*
|
||||
* @return POSIX OK(0) or non-zero with @param errno set on error.
|
||||
*/
|
||||
int uev_cron_init(uev_ctx_t *ctx, uev_t *w, uev_cb_t *cb, void *arg, time_t when, time_t interval)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (when < 0 || interval < 0) {
|
||||
errno = ERANGE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
if (uev_watcher_init(ctx, w, UEV_CRON_TYPE, cb, arg, fd, UEV_READ))
|
||||
goto exit;
|
||||
|
||||
if (uev_cron_set(w, when, interval)) {
|
||||
uev_watcher_stop(w);
|
||||
exit:
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset an at/cron job watcher
|
||||
* @param w Watcher to reset
|
||||
* @param when First point in time to call @param cb
|
||||
* @param interval For an at job this is zero, for cron the offset interval
|
||||
*
|
||||
* @return POSIX OK(0) or non-zero with @param errno set on error.
|
||||
*/
|
||||
int uev_cron_set(uev_t *w, time_t when, time_t interval)
|
||||
{
|
||||
/* Every watcher must be registered to a context */
|
||||
if (!w || !w->ctx) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (when < 0 || interval < 0) {
|
||||
errno = ERANGE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Handle stopped timers */
|
||||
if (w->fd < 0) {
|
||||
/* Timer already stopped */
|
||||
if (!when && !interval)
|
||||
return 0;
|
||||
|
||||
/* Remove from internal list */
|
||||
LIST_REMOVE(w, link);
|
||||
|
||||
if (uev_cron_init(w->ctx, w, (uev_cb_t *)w->cb, w->arg, when, interval))
|
||||
return -1;
|
||||
}
|
||||
|
||||
w->u.c.when = when;
|
||||
w->u.c.interval = interval;
|
||||
|
||||
if (w->ctx->running) {
|
||||
struct itimerspec time;
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
time.it_value.tv_sec = when;
|
||||
time.it_interval.tv_sec = interval;
|
||||
if (timerfd_settime(w->fd, TFD_SETTIME_FLAGS, &time, NULL) < 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return uev_watcher_start(w);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a stopped at/cron job watcher
|
||||
* @param w Watcher to start (again)
|
||||
*
|
||||
* @return POSIX OK(0) or non-zero with @param errno set on error.
|
||||
*/
|
||||
int uev_cron_start(uev_t *w)
|
||||
{
|
||||
return uev_timer_start(w);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop and unregister an at/cron job watcher
|
||||
* @param w Watcher to stop
|
||||
*
|
||||
* @return POSIX OK(0) or non-zero with @param errno set on error.
|
||||
*/
|
||||
int uev_cron_stop(uev_t *w)
|
||||
{
|
||||
return uev_timer_stop(w);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -0,0 +1,4 @@
|
||||
joystick
|
||||
signal
|
||||
cronrun
|
||||
test
|
||||
@@ -1,4 +1,4 @@
|
||||
noinst_PROGRAMS = signal test joystick
|
||||
noinst_PROGRAMS = signal test joystick cronrun
|
||||
AM_CPPFLAGS = -I$(top_srcdir)
|
||||
AM_LDFLAGS = -L$(top_srcdir)
|
||||
LIBS = $(LTLIBINTL)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Test cron API in libuEv event library
|
||||
*
|
||||
* Copyright (c) 2016 Joachim Nilsson <troglobit()gmail!com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include "uev.h"
|
||||
|
||||
#define UNUSED(arg) arg __attribute__ ((unused))
|
||||
|
||||
|
||||
static void cron_job(uev_t *w, void *arg, int events)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
printf("Cron job HELO %s", ctime(&tv.tv_sec));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uev_t cron_watcher;
|
||||
uev_ctx_t ctx;
|
||||
time_t when, interval;
|
||||
struct timeval tv;
|
||||
|
||||
/* Initialize libuEv */
|
||||
uev_init(&ctx);
|
||||
|
||||
/* Cron job */
|
||||
gettimeofday(&tv, NULL);
|
||||
when = tv.tv_sec + 30;
|
||||
interval = 30;
|
||||
uev_cron_init(&ctx, &cron_watcher, cron_job, NULL, when, interval);
|
||||
|
||||
/* Start event loop */
|
||||
printf("Start of test %s", ctime(&tv.tv_sec));
|
||||
printf("Expected cron %s", ctime(&when));
|
||||
|
||||
return uev_run(&ctx, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -31,13 +31,14 @@
|
||||
|
||||
/* I/O, timer, or signal watcher */
|
||||
typedef enum {
|
||||
UEV_IO_TYPE = 1,
|
||||
UEV_TIMER_TYPE,
|
||||
UEV_CRON_TYPE = 1,
|
||||
UEV_IO_TYPE,
|
||||
UEV_SIGNAL_TYPE,
|
||||
UEV_TIMER_TYPE,
|
||||
} uev_type_t;
|
||||
|
||||
/* Event mask, used internally only. */
|
||||
#define UEV_EVENT_MASK (UEV_READ | UEV_WRITE | UEV_PRI)
|
||||
#define UEV_EVENT_MASK (UEV_READ | UEV_WRITE | UEV_PRI | UEV_HUP)
|
||||
|
||||
/* Main libuEv context type */
|
||||
typedef struct {
|
||||
@@ -63,6 +64,12 @@ struct uev;
|
||||
\
|
||||
/* Arguments for different watchers */ \
|
||||
union { \
|
||||
/* Cron watchers */ \
|
||||
struct { \
|
||||
time_t when; \
|
||||
time_t interval; \
|
||||
} c; \
|
||||
\
|
||||
/* Timer watchers, time in milliseconds */ \
|
||||
struct { \
|
||||
int timeout; \
|
||||
|
||||
@@ -166,6 +166,7 @@ int uev_exit(uev_ctx_t *ctx)
|
||||
continue;
|
||||
|
||||
switch (w->type) {
|
||||
case UEV_CRON_TYPE:
|
||||
case UEV_TIMER_TYPE:
|
||||
uev_timer_stop(w);
|
||||
break;
|
||||
@@ -219,6 +220,8 @@ int uev_run(uev_ctx_t *ctx, int flags)
|
||||
|
||||
/* Start all dormant timers */
|
||||
LIST_FOREACH(w, &ctx->watchers, link) {
|
||||
if (UEV_CRON_TYPE == w->type)
|
||||
uev_cron_set(w, w->u.c.when, w->u.c.interval);
|
||||
if (UEV_TIMER_TYPE == w->type)
|
||||
uev_timer_set(w, w->u.t.timeout, w->u.t.period);
|
||||
}
|
||||
@@ -274,6 +277,24 @@ int uev_run(uev_ctx_t *ctx, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
if (UEV_CRON_TYPE == w->type) {
|
||||
uint64_t exp;
|
||||
|
||||
if (read(w->fd, &exp, sizeof(exp)) != sizeof(exp)) {
|
||||
if (errno != ECANCELED) {
|
||||
uev_exit(ctx);
|
||||
return -3;
|
||||
}
|
||||
|
||||
events[i].events = UEV_HUP;
|
||||
}
|
||||
|
||||
if (!w->u.c.interval)
|
||||
w->u.c.when = 0;
|
||||
else
|
||||
w->u.c.when += w->u.c.interval;
|
||||
}
|
||||
|
||||
if (UEV_TIMER_TYPE == w->type) {
|
||||
uint64_t exp;
|
||||
|
||||
@@ -299,6 +320,10 @@ int uev_run(uev_ctx_t *ctx, int flags)
|
||||
if (w->cb)
|
||||
w->cb(w, w->arg, events[i].events & UEV_EVENT_MASK);
|
||||
|
||||
if (UEV_CRON_TYPE == w->type) {
|
||||
if (!w->u.c.when)
|
||||
uev_timer_stop(w);
|
||||
}
|
||||
if (UEV_TIMER_TYPE == w->type) {
|
||||
if (!w->u.t.timeout)
|
||||
uev_timer_stop(w);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#define UEV_READ EPOLLIN
|
||||
#define UEV_WRITE EPOLLOUT
|
||||
#define UEV_PRI EPOLLPRI
|
||||
#define UEV_HUP EPOLLHUP
|
||||
|
||||
/* Run flags */
|
||||
#define UEV_ONCE 1
|
||||
@@ -69,6 +70,11 @@ int uev_timer_set (uev_t *w, int timeout, int period);
|
||||
int uev_timer_start (uev_t *w);
|
||||
int uev_timer_stop (uev_t *w);
|
||||
|
||||
int uev_cron_init (uev_ctx_t *ctx, uev_t *w, uev_cb_t *cb, void *arg, time_t when, time_t interval);
|
||||
int uev_cron_set (uev_t *w, time_t when, time_t interval);
|
||||
int uev_cron_start (uev_t *w);
|
||||
int uev_cron_stop (uev_t *w);
|
||||
|
||||
int uev_signal_init (uev_ctx_t *ctx, uev_t *w, uev_cb_t *cb, void *arg, int signo);
|
||||
int uev_signal_set (uev_t *w, int signo);
|
||||
int uev_signal_start (uev_t *w);
|
||||
|
||||
Reference in New Issue
Block a user