server: stop wl_display event loop from any context
authorDamian Hobson-Garcia <dhobsong@igel.co.jp>
Tue, 31 Aug 2021 09:59:33 +0000 (18:59 +0900)
committerDaniel Stone <daniels@collabora.com>
Sat, 9 Oct 2021 13:09:04 +0000 (13:09 +0000)
Calling wl_display_terminate() will exit the wl_display event loop
at the start of the next loop iteration.  This works fine when
wl_display_terminate() is called after the event loop wakes up
from polling on the added event sources.  If, however, it is
called before polling starts, the event loop will not exit until
one or more event sources trigger.  Depending on the types of event
sources, they may never trigger (or may not trigger for a long time),
so the event loop may never exit.

Add an extra event source to the wl_display event loop that will trigger
whenever wl_display_terminate() is called, so that the event loop will
always exit.

Fixes #201

Signed-off-by: Damian Hobson-Garcia <dhobsong@igel.co.jp>
src/wayland-server.c
tests/display-test.c

index 4778d85..6ae4689 100644 (file)
@@ -40,6 +40,7 @@
 #include <assert.h>
 #include <sys/time.h>
 #include <fcntl.h>
+#include <sys/eventfd.h>
 #include <sys/file.h>
 #include <sys/stat.h>
 
@@ -105,6 +106,9 @@ struct wl_display {
 
        wl_display_global_filter_func_t global_filter;
        void *global_filter_data;
+
+       int terminate_efd;
+       struct wl_event_source *term_source;
 };
 
 struct wl_global {
@@ -1030,6 +1034,16 @@ bind_display(struct wl_client *client, struct wl_display *display)
        return 0;
 }
 
+static int
+handle_display_terminate(int fd, uint32_t mask, void *data) {
+       uint64_t term_event;
+
+       if (read(fd, &term_event, sizeof(term_event)) < 0 && errno != EAGAIN)
+               return -1;
+
+       return 0;
+}
+
 /** Create Wayland display object.
  *
  * \return The Wayland display object. Null if failed to create
@@ -1058,6 +1072,19 @@ wl_display_create(void)
                return NULL;
        }
 
+       display->terminate_efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+       if (display->terminate_efd < 0)
+               goto err_eventfd;
+
+       display->term_source = wl_event_loop_add_fd(display->loop,
+                                                   display->terminate_efd,
+                                                   WL_EVENT_READABLE,
+                                                   handle_display_terminate,
+                                                   NULL);
+
+       if (display->term_source == NULL)
+               goto err_term_source;
+
        wl_list_init(&display->global_list);
        wl_list_init(&display->socket_list);
        wl_list_init(&display->client_list);
@@ -1076,6 +1103,13 @@ wl_display_create(void)
        wl_array_init(&display->additional_shm_formats);
 
        return display;
+
+err_term_source:
+       close(display->terminate_efd);
+err_eventfd:
+       wl_event_loop_destroy(display->loop);
+       free(display);
+       return NULL;
 }
 
 static void
@@ -1135,6 +1169,10 @@ wl_display_destroy(struct wl_display *display)
        wl_list_for_each_safe(s, next, &display->socket_list, link) {
                wl_socket_destroy(s);
        }
+
+       close(display->terminate_efd);
+       wl_event_source_remove(display->term_source);
+
        wl_event_loop_destroy(display->loop);
 
        wl_list_for_each_safe(global, gnext, &display->global_list, link)
@@ -1351,7 +1389,13 @@ wl_display_get_event_loop(struct wl_display *display)
 WL_EXPORT void
 wl_display_terminate(struct wl_display *display)
 {
+       int ret;
+       uint64_t terminate = 1;
+
        display->run = 0;
+
+       ret = write(display->terminate_efd, &terminate, sizeof(terminate));
+       assert (ret >= 0 || errno == EAGAIN);
 }
 
 WL_EXPORT void
index 3db7c95..763adc9 100644 (file)
@@ -1629,3 +1629,24 @@ TEST(global_remove)
 
        display_destroy(d);
 }
+
+static void
+terminate_display(void *arg)
+{
+       struct wl_display *wl_display = arg;
+       wl_display_terminate(wl_display);
+}
+
+TEST(no_source_terminate)
+{
+       struct display *d;
+       struct wl_event_loop *loop;
+
+       d = display_create();
+       loop = wl_display_get_event_loop(d->wl_display);
+
+       wl_event_loop_add_idle(loop, terminate_display, d->wl_display);
+
+       display_run(d);
+       display_destroy(d);
+}