diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2012-02-07 15:03:56 +0000 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2012-02-07 15:04:25 +0000 |
commit | 88498b706a39bbe520f9591d8d52b54fb1f8e378 (patch) | |
tree | 5243eb3c97b8039d0ff43cd7121a632bbdb9d3bc | |
parent | b816cb6e900073c6a6126687f2102dfd8e594e68 (diff) | |
parent | 3f4ed9def33c359142c340f28345755ca37663f2 (diff) | |
download | dbus-88498b706a39bbe520f9591d8d52b54fb1f8e378.tar.gz |
Merge branch 'socket-set-33337'
Reviewed-by: Will Thompson <will.thompson@collabora.co.uk>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=33337
-rw-r--r-- | bus/activation.c | 12 | ||||
-rw-r--r-- | bus/bus.c | 14 | ||||
-rw-r--r-- | bus/connection.c | 11 | ||||
-rw-r--r-- | bus/test.c | 9 | ||||
-rw-r--r-- | configure.ac | 30 | ||||
-rw-r--r-- | dbus/Makefile.am | 7 | ||||
-rw-r--r-- | dbus/dbus-connection.h | 1 | ||||
-rw-r--r-- | dbus/dbus-mainloop.c | 314 | ||||
-rw-r--r-- | dbus/dbus-mainloop.h | 2 | ||||
-rw-r--r-- | dbus/dbus-socket-set-epoll.c | 371 | ||||
-rw-r--r-- | dbus/dbus-socket-set-poll.c | 320 | ||||
-rw-r--r-- | dbus/dbus-socket-set.c | 47 | ||||
-rw-r--r-- | dbus/dbus-socket-set.h | 122 | ||||
-rw-r--r-- | dbus/dbus-watch.h | 2 | ||||
-rw-r--r-- | test/test-utils.c | 27 |
15 files changed, 1124 insertions, 165 deletions
diff --git a/bus/activation.c b/bus/activation.c index 2744e214..3dfba787 100644 --- a/bus/activation.c +++ b/bus/activation.c @@ -1427,6 +1427,16 @@ remove_babysitter_watch (DBusWatch *watch, watch); } +static void +toggle_babysitter_watch (DBusWatch *watch, + void *data) +{ + BusPendingActivation *pending_activation = data; + + _dbus_loop_toggle_watch (bus_context_get_loop (pending_activation->activation->context), + watch); +} + static dbus_bool_t pending_activation_timed_out (void *data) { @@ -2110,7 +2120,7 @@ bus_activation_activate_service (BusActivation *activation, if (!_dbus_babysitter_set_watch_functions (pending_activation->babysitter, add_babysitter_watch, remove_babysitter_watch, - NULL, + toggle_babysitter_watch, pending_activation, NULL)) { @@ -126,6 +126,18 @@ remove_server_watch (DBusWatch *watch, _dbus_loop_remove_watch (context->loop, watch); } +static void +toggle_server_watch (DBusWatch *watch, + void *data) +{ + DBusServer *server = data; + BusContext *context; + + context = server_get_context (server); + + _dbus_loop_toggle_watch (context->loop, watch); +} + static dbus_bool_t add_server_timeout (DBusTimeout *timeout, void *data) @@ -228,7 +240,7 @@ setup_server (BusContext *context, if (!dbus_server_set_watch_functions (server, add_server_watch, remove_server_watch, - NULL, + toggle_server_watch, server, NULL)) { diff --git a/bus/connection.c b/bus/connection.c index 26839529..97e5f64b 100644 --- a/bus/connection.c +++ b/bus/connection.c @@ -327,6 +327,15 @@ remove_connection_watch (DBusWatch *watch, _dbus_loop_remove_watch (connection_get_loop (connection), watch); } +static void +toggle_connection_watch (DBusWatch *watch, + void *data) +{ + DBusConnection *connection = data; + + _dbus_loop_toggle_watch (connection_get_loop (connection), watch); +} + static dbus_bool_t add_connection_timeout (DBusTimeout *timeout, void *data) @@ -631,7 +640,7 @@ bus_connections_setup_connection (BusConnections *connections, if (!dbus_connection_set_watch_functions (connection, add_connection_watch, remove_connection_watch, - NULL, + toggle_connection_watch, connection, NULL)) goto out; @@ -50,6 +50,13 @@ remove_client_watch (DBusWatch *watch, _dbus_loop_remove_watch (client_loop, watch); } +static void +toggle_client_watch (DBusWatch *watch, + void *data) +{ + _dbus_loop_toggle_watch (client_loop, watch); +} + static dbus_bool_t add_client_timeout (DBusTimeout *timeout, void *data) @@ -112,7 +119,7 @@ bus_setup_debug_client (DBusConnection *connection) if (!dbus_connection_set_watch_functions (connection, add_client_watch, remove_client_watch, - NULL, + toggle_client_watch, connection, NULL)) goto out; diff --git a/configure.ac b/configure.ac index 3ca922b8..6ecea15e 100644 --- a/configure.ac +++ b/configure.ac @@ -1036,6 +1036,36 @@ fi AM_CONDITIONAL(DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX, test x$have_dnotify = xyes) +# For simplicity, we require the userland API for epoll_create1 at +# compile-time (glibc 2.9), but we'll run on kernels that turn out +# not to have it at runtime. +AC_ARG_ENABLE([epoll], + [AS_HELP_STRING([--enable-epoll],[use epoll(4) on Linux])], + [enable_epoll=$enableval], [enable_epoll=auto]) +if test x$enable_epoll = xno; then + have_linux_epoll=no +else + AC_MSG_CHECKING([for Linux epoll(4)]) + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [ + #ifndef __linux__ + #error This is not Linux + #endif + #include <sys/epoll.h> + ], + [epoll_create1 (EPOLL_CLOEXEC);])], + [have_linux_epoll=yes], + [have_linux_epoll=no]) + AC_MSG_RESULT([$have_linux_epoll]) +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 if test x$enable_kqueue = xno ; then have_kqueue=no diff --git a/dbus/Makefile.am b/dbus/Makefile.am index d3d1b95f..1c8fe8c8 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -111,6 +111,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 \ @@ -239,6 +243,9 @@ DBUS_UTIL_SOURCES= \ dbus-shell.c \ dbus-shell.h \ $(DBUS_UTIL_arch_sources) \ + dbus-socket-set.h \ + dbus-socket-set.c \ + dbus-socket-set-poll.c \ dbus-spawn.h \ dbus-string-util.c \ dbus-sysdeps-util.c \ diff --git a/dbus/dbus-connection.h b/dbus/dbus-connection.h index 3e2a7d8d..fe4d04ef 100644 --- a/dbus/dbus-connection.h +++ b/dbus/dbus-connection.h @@ -69,6 +69,7 @@ typedef enum * state passed to * dbus_watch_handle()). */ + /* Internal to libdbus, there is also _DBUS_WATCH_NVAL in dbus-watch.h */ } DBusWatchFlags; /** diff --git a/dbus/dbus-mainloop.c b/dbus/dbus-mainloop.c index 1a046a1f..579614de 100644 --- a/dbus/dbus-mainloop.c +++ b/dbus/dbus-mainloop.c @@ -1,7 +1,8 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* dbus-mainloop.c Main loop utility * - * Copyright (C) 2003, 2004 Red Hat, Inc. + * Copyright © 2003, 2004 Red Hat, Inc. + * Copyright © 2011 Nokia Corporation * * Licensed under the Academic Free License version 2.1 * @@ -28,7 +29,7 @@ #include <dbus/dbus-hash.h> #include <dbus/dbus-list.h> -#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-socket-set.h> #include <dbus/dbus-watch.h> #define MAINLOOP_SPEW 0 @@ -59,44 +60,18 @@ struct DBusLoop int refcount; /** fd => dbus_malloc'd DBusList ** of references to DBusWatch */ DBusHashTable *watches; + DBusSocketSet *socket_set; DBusList *timeouts; int callback_list_serial; int watch_count; int timeout_count; int depth; /**< number of recursive runs */ DBusList *need_dispatch; + /** TRUE if we will skip a watch next time because it was OOM; becomes + * FALSE between polling, and dealing with the results of the poll */ + unsigned oom_watch_pending : 1; }; -static short -watch_flags_to_poll_events (unsigned int flags) -{ - short events = 0; - - if (flags & DBUS_WATCH_READABLE) - events |= _DBUS_POLLIN; - if (flags & DBUS_WATCH_WRITABLE) - events |= _DBUS_POLLOUT; - - return events; -} - -static unsigned int -watch_flags_from_poll_revents (short revents) -{ - unsigned int condition = 0; - - if (revents & _DBUS_POLLIN) - condition |= DBUS_WATCH_READABLE; - if (revents & _DBUS_POLLOUT) - condition |= DBUS_WATCH_WRITABLE; - if (revents & _DBUS_POLLHUP) - condition |= DBUS_WATCH_HANGUP; - if (revents & _DBUS_POLLERR) - condition |= DBUS_WATCH_ERROR; - - return condition; -} - typedef struct { DBusTimeout *timeout; @@ -161,8 +136,16 @@ _dbus_loop_new (void) loop->watches = _dbus_hash_table_new (DBUS_HASH_INT, NULL, free_watch_table_entry); - if (loop->watches == NULL) + loop->socket_set = _dbus_socket_set_new (0); + + if (loop->watches == NULL || loop->socket_set == NULL) { + if (loop->watches != NULL) + _dbus_hash_table_unref (loop->watches); + + if (loop->socket_set != NULL) + _dbus_socket_set_free (loop->socket_set); + dbus_free (loop); return NULL; } @@ -200,6 +183,7 @@ _dbus_loop_unref (DBusLoop *loop) } _dbus_hash_table_unref (loop->watches); + _dbus_socket_set_free (loop->socket_set); dbus_free (loop); } } @@ -250,20 +234,57 @@ cull_watches_for_invalid_fd (DBusLoop *loop, _dbus_hash_table_remove_int (loop->watches, fd); } -static void +static dbus_bool_t gc_watch_table_entry (DBusLoop *loop, DBusList **watches, int fd) { /* If watches is already NULL we have nothing to do */ if (watches == NULL) - return; + return FALSE; /* We can't GC hash table entries if they're non-empty lists */ if (*watches != NULL) - return; + return FALSE; _dbus_hash_table_remove_int (loop->watches, fd); + return TRUE; +} + +static void +refresh_watches_for_fd (DBusLoop *loop, + DBusList **watches, + int fd) +{ + DBusList *link; + unsigned int flags = 0; + dbus_bool_t interested; + + _dbus_assert (fd != -1); + + if (watches == NULL) + watches = _dbus_hash_table_lookup_int (loop->watches, fd); + + /* we allocated this in the first _dbus_loop_add_watch for the fd, and keep + * it until there are none left */ + _dbus_assert (watches != NULL); + + for (link = _dbus_list_get_first_link (watches); + link != NULL; + link = _dbus_list_get_next_link (watches, link)) + { + if (dbus_watch_get_enabled (link->data) && + !_dbus_watch_get_oom_last_time (link->data)) + { + flags |= dbus_watch_get_flags (link->data); + interested = TRUE; + } + } + + if (interested) + _dbus_socket_set_enable (loop->socket_set, fd, flags); + else + _dbus_socket_set_disable (loop->socket_set, fd); } dbus_bool_t @@ -281,23 +302,43 @@ _dbus_loop_add_watch (DBusLoop *loop, if (watches == NULL) return FALSE; - if (_dbus_list_append (watches, _dbus_watch_ref (watch))) - { - loop->callback_list_serial += 1; - loop->watch_count += 1; - } - else + if (!_dbus_list_append (watches, _dbus_watch_ref (watch))) { _dbus_watch_unref (watch); gc_watch_table_entry (loop, watches, fd); - return FALSE; - } + return FALSE; + } + + if (_dbus_list_length_is_one (watches)) + { + if (!_dbus_socket_set_add (loop->socket_set, fd, + dbus_watch_get_flags (watch), + dbus_watch_get_enabled (watch))) + { + _dbus_hash_table_remove_int (loop->watches, fd); + return FALSE; + } + } + else + { + /* we're modifying, not adding, which can't fail with OOM */ + refresh_watches_for_fd (loop, watches, fd); + } + loop->callback_list_serial += 1; + loop->watch_count += 1; return TRUE; } void +_dbus_loop_toggle_watch (DBusLoop *loop, + DBusWatch *watch) +{ + refresh_watches_for_fd (loop, NULL, dbus_watch_get_socket (watch)); +} + +void _dbus_loop_remove_watch (DBusLoop *loop, DBusWatch *watch) { @@ -329,8 +370,11 @@ _dbus_loop_remove_watch (DBusLoop *loop, _dbus_watch_unref (this); /* if that was the last watch for that fd, drop the hash table - * entry too */ - gc_watch_table_entry (loop, watches, fd); + * entry, and stop reserving space for it in the socket set */ + if (gc_watch_table_entry (loop, watches, fd)) + { + _dbus_socket_set_remove (loop->socket_set, fd); + } return; } @@ -548,23 +592,16 @@ _dbus_loop_iterate (DBusLoop *loop, { #define N_STACK_DESCRIPTORS 64 dbus_bool_t retval; - DBusPollFD *fds; - DBusPollFD stack_fds[N_STACK_DESCRIPTORS]; - int n_fds; + DBusSocketEvent ready_fds[N_STACK_DESCRIPTORS]; int i; DBusList *link; int n_ready; int initial_serial; long timeout; - dbus_bool_t oom_watch_pending; int orig_depth; - DBusHashIter hash_iter; retval = FALSE; - fds = NULL; - n_fds = 0; - oom_watch_pending = FALSE; orig_depth = loop->depth; #if MAINLOOP_SPEW @@ -576,87 +613,6 @@ _dbus_loop_iterate (DBusLoop *loop, loop->timeouts == NULL) goto next_iteration; - if (loop->watch_count > N_STACK_DESCRIPTORS) - { - fds = dbus_new0 (DBusPollFD, loop->watch_count); - - while (fds == NULL) - { - _dbus_wait_for_memory (); - fds = dbus_new0 (DBusPollFD, loop->watch_count); - } - } - else - { - fds = stack_fds; - } - - /* fill our array of fds and watches */ - n_fds = 0; - _dbus_hash_iter_init (loop->watches, &hash_iter); - - while (_dbus_hash_iter_next (&hash_iter)) - { - DBusList **watches; - unsigned int flags; - int fd; - - fd = _dbus_hash_iter_get_int_key (&hash_iter); - watches = _dbus_hash_iter_get_value (&hash_iter); - flags = 0; - - for (link = _dbus_list_get_first_link (watches); - link != NULL; - link = _dbus_list_get_next_link (watches, link)) - { - DBusWatch *watch = link->data; - - if (_dbus_watch_get_oom_last_time (watch)) - { - /* we skip this one this time, but reenable it next time, - * and have a timeout on this iteration - */ - _dbus_watch_set_oom_last_time (watch, FALSE); - oom_watch_pending = TRUE; - - retval = TRUE; /* return TRUE here to keep the loop going, - * since we don't know the watch is inactive - */ - -#if MAINLOOP_SPEW - _dbus_verbose (" skipping watch on fd %d as it was out of memory last time\n", - fd); -#endif - } - else if (dbus_watch_get_enabled (watch)) - { - flags |= dbus_watch_get_flags (watch); - } - } - - if (flags != 0) - { - fds[n_fds].fd = fd; - fds[n_fds].revents = 0; - fds[n_fds].events = watch_flags_to_poll_events (flags); - -#if MAINLOOP_SPEW - _dbus_verbose (" polling watch on fd %d %s\n", - loop->fds[loop->n_fds].fd, watch_flags_to_string (flags)); -#endif - - n_fds += 1; - } - else - { -#if MAINLOOP_SPEW - _dbus_verbose (" skipping disabled watch on fd %d %s\n", - fd, - watch_flags_to_string (dbus_watch_get_flags (watch))); -#endif - } - } - timeout = -1; if (loop->timeout_count > 0) { @@ -712,17 +668,58 @@ _dbus_loop_iterate (DBusLoop *loop, #endif } - /* if a watch is OOM, don't wait longer than the OOM + /* if a watch was OOM last time, don't wait longer than the OOM * wait to re-enable it */ - if (oom_watch_pending) + if (loop->oom_watch_pending) timeout = MIN (timeout, _dbus_get_oom_wait ()); #if MAINLOOP_SPEW _dbus_verbose (" polling on %d descriptors timeout %ld\n", n_fds, timeout); #endif - - n_ready = _dbus_poll (fds, n_fds, timeout); + + n_ready = _dbus_socket_set_poll (loop->socket_set, ready_fds, + _DBUS_N_ELEMENTS (ready_fds), timeout); + + /* re-enable any watches we skipped this time */ + if (loop->oom_watch_pending) + { + DBusHashIter hash_iter; + + loop->oom_watch_pending = FALSE; + + _dbus_hash_iter_init (loop->watches, &hash_iter); + + while (_dbus_hash_iter_next (&hash_iter)) + { + DBusList **watches; + int fd; + dbus_bool_t changed; + + changed = FALSE; + fd = _dbus_hash_iter_get_int_key (&hash_iter); + watches = _dbus_hash_iter_get_value (&hash_iter); + + for (link = _dbus_list_get_first_link (watches); + link != NULL; + link = _dbus_list_get_next_link (watches, link)) + { + DBusWatch *watch = link->data; + + if (_dbus_watch_get_oom_last_time (watch)) + { + _dbus_watch_set_oom_last_time (watch, FALSE); + changed = TRUE; + } + } + + if (changed) + refresh_watches_for_fd (loop, watches, fd); + } + + retval = TRUE; /* return TRUE here to keep the loop going, + * since we don't know the watch was inactive */ + } initial_serial = loop->callback_list_serial; @@ -785,14 +782,15 @@ _dbus_loop_iterate (DBusLoop *loop, link = next; } } - + if (n_ready > 0) { - for (i = 0; i < n_fds; i++) + for (i = 0; i < n_ready; i++) { DBusList **watches; DBusList *next; unsigned int condition; + dbus_bool_t any_oom; /* FIXME I think this "restart if we change the watches" * approach could result in starving watches @@ -804,16 +802,16 @@ _dbus_loop_iterate (DBusLoop *loop, if (loop->depth != orig_depth) goto next_iteration; - if (fds[i].revents == 0) - continue; + _dbus_assert (ready_fds[i].flags != 0); - if (_DBUS_UNLIKELY (fds[i].revents & _DBUS_POLLNVAL)) + if (_DBUS_UNLIKELY (ready_fds[i].flags & _DBUS_WATCH_NVAL)) { - cull_watches_for_invalid_fd (loop, fds[i].fd); + cull_watches_for_invalid_fd (loop, ready_fds[i].fd); goto next_iteration; } - condition = watch_flags_from_poll_revents (fds[i].revents); + condition = ready_fds[i].flags; + _dbus_assert ((condition & _DBUS_WATCH_NVAL) == 0); /* condition may still be 0 if we got some * weird POLLFOO thing like POLLWRBAND @@ -821,11 +819,14 @@ _dbus_loop_iterate (DBusLoop *loop, if (condition == 0) continue; - watches = _dbus_hash_table_lookup_int (loop->watches, fds[i].fd); + watches = _dbus_hash_table_lookup_int (loop->watches, + ready_fds[i].fd); if (watches == NULL) continue; + any_oom = FALSE; + for (link = _dbus_list_get_first_link (watches); link != NULL; link = next) @@ -843,6 +844,8 @@ _dbus_loop_iterate (DBusLoop *loop, if (oom) { _dbus_watch_set_oom_last_time (watch, TRUE); + loop->oom_watch_pending = TRUE; + any_oom = TRUE; } #if MAINLOOP_SPEW @@ -853,13 +856,19 @@ _dbus_loop_iterate (DBusLoop *loop, /* We re-check this every time, in case the callback * added/removed watches, which might make our position in * the linked list invalid. See the FIXME above. */ - if (initial_serial != loop->callback_list_serial) - goto next_iteration; + if (initial_serial != loop->callback_list_serial || + loop->depth != orig_depth) + { + if (any_oom) + refresh_watches_for_fd (loop, NULL, ready_fds[i].fd); - if (loop->depth != orig_depth) - goto next_iteration; + goto next_iteration; + } } } + + if (any_oom) + refresh_watches_for_fd (loop, watches, ready_fds[i].fd); } } @@ -867,9 +876,6 @@ _dbus_loop_iterate (DBusLoop *loop, #if MAINLOOP_SPEW _dbus_verbose (" moving to next iteration\n"); #endif - - if (fds && fds != stack_fds) - dbus_free (fds); if (_dbus_loop_dispatch (loop)) retval = TRUE; diff --git a/dbus/dbus-mainloop.h b/dbus/dbus-mainloop.h index a3417adb..a76cb6f0 100644 --- a/dbus/dbus-mainloop.h +++ b/dbus/dbus-mainloop.h @@ -41,6 +41,8 @@ dbus_bool_t _dbus_loop_add_watch (DBusLoop *loop, DBusWatch *watch); void _dbus_loop_remove_watch (DBusLoop *loop, DBusWatch *watch); +void _dbus_loop_toggle_watch (DBusLoop *loop, + DBusWatch *watch); dbus_bool_t _dbus_loop_add_timeout (DBusLoop *loop, DBusTimeout *timeout); void _dbus_loop_remove_timeout (DBusLoop *loop, diff --git a/dbus/dbus-socket-set-epoll.c b/dbus/dbus-socket-set-epoll.c new file mode 100644 index 00000000..4cd9a563 --- /dev/null +++ b/dbus/dbus-socket-set-epoll.c @@ -0,0 +1,371 @@ +/* -*- 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 <config.h> +#include "dbus-socket-set.h" + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-sysdeps.h> + +#ifndef __linux__ +# error This file is for Linux epoll(4) +#endif + +#include <errno.h> +#include <fcntl.h> +#include <sys/epoll.h> +#include <unistd.h> + +#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) + { + int flags; + + /* the size hint is ignored unless you have a rather old kernel, + * but must be positive on some versions, so just pick something + * arbitrary; it's a hint, not a limit */ + self->epfd = epoll_create (42); + + flags = fcntl (self->epfd, F_GETFD, 0); + + if (flags != -1) + fcntl (self->epfd, F_SETFD, flags | FD_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; + /* Kernels < 2.6.9 require a non-NULL struct pointer, even though its + * contents are ignored */ + struct epoll_event dummy = { 0 }; + + if (epoll_ctl (self->epfd, EPOLL_CTL_DEL, fd, &dummy) == 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 <sys/epoll.h> + +#include <stdio.h> + +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-poll.c b/dbus/dbus-socket-set-poll.c new file mode 100644 index 00000000..65a1fd23 --- /dev/null +++ b/dbus/dbus-socket-set-poll.c @@ -0,0 +1,320 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-socket-set-poll.c - a socket set implemented via _dbus_poll + * + * 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 <config.h> +#include "dbus-socket-set.h" + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-watch.h> + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +typedef struct { + DBusSocketSet parent; + DBusPollFD *fds; + int n_fds; + int n_reserved; + int n_allocated; +} DBusSocketSetPoll; + +#define REALLOC_INCREMENT 8 +#define MINIMUM_SIZE 8 + +/* If we're in the regression tests, force reallocation to happen sooner */ +#ifdef DBUS_BUILD_TESTS +#define DEFAULT_SIZE_HINT 1 +#else +#define DEFAULT_SIZE_HINT MINIMUM_SIZE +#endif + +static inline DBusSocketSetPoll * +socket_set_poll_cast (DBusSocketSet *set) +{ + _dbus_assert (set->cls == &_dbus_socket_set_poll_class); + return (DBusSocketSetPoll *) set; +} + +/* this is safe to call on a partially-allocated socket set */ +static void +socket_set_poll_free (DBusSocketSet *set) +{ + DBusSocketSetPoll *self = socket_set_poll_cast (set); + + dbus_free (self->fds); + dbus_free (self); + _dbus_verbose ("freed socket set %p\n", self); +} + +DBusSocketSet * +_dbus_socket_set_poll_new (int size_hint) +{ + DBusSocketSetPoll *ret; + + if (size_hint <= 0) + size_hint = DEFAULT_SIZE_HINT; + + ret = dbus_new0 (DBusSocketSetPoll, 1); + + if (ret == NULL) + return NULL; + + ret->parent.cls = &_dbus_socket_set_poll_class; + ret->n_fds = 0; + ret->n_allocated = size_hint; + + ret->fds = dbus_new0 (DBusPollFD, size_hint); + + if (ret->fds == NULL) + { + /* socket_set_poll_free specifically supports half-constructed + * socket sets */ + socket_set_poll_free ((DBusSocketSet *) ret); + return NULL; + } + + _dbus_verbose ("new socket set at %p\n", ret); + return (DBusSocketSet *) ret; +} + +static short +watch_flags_to_poll_events (unsigned int flags) +{ + short events = 0; + + if (flags & DBUS_WATCH_READABLE) + events |= _DBUS_POLLIN; + if (flags & DBUS_WATCH_WRITABLE) + events |= _DBUS_POLLOUT; + + return events; +} + +static dbus_bool_t +socket_set_poll_add (DBusSocketSet *set, + int fd, + unsigned int flags, + dbus_bool_t enabled) +{ + DBusSocketSetPoll *self = socket_set_poll_cast (set); +#ifndef DBUS_DISABLE_ASSERT + int i; + + for (i = 0; i < self->n_fds; i++) + _dbus_assert (self->fds[i].fd != fd); +#endif + + if (self->n_reserved >= self->n_allocated) + { + DBusPollFD *new_fds = dbus_realloc (self->fds, + sizeof (DBusPollFD) * (self->n_allocated + REALLOC_INCREMENT)); + + _dbus_verbose ("inflating set %p from %d en/%d res/%d alloc to %d\n", + self, self->n_fds, self->n_reserved, self->n_allocated, + self->n_allocated + REALLOC_INCREMENT); + + if (new_fds == NULL) + return FALSE; + + self->fds = new_fds; + self->n_allocated += REALLOC_INCREMENT; + } + + _dbus_verbose ("before adding fd %d to %p, %d en/%d res/%d alloc\n", + fd, self, self->n_fds, self->n_reserved, self->n_allocated); + _dbus_assert (self->n_reserved >= self->n_fds); + _dbus_assert (self->n_allocated > self->n_reserved); + + self->n_reserved++; + + if (enabled) + { + self->fds[self->n_fds].fd = fd; + self->fds[self->n_fds].events = watch_flags_to_poll_events (flags); + self->n_fds++; + } + + return TRUE; +} + +static void +socket_set_poll_enable (DBusSocketSet *set, + int fd, + unsigned int flags) +{ + DBusSocketSetPoll *self = socket_set_poll_cast (set); + int i; + + for (i = 0; i < self->n_fds; i++) + { + if (self->fds[i].fd == fd) + { + self->fds[i].events = watch_flags_to_poll_events (flags); + return; + } + } + + /* we allocated space when the socket was added */ + _dbus_assert (self->n_fds < self->n_reserved); + _dbus_assert (self->n_reserved <= self->n_allocated); + + self->fds[self->n_fds].fd = fd; + self->fds[self->n_fds].events = watch_flags_to_poll_events (flags); + self->n_fds++; +} + +static void +socket_set_poll_disable (DBusSocketSet *set, + int fd) +{ + DBusSocketSetPoll *self = socket_set_poll_cast (set); + int i; + + for (i = 0; i < self->n_fds; i++) + { + if (self->fds[i].fd == fd) + { + if (i != self->n_fds - 1) + { + self->fds[i].fd = self->fds[self->n_fds - 1].fd; + self->fds[i].events = self->fds[self->n_fds - 1].events; + } + + self->n_fds--; + return; + } + } +} + +static void +socket_set_poll_remove (DBusSocketSet *set, + int fd) +{ + DBusSocketSetPoll *self = socket_set_poll_cast (set); + + socket_set_poll_disable (set, fd); + self->n_reserved--; + + _dbus_verbose ("after removing fd %d from %p, %d en/%d res/%d alloc\n", + fd, self, self->n_fds, self->n_reserved, self->n_allocated); + _dbus_assert (self->n_fds <= self->n_reserved); + _dbus_assert (self->n_reserved <= self->n_allocated); + + if (self->n_reserved + MINIMUM_SIZE < self->n_allocated / 2) + { + /* Our array is twice as big as it needs to be - deflate it until it's + * only slightly larger than the number reserved. */ + DBusPollFD *new_fds = dbus_realloc (self->fds, + sizeof (DBusPollFD) * (self->n_reserved + MINIMUM_SIZE)); + + _dbus_verbose ("before deflating %p, %d en/%d res/%d alloc\n", + self, self->n_fds, self->n_reserved, self->n_allocated); + + if (_DBUS_UNLIKELY (new_fds == NULL)) + { + /* Weird. Oh well, never mind, the too-big array is untouched */ + return; + } + + self->fds = new_fds; + self->n_allocated = self->n_reserved; + } +} + +static unsigned int +watch_flags_from_poll_revents (short revents) +{ + unsigned int condition = 0; + + if (revents & _DBUS_POLLIN) + condition |= DBUS_WATCH_READABLE; + if (revents & _DBUS_POLLOUT) + condition |= DBUS_WATCH_WRITABLE; + if (revents & _DBUS_POLLHUP) + condition |= DBUS_WATCH_HANGUP; + if (revents & _DBUS_POLLERR) + condition |= DBUS_WATCH_ERROR; + + if (_DBUS_UNLIKELY (revents & _DBUS_POLLNVAL)) + condition |= _DBUS_WATCH_NVAL; + + return condition; +} + +/** 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. */ +static int +socket_set_poll_poll (DBusSocketSet *set, + DBusSocketEvent *revents, + int max_events, + int timeout_ms) +{ + DBusSocketSetPoll *self = socket_set_poll_cast (set); + int i; + int n_events; + int n_ready; + + _dbus_assert (max_events > 0); + + for (i = 0; i < self->n_fds; i++) + self->fds[i].revents = 0; + + n_ready = _dbus_poll (self->fds, self->n_fds, timeout_ms); + + if (n_ready <= 0) + return n_ready; + + n_events = 0; + + for (i = 0; i < self->n_fds; i++) + { + if (self->fds[i].revents != 0) + { + revents[n_events].fd = self->fds[i].fd; + revents[n_events].flags = watch_flags_from_poll_revents (self->fds[i].revents); + + n_events += 1; + + /* We ignore events beyond max_events because we have nowhere to + * put them. _dbus_poll is level-triggered, so we'll just be told + * about them next time round the main loop anyway. */ + if (n_events == max_events) + return n_events; + } + } + + return n_events; +} + +DBusSocketSetClass _dbus_socket_set_poll_class = { + socket_set_poll_free, + socket_set_poll_add, + socket_set_poll_remove, + socket_set_poll_enable, + socket_set_poll_disable, + socket_set_poll_poll +}; + +#endif /* !DOXYGEN_SHOULD_SKIP_THIS */ diff --git a/dbus/dbus-socket-set.c b/dbus/dbus-socket-set.c new file mode 100644 index 00000000..210d600e --- /dev/null +++ b/dbus/dbus-socket-set.c @@ -0,0 +1,47 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * dbus-socket-set.c - used to bolt file descriptors onto a bus + * + * 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 <config.h> +#include <dbus/dbus-socket-set.h> + +DBusSocketSet * +_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) + return ret; + + return NULL; +} diff --git a/dbus/dbus-socket-set.h b/dbus/dbus-socket-set.h new file mode 100644 index 00000000..3b71a925 --- /dev/null +++ b/dbus/dbus-socket-set.h @@ -0,0 +1,122 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * dbus-socket-set.h - used to bolt file descriptors onto a bus + * + * 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 + * + */ + +#ifndef DBUS_SOCKET_SET_H +#define DBUS_SOCKET_SET_H + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include <dbus/dbus.h> + +typedef struct { + int fd; + unsigned int flags; +} DBusSocketEvent; + +typedef struct DBusSocketSet DBusSocketSet; + +typedef struct DBusSocketSetClass DBusSocketSetClass; +struct DBusSocketSetClass { + void (*free) (DBusSocketSet *self); + dbus_bool_t (*add) (DBusSocketSet *self, + int fd, + unsigned int flags, + dbus_bool_t enabled); + void (*remove) (DBusSocketSet *self, + int fd); + void (*enable) (DBusSocketSet *self, + int fd, + unsigned int flags); + void (*disable) (DBusSocketSet *self, + int fd); + int (*poll) (DBusSocketSet *self, + DBusSocketEvent *revents, + int max_events, + int timeout_ms); +}; + +struct DBusSocketSet { + DBusSocketSetClass *cls; +}; + +DBusSocketSet *_dbus_socket_set_new (int size_hint); + +static inline void +_dbus_socket_set_free (DBusSocketSet *self) +{ + (self->cls->free) (self); +} + +static inline dbus_bool_t +_dbus_socket_set_add (DBusSocketSet *self, + int fd, + unsigned int flags, + dbus_bool_t enabled) +{ + return (self->cls->add) (self, fd, flags, enabled); +} + +static inline void +_dbus_socket_set_remove (DBusSocketSet *self, + int fd) +{ + (self->cls->remove) (self, fd); +} + +static inline void +_dbus_socket_set_enable (DBusSocketSet *self, + int fd, + unsigned int flags) +{ + (self->cls->enable) (self, fd, flags); +} + +static inline void +_dbus_socket_set_disable (DBusSocketSet *self, + int fd) +{ + (self->cls->disable) (self, fd); +} + + +static inline int +_dbus_socket_set_poll (DBusSocketSet *self, + DBusSocketEvent *revents, + int max_events, + int timeout_ms) +{ + return (self->cls->poll) (self, revents, max_events, timeout_ms); +} + +/* 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 */ diff --git a/dbus/dbus-watch.h b/dbus/dbus-watch.h index dd23b862..c5832141 100644 --- a/dbus/dbus-watch.h +++ b/dbus/dbus-watch.h @@ -37,6 +37,8 @@ DBUS_BEGIN_DECLS typedef struct DBusWatchList DBusWatchList; +#define _DBUS_WATCH_NVAL (1<<4) + /** function to run when the watch is handled */ typedef dbus_bool_t (* DBusWatchHandler) (DBusWatch *watch, unsigned int flags, diff --git a/test/test-utils.c b/test/test-utils.c index 4fd84fe8..c3c3ed34 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -26,6 +26,15 @@ remove_watch (DBusWatch *watch, _dbus_loop_remove_watch (cd->loop, watch); } +static void +toggle_watch (DBusWatch *watch, + void *data) +{ + CData *cd = data; + + _dbus_loop_toggle_watch (cd->loop, watch); +} + static dbus_bool_t add_timeout (DBusTimeout *timeout, void *data) @@ -103,15 +112,10 @@ test_connection_setup (DBusLoop *loop, if (cd == NULL) goto nomem; - /* Because dbus-mainloop.c checks dbus_timeout_get_enabled(), - * dbus_watch_get_enabled() directly, we don't have to provide - * "toggled" callbacks. - */ - if (!dbus_connection_set_watch_functions (connection, add_watch, remove_watch, - NULL, + toggle_watch, cd, cdata_free)) goto nomem; @@ -213,6 +217,15 @@ add_server_watch (DBusWatch *watch, } static void +toggle_server_watch (DBusWatch *watch, + void *data) +{ + ServerData *context = data; + + _dbus_loop_toggle_watch (context->loop, watch); +} + +static void remove_server_watch (DBusWatch *watch, void *data) { @@ -252,7 +265,7 @@ test_server_setup (DBusLoop *loop, if (!dbus_server_set_watch_functions (server, add_server_watch, remove_server_watch, - NULL, + toggle_server_watch, sd, serverdata_free)) { |