bus: add minimal event loop API

So far we tried to use epoll directly wherever we needed an event loop.
However, that has various shortcomings, such as the inability to handle
larger amounts of timers (since each timerfd costs one fd, which is a
very limited resource, usually bounded to 1024), and inability to do
priorisation between multiple queued events.

Let's add a minimal event loop API around epoll that is suitable for
implementation of our own daemons and maybe one day can become public
API for those who desire it.

This loop is part of libsystemd-bus, but may be used independently of
it.
This commit is contained in:
Lennart Poettering 2013-10-10 04:40:28 +02:00
parent 2b98f75a63
commit fd38203a2a
7 changed files with 1753 additions and 4 deletions

1
.gitignore vendored
View file

@ -107,6 +107,7 @@
/test-device-nodes
/test-engine
/test-env-replace
/test-event
/test-fileio
/test-hashmap
/test-hostname

View file

@ -1936,8 +1936,9 @@ EXTRA_DIST += \
libsystemd_bus_la_SOURCES = \
src/systemd/sd-bus.h \
src/systemd/sd-bus-protocol.h \
src/systemd/sd-memfd.h \
src/systemd/sd-bus-vtable.h \
src/systemd/sd-memfd.h \
src/systemd/sd-event.h \
src/libsystemd-bus/sd-bus.c \
src/libsystemd-bus/bus-control.c \
src/libsystemd-bus/bus-control.h \
@ -1962,7 +1963,8 @@ libsystemd_bus_la_SOURCES = \
src/libsystemd-bus/bus-introspect.c \
src/libsystemd-bus/bus-introspect.h \
src/libsystemd-bus/kdbus.h \
src/libsystemd-bus/sd-memfd.c
src/libsystemd-bus/sd-memfd.c \
src/libsystemd-bus/sd-event.c
libsystemd_bus_la_LIBADD = \
libsystemd-id128-internal.la \
@ -1988,7 +1990,8 @@ tests += \
test-bus-memfd \
test-bus-zero-copy \
test-bus-introspect \
test-bus-objects
test-bus-objects \
test-event
noinst_PROGRAMS += \
busctl
@ -2124,6 +2127,14 @@ test_bus_introspect_LDADD = \
libsystemd-shared.la \
libsystemd-bus.la
test_event_SOURCES = \
src/libsystemd-bus/test-event.c
test_event_LDADD = \
libsystemd-shared.la \
libsystemd-bus.la \
libsystemd-id128-internal.la
busctl_SOURCES = \
src/libsystemd-bus/busctl.c

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,197 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2013 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "sd-event.h"
#include "log.h"
#include "util.h"
static int prepare_handler(sd_event_source *s, void *userdata) {
log_info("preparing %c", PTR_TO_INT(userdata));
return 1;
}
static bool got_a, got_b, got_c;
static int io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
log_info("got IO on %c", PTR_TO_INT(userdata));
if (userdata == INT_TO_PTR('a')) {
assert_se(sd_event_source_set_mute(s, SD_EVENT_MUTED) >= 0);
assert_se(!got_a);
got_a = true;
} else if (userdata == INT_TO_PTR('b')) {
assert_se(!got_b);
got_b = true;
} else
assert_not_reached("Yuck!");
return 1;
}
static int child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) {
assert(s);
assert(si);
log_info("got child on %c", PTR_TO_INT(userdata));
assert(userdata == INT_TO_PTR('f'));
assert_se(sd_event_request_quit(sd_event_get(s)) >= 0);
sd_event_source_unref(s);
return 1;
}
static int signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
sd_event_source *p;
sigset_t ss;
pid_t pid;
assert(s);
assert(si);
log_info("got signal on %c", PTR_TO_INT(userdata));
assert(userdata == INT_TO_PTR('e'));
assert_se(sigemptyset(&ss) >= 0);
assert_se(sigaddset(&ss, SIGCHLD) >= 0);
assert_se(sigprocmask(SIG_BLOCK, &ss, NULL) >= 0);
pid = fork();
assert_se(pid >= 0);
if (pid == 0)
_exit(0);
assert_se(sd_event_add_child(sd_event_get(s), pid, WEXITED, child_handler, INT_TO_PTR('f'), &p) >= 0);
assert_se(sd_event_source_set_mute(p, SD_EVENT_ONESHOT) >= 0);
sd_event_source_unref(s);
return 1;
}
static int defer_handler(sd_event_source *s, void *userdata) {
sd_event_source *p;
sigset_t ss;
assert(s);
log_info("got defer on %c", PTR_TO_INT(userdata));
assert(userdata == INT_TO_PTR('d'));
assert_se(sigemptyset(&ss) >= 0);
assert_se(sigaddset(&ss, SIGUSR1) >= 0);
assert_se(sigprocmask(SIG_BLOCK, &ss, NULL) >= 0);
assert_se(sd_event_add_signal(sd_event_get(s), SIGUSR1, signal_handler, INT_TO_PTR('e'), &p) >= 0);
assert_se(sd_event_source_set_mute(p, SD_EVENT_ONESHOT) >= 0);
raise(SIGUSR1);
sd_event_source_unref(s);
return 1;
}
static bool do_quit = false;
static int time_handler(sd_event_source *s, uint64_t usec, void *userdata) {
log_info("got timer on %c", PTR_TO_INT(userdata));
if (userdata == INT_TO_PTR('c')) {
if (do_quit) {
sd_event_source *p;
assert_se(sd_event_add_defer(sd_event_get(s), defer_handler, INT_TO_PTR('d'), &p) >= 0);
assert_se(sd_event_source_set_mute(p, SD_EVENT_ONESHOT) >= 0);
} else {
assert(!got_c);
got_c = true;
}
} else
assert_not_reached("Huh?");
return 2;
}
int main(int argc, char *argv[]) {
sd_event *e = NULL;
sd_event_source *x = NULL, *y = NULL, *z = NULL;
static const char ch = 'x';
int a[2] = { -1, -1 }, b[2] = { -1, -1};
assert_se(pipe(a) >= 0);
assert_se(pipe(b) >= 0);
assert_se(sd_event_new(&e) >= 0);
got_a = false, got_b = false, got_c = false;
assert_se(sd_event_add_io(e, a[0], EPOLLIN, io_handler, INT_TO_PTR('a'), &x) >= 0);
assert_se(sd_event_add_io(e, b[0], EPOLLIN, io_handler, INT_TO_PTR('b'), &y) >= 0);
assert_se(sd_event_add_monotonic(e, 0, time_handler, INT_TO_PTR('c'), &z) >= 0);
assert_se(sd_event_source_set_priority(x, 99) >= 0);
assert_se(sd_event_source_set_mute(y, SD_EVENT_ONESHOT) >= 0);
assert_se(sd_event_source_set_prepare(x, prepare_handler) >= 0);
assert_se(sd_event_source_set_priority(z, 50) >= 0);
assert_se(sd_event_source_set_mute(z, SD_EVENT_ONESHOT) >= 0);
assert_se(sd_event_source_set_prepare(z, prepare_handler) >= 0);
assert_se(write(a[1], &ch, 1) >= 0);
assert_se(write(b[1], &ch, 1) >= 0);
assert_se(!got_a && !got_b && !got_c);
assert_se(sd_event_run(e, (uint64_t) -1) >= 1);
assert_se(!got_a && got_b && !got_c);
assert_se(sd_event_run(e, (uint64_t) -1) >= 1);
assert_se(!got_a && got_b && got_c);
assert_se(sd_event_run(e, (uint64_t) -1) >= 1);
assert_se(got_a && got_b && got_c);
sd_event_source_unref(x);
sd_event_source_unref(y);
do_quit = true;
assert_se(sd_event_source_set_time(z, now(CLOCK_MONOTONIC) + 200 * USEC_PER_MSEC) >= 0);
assert_se(sd_event_source_set_mute(z, SD_EVENT_ONESHOT) >= 0);
assert_se(sd_event_loop(e) >= 0);
sd_event_source_unref(z);
sd_event_unref(e);
close_pipe(a);
close_pipe(b);
return 0;
}

View file

@ -25,6 +25,8 @@
typedef struct Prioq Prioq;
#define PRIOQ_IDX_NULL ((unsigned) -1)
Prioq *prioq_new(compare_func_t compare);
void prioq_free(Prioq *q);
int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func);

97
src/systemd/sd-event.h Normal file
View file

@ -0,0 +1,97 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#ifndef foosdeventhfoo
#define foosdeventhfoo
/***
This file is part of systemd.
Copyright 2013 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/types.h>
#include <sys/signalfd.h>
#include <sys/epoll.h>
#include <inttypes.h>
#include <signal.h>
/*
Why is this better than pure epoll?
- Supports event source priorisation
- Scales better with a large number of time events, since it doesn't require one timerfd each
- Handles signals and child PIDs
TODO:
- Detect forks and return ECHILD
- Timer events with accuracy for coalescing time events
*/
typedef struct sd_event sd_event;
typedef struct sd_event_source sd_event_source;
typedef enum sd_event_mute {
SD_EVENT_MUTED = 0,
SD_EVENT_UNMUTED = 1,
SD_EVENT_ONESHOT = -1
} sd_event_mute_t;
typedef int (*sd_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata);
typedef int (*sd_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata);
typedef int (*sd_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata);
typedef int (*sd_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata);
typedef int (*sd_defer_handler_t)(sd_event_source *s, void *userdata);
typedef int (*sd_prepare_handler_t)(sd_event_source *s, void *userdata);
int sd_event_new(sd_event **e);
sd_event* sd_event_ref(sd_event *e);
sd_event* sd_event_unref(sd_event *e);
int sd_event_add_io(sd_event *e, int fd, uint32_t events, sd_io_handler_t callback, void *userdata, sd_event_source **s);
int sd_event_add_monotonic(sd_event *e, uint64_t usec, sd_time_handler_t callback, void *userdata, sd_event_source **s);
int sd_event_add_realtime(sd_event *e, uint64_t usec, sd_time_handler_t callback, void *userdata, sd_event_source **s);
int sd_event_add_signal(sd_event *e, int sig, sd_signal_handler_t callback, void *userdata, sd_event_source **s);
int sd_event_add_child(sd_event *e, pid_t pid, int options, sd_child_handler_t callback, void *userdata, sd_event_source **s);
int sd_event_add_defer(sd_event *e, sd_defer_handler_t callback, void *userdata, sd_event_source **s);
int sd_event_run(sd_event *e, uint64_t timeout);
int sd_event_loop(sd_event *e);
int sd_event_quit(sd_event *e);
int sd_event_request_quit(sd_event *e);
sd_event *sd_event_get(sd_event_source *s);
sd_event_source* sd_event_source_ref(sd_event_source *s);
sd_event_source* sd_event_source_unref(sd_event_source *s);
int sd_event_source_get_pending(sd_event_source *s);
int sd_event_source_get_io_fd(sd_event_source *s);
int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events);
int sd_event_source_set_io_events(sd_event_source *s, uint32_t events);
int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents);
int sd_event_source_get_signal(sd_event_source *s);
int sd_event_source_get_priority(sd_event_source *s, int *priority);
int sd_event_source_set_priority(sd_event_source *s, int priority);
int sd_event_source_get_mute(sd_event_source *s, sd_event_mute_t *m);
int sd_event_source_set_mute(sd_event_source *s, sd_event_mute_t m);
int sd_event_source_get_time(sd_event_source *s, uint64_t *usec);
int sd_event_source_set_time(sd_event_source *s, uint64_t usec);
int sd_event_source_set_prepare(sd_event_source *s, sd_prepare_handler_t callback);
void* sd_event_source_get_userdata(sd_event_source *s);
#endif

View file

@ -87,8 +87,9 @@ enum {
SD_JOURNAL_LOCAL_ONLY = 1,
SD_JOURNAL_RUNTIME_ONLY = 2,
SD_JOURNAL_SYSTEM = 4,
SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM, /* deprecated */
SD_JOURNAL_CURRENT_USER = 8,
SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM, /* deprecated name */
};
/* Wakeup event types */