From f567a7f7192c62310fc57cda7350d7e9de5d082b Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 13 Jun 2011 17:12:42 +0100 Subject: [PATCH 3/6] Add an implementation of DBusSocketSet using epoll Bug: https://bugs.freedesktop.org/show_bug.cgi?id=33337 Bug-NB: NB#197191 Bug-NB: NB#225019 --- configure.ac | 3 + dbus/Makefile.am | 4 + dbus/dbus-socket-set-epoll.c | 352 ++++++++++++++++++++++++++++++++++++++++++ dbus/dbus-socket-set.c | 7 + dbus/dbus-socket-set.h | 2 + 5 files changed, 368 insertions(+), 0 deletions(-) create mode 100644 dbus/dbus-socket-set-epoll.c diff --git a/configure.ac b/configure.ac index 654ae4b..d3fba82 100644 --- a/configure.ac +++ b/configure.ac @@ -1021,6 +1021,9 @@ fi if test x$enable_epoll,$have_linux_epoll = xyes,no; then AC_MSG_ERROR([epoll support explicitly enabled but not available]) fi +if test x$have_linux_epoll = xyes; then + AC_DEFINE([DBUS_HAVE_LINUX_EPOLL], 1, [Define to use epoll(4) on Linux]) +fi AM_CONDITIONAL([HAVE_LINUX_EPOLL], [test x$have_linux_epoll = xyes]) # kqueue checks diff --git a/dbus/Makefile.am b/dbus/Makefile.am index 7fb7ee2..1f40aac 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -108,6 +108,10 @@ DBUS_UTIL_arch_sources = \ dbus-spawn.c endif +if HAVE_LINUX_EPOLL +DBUS_UTIL_arch_sources += dbus-socket-set-epoll.c +endif + dbusinclude_HEADERS= \ dbus.h \ dbus-address.h \ diff --git a/dbus/dbus-socket-set-epoll.c b/dbus/dbus-socket-set-epoll.c new file mode 100644 index 0000000..430a857 --- /dev/null +++ b/dbus/dbus-socket-set-epoll.c @@ -0,0 +1,352 @@ +/* -*- 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 + +typedef struct { + DBusSocketSet parent; + int epfd; +} DBusSocketSetEpoll; + +static inline DBusSocketSetEpoll * +socket_set_epoll_cast (DBusSocketSet *set) +{ + _dbus_assert (set->cls == &_dbus_socket_set_epoll_class); + return (DBusSocketSetEpoll *) set; +} + +/* this is safe to call on a partially-allocated socket set */ +static void +socket_set_epoll_free (DBusSocketSet *set) +{ + DBusSocketSetEpoll *self = socket_set_epoll_cast (set); + + if (self == NULL) + return; + + if (self->epfd != -1) + close (self->epfd); + + dbus_free (self); +} + +DBusSocketSet * +_dbus_socket_set_epoll_new (void) +{ + DBusSocketSetEpoll *self; + + self = dbus_new0 (DBusSocketSetEpoll, 1); + + if (self == NULL) + return NULL; + + self->parent.cls = &_dbus_socket_set_epoll_class; + + self->epfd = epoll_create1 (EPOLL_CLOEXEC); + + if (self->epfd == -1) + { + socket_set_epoll_free ((DBusSocketSet *) self); + return NULL; + } + + return (DBusSocketSet *) self; +} + +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; +} + +static dbus_bool_t +socket_set_epoll_add (DBusSocketSet *set, + int fd, + unsigned int flags, + dbus_bool_t enabled) +{ + DBusSocketSetEpoll *self = socket_set_epoll_cast (set); + 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 socket_set_epoll_disable for more details */ + event.events = EPOLLET; + } + + if (epoll_ctl (self->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; +} + +static void +socket_set_epoll_enable (DBusSocketSet *set, + int fd, + unsigned int flags) +{ + DBusSocketSetEpoll *self = socket_set_epoll_cast (set); + struct epoll_event event; + int err; + + event.data.fd = fd; + event.events = watch_flags_to_epoll_events (flags); + + if (epoll_ctl (self->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 enabled 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; + } +} + +static void +socket_set_epoll_disable (DBusSocketSet *set, + int fd) +{ + DBusSocketSetEpoll *self = socket_set_epoll_cast (set); + 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 socket_set_epoll_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 (self->epfd, EPOLL_CTL_MOD, fd, &event) == 0) + return; + + err = errno; + _dbus_warn ("Error when trying to watch fd %d: %s\n", fd, + strerror (err)); +} + +static void +socket_set_epoll_remove (DBusSocketSet *set, + int fd) +{ + DBusSocketSetEpoll *self = socket_set_epoll_cast (set); + int err; + + if (epoll_ctl (self->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: we use it to translate + * between struct epoll_event and DBusSocketEvent without allocating heap + * memory. */ +#define N_STACK_DESCRIPTORS 64 + +static int +socket_set_epoll_poll (DBusSocketSet *set, + DBusSocketEvent *revents, + int max_events, + int timeout_ms) +{ + DBusSocketSetEpoll *self = socket_set_epoll_cast (set); + struct epoll_event events[N_STACK_DESCRIPTORS]; + int n_ready; + int i; + + _dbus_assert (max_events > 0); + + n_ready = epoll_wait (self->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; +} + +DBusSocketSetClass _dbus_socket_set_epoll_class = { + socket_set_epoll_free, + socket_set_epoll_add, + socket_set_epoll_remove, + socket_set_epoll_enable, + socket_set_epoll_disable, + socket_set_epoll_poll +}; + +#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 */ diff --git a/dbus/dbus-socket-set.c b/dbus/dbus-socket-set.c index ddc4751..210d600 100644 --- a/dbus/dbus-socket-set.c +++ b/dbus/dbus-socket-set.c @@ -31,6 +31,13 @@ _dbus_socket_set_new (int size_hint) { DBusSocketSet *ret; +#ifdef DBUS_HAVE_LINUX_EPOLL + ret = _dbus_socket_set_epoll_new (); + + if (ret != NULL) + return ret; +#endif + ret = _dbus_socket_set_poll_new (size_hint); if (ret != NULL) diff --git a/dbus/dbus-socket-set.h b/dbus/dbus-socket-set.h index aca17da..3b71a92 100644 --- a/dbus/dbus-socket-set.h +++ b/dbus/dbus-socket-set.h @@ -113,8 +113,10 @@ _dbus_socket_set_poll (DBusSocketSet *self, /* concrete implementations, not necessarily built on all platforms */ extern DBusSocketSetClass _dbus_socket_set_poll_class; +extern DBusSocketSetClass _dbus_socket_set_epoll_class; DBusSocketSet *_dbus_socket_set_poll_new (int size_hint); +DBusSocketSet *_dbus_socket_set_epoll_new (void); #endif /* !DOXYGEN_SHOULD_SKIP_THIS */ #endif /* multiple-inclusion guard */ -- 1.7.5.4