From 3fc85e265f67448a28429d75de500c468dc4c56a Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 26 Jan 2011 16:29:04 +0000 Subject: [PATCH 3/5] Add an implementation of DBusSocketSet using epoll --- dbus/Makefile.am | 7 +- dbus/dbus-socket-set-epoll.c | 314 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+), 1 deletions(-) create mode 100644 dbus/dbus-socket-set-epoll.c diff --git a/dbus/Makefile.am b/dbus/Makefile.am index 7693c8a..7ef2b8e 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -106,6 +106,12 @@ DBUS_UTIL_arch_sources = \ dbus-spawn.c endif +if HAVE_LINUX_EPOLL +DBUS_UTIL_arch_sources += dbus-socket-set-epoll.c +else +DBUS_UTIL_arch_sources += dbus-socket-set-poll.c +endif + dbusinclude_HEADERS= \ dbus.h \ dbus-address.h \ @@ -238,7 +244,6 @@ DBUS_UTIL_SOURCES= \ dbus-shell.h \ $(DBUS_UTIL_arch_sources) \ dbus-socket-set.h \ - dbus-socket-set-poll.c \ dbus-spawn.h \ dbus-string-util.c \ dbus-sysdeps-util.c \ diff --git a/dbus/dbus-socket-set-epoll.c b/dbus/dbus-socket-set-epoll.c new file mode 100644 index 0000000..0c6745f --- /dev/null +++ b/dbus/dbus-socket-set-epoll.c @@ -0,0 +1,314 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-socket-set-epoll.c - a socket set implemented via Linux epoll(4) + * + * Copyright © 2011 Nokia Corporation + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + */ + +#include +#include "dbus-socket-set.h" + +#include +#include + +#ifndef __linux__ +# error This file is for Linux epoll(4) +#endif + +#include +#include +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +DBusSocketSet * +_dbus_socket_set_new (int size_hint) +{ + int epfd = epoll_create1 (EPOLL_CLOEXEC); + + if (epfd == -1) + return NULL; + + return _DBUS_INT_TO_POINTER (epfd); +} + +static uint32_t +watch_flags_to_epoll_events (unsigned int flags) +{ + uint32_t events = 0; + + if (flags & DBUS_WATCH_READABLE) + events |= EPOLLIN; + if (flags & DBUS_WATCH_WRITABLE) + events |= EPOLLOUT; + + return events; +} + +static unsigned int +epoll_events_to_watch_flags (uint32_t events) +{ + short flags = 0; + + if (events & EPOLLIN) + flags |= DBUS_WATCH_READABLE; + if (events & EPOLLOUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & EPOLLHUP) + flags |= DBUS_WATCH_HANGUP; + if (events & EPOLLERR) + flags |= DBUS_WATCH_ERROR; + + return flags; +} + +dbus_bool_t +_dbus_socket_set_add (DBusSocketSet *self, + int fd, + unsigned int flags, + dbus_bool_t enabled) +{ + int epfd = _DBUS_POINTER_TO_INT (self); + struct epoll_event event; + int err; + + event.data.fd = fd; + + if (enabled) + { + event.events = watch_flags_to_epoll_events (flags); + } + else + { + /* We need to add *something* to reserve space in the kernel's data + * structures: see _dbus_socket_set_disable for more details */ + event.events = EPOLLET; + } + + if (epoll_ctl (epfd, EPOLL_CTL_ADD, fd, &event) == 0) + return TRUE; + + /* Anything except ENOMEM, ENOSPC means we have an internal error. */ + err = errno; + switch (err) + { + case ENOMEM: + case ENOSPC: + /* be silent: this is basically OOM, which our callers are expected + * to cope with */ + break; + + case EBADF: + _dbus_warn ("Bad fd %d\n", fd); + break; + + case EEXIST: + _dbus_warn ("fd %d added and then added again\n", fd); + break; + + default: + _dbus_warn ("Misc error when trying to watch fd %d: %s\n", fd, + strerror (err)); + break; + } + + return FALSE; +} + +void +_dbus_socket_set_enable (DBusSocketSet *self, + int fd, + unsigned int flags) +{ + int epfd = _DBUS_POINTER_TO_INT (self); + struct epoll_event event; + int err; + + event.data.fd = fd; + event.events = watch_flags_to_epoll_events (flags); + + if (epoll_ctl (epfd, EPOLL_CTL_MOD, fd, &event) == 0) + return; + + err = errno; + + /* Enabling a file descriptor isn't allowed to fail, even for OOM, so we + * do our best to avoid all of these. */ + switch (err) + { + case EBADF: + _dbus_warn ("Bad fd %d\n", fd); + break; + + case ENOENT: + _dbus_warn ("fd %d modified before it was added\n", fd); + break; + + case ENOMEM: + _dbus_warn ("Insufficient memory to change watch for fd %d\n", fd); + break; + + default: + _dbus_warn ("Misc error when trying to watch fd %d: %s\n", fd, + strerror (err)); + break; + } +} + +void +_dbus_socket_set_disable (DBusSocketSet *self, + int fd) +{ + int epfd = _DBUS_POINTER_TO_INT (self); + struct epoll_event event; + int err; + + /* The naive thing to do would be EPOLL_CTL_DEL, but that'll probably + * free resources in the kernel. When we come to do _dbus_socket_set_enable, + * there might not be enough resources to bring it back! + * + * The next idea you might have is to set the flags to 0. However, events + * always trigger on EPOLLERR and EPOLLHUP, even if libdbus isn't actually + * delivering them to a DBusWatch. Because epoll is level-triggered by + * default, we'll busy-loop on an unhandled error or hangup; not good. + * + * So, let's set it to be edge-triggered: then the worst case is that + * we return from poll immediately on one iteration, ignore it because no + * watch is enabled, then go back to normal. When we re-enable a watch + * we'll switch back to level-triggered and be notified again (verified to + * work on 2.6.32). Compile this file with -DTEST_BEHAVIOUR_OF_EPOLLET for + * test code. + */ + event.data.fd = fd; + event.events = EPOLLET; + + if (epoll_ctl (epfd, EPOLL_CTL_MOD, fd, &event) == 0) + return; + + err = errno; + _dbus_warn ("Error when trying to watch fd %d: %s\n", fd, + strerror (err)); +} + +void +_dbus_socket_set_remove (DBusSocketSet *self, + int fd) +{ + int epfd = _DBUS_POINTER_TO_INT (self); + int err; + + if (epoll_ctl (epfd, EPOLL_CTL_DEL, fd, NULL) == 0) + return; + + err = errno; + _dbus_warn ("Error when trying to remove fd %d: %s\n", fd, strerror (err)); +} + +/* Optimally, this should be the same as in DBusLoop. This is a bit stupid, + * but it makes things simpler... */ +#define N_STACK_DESCRIPTORS 64 + +/** This is basically Linux's epoll_wait(2) implemented in terms of poll(2); + * it returns results into a caller-supplied buffer so we can be reentrant. */ +int +_dbus_socket_set_poll (DBusSocketSet *self, + DBusSocketEvent *revents, + int max_events, + int timeout_ms) +{ + int epfd = _DBUS_POINTER_TO_INT (self); + struct epoll_event events[N_STACK_DESCRIPTORS]; + int n_ready; + int i; + + _dbus_assert (max_events > 0); + + n_ready = epoll_wait (epfd, events, + MIN (_DBUS_N_ELEMENTS (events), max_events), + timeout_ms); + + if (n_ready <= 0) + return n_ready; + + for (i = 0; i < n_ready; i++) + { + revents[i].fd = events[i].data.fd; + revents[i].flags = epoll_events_to_watch_flags (events[i].events); + } + + return n_ready; +} + +/* this is safe to call on a partially-allocated socket set */ +void +_dbus_socket_set_free (DBusSocketSet *self) +{ + int epfd = _DBUS_POINTER_TO_INT (self); + + close (epfd); +} + +#ifdef TEST_BEHAVIOUR_OF_EPOLLET +/* usage: cat /dev/null | ./epoll + * + * desired output: + * ctl ADD: 0 + * wait for HUP, edge-triggered: 1 + * wait for HUP again: 0 + * ctl MOD: 0 + * wait for HUP: 1 + */ + +#include + +#include + +int +main (void) +{ + struct epoll_event input; + struct epoll_event output; + int epfd = epoll_create1 (EPOLL_CLOEXEC); + int fd = 0; /* stdin */ + int ret; + + input.events = EPOLLHUP | EPOLLET; + ret = epoll_ctl (epfd, EPOLL_CTL_ADD, fd, &input); + printf ("ctl ADD: %d\n", ret); + + ret = epoll_wait (epfd, &output, 1, -1); + printf ("wait for HUP, edge-triggered: %d\n", ret); + + ret = epoll_wait (epfd, &output, 1, 1); + printf ("wait for HUP again: %d\n", ret); + + input.events = EPOLLHUP; + ret = epoll_ctl (epfd, EPOLL_CTL_MOD, fd, &input); + printf ("ctl MOD: %d\n", ret); + + ret = epoll_wait (epfd, &output, 1, -1); + printf ("wait for HUP: %d\n", ret); + + return 0; +} + +#endif /* TEST_BEHAVIOUR_OF_EPOLLET */ + +#endif /* !DOXYGEN_SHOULD_SKIP_THIS */ -- 1.7.2.3