Use ppoll to allow signals to wake service loop early on change of pollfd event
To enable this code you need to force LWS_HAS_PPOLL to de defined.
#defining it at the top of libwebsockets.c is enough.
Signed-off-by: Andy Green <andy.green@linaro.org>
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 687bad6..6dc4d1d 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -37,6 +37,8 @@
#include <netdb.h>
#endif
+#include <sys/types.h>
+
#ifdef LWS_OPENSSL_SUPPORT
int openssl_websocket_private_data_index;
#endif
@@ -83,6 +85,17 @@
struct libwebsocket *wsi, struct pollfd *pollfd);
#endif
+
+#ifdef LWS_HAS_PPOLL
+/*
+ * set to the Thread ID that's doing the service loop just before entry to ppoll
+ * indicates service thread likely idling in ppoll()
+ * volatile because other threads may check it as part of processing for pollfd
+ * event change.
+ */
+static volatile int lws_idling_ppoll_tid;
+#endif
+
/**
* lws_get_library_version: get version and git hash library built from
*
@@ -1325,15 +1338,32 @@
{
int n;
int m;
+#ifdef LWS_HAS_PPOLL
+ struct timespec timeout_ts;
+ sigset_t sigmask;
+#endif
/* stay dead once we are dead */
if (context == NULL)
return 1;
+#ifdef LWS_HAS_PPOLL
+ lws_idling_ppoll_tid = context->protocols[0].callback(context, NULL,
+ LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
+
+ timeout_ts.tv_sec = timeout_ms / 1000;
+ timeout_ts.tv_nsec = timeout_ms % 1000;
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGUSR2);
+
/* wait for something to need service */
+ n = ppoll(context->fds, context->fds_count, &timeout_ts, &sigmask);
+ lws_idling_ppoll_tid = 0;
+#else
n = poll(context->fds, context->fds_count, timeout_ms);
+#endif
if (n == 0) /* poll timeout */ {
libwebsocket_service_fd(context, NULL);
return 0;
@@ -1408,13 +1438,19 @@
lws_change_pollfd(struct libwebsocket *wsi, int _and, int _or)
{
struct libwebsocket_context *context = wsi->protocol->owning_server;
+ int events;
+#ifdef LWS_HAS_PPOLL
+ int tid;
+ int sampled_ppoll_tid;
+#endif
context->protocols[0].callback(context, wsi,
LWS_CALLBACK_LOCK_POLL,
wsi->user_space, (void *)(long)wsi->sock, 0);
- context->fds[wsi->position_in_fds_table].events &= ~_and;
- context->fds[wsi->position_in_fds_table].events |= _or;
+ events = context->fds[wsi->position_in_fds_table].events;
+
+ context->fds[wsi->position_in_fds_table].events = (events & ~_and) | _or;
/* external POLL support via protocol 0 */
if (_and)
@@ -1427,6 +1463,26 @@
LWS_CALLBACK_SET_MODE_POLL_FD,
wsi->user_space, (void *)(long)wsi->sock, _or);
+#ifdef LWS_HAS_PPOLL
+ /*
+ * if we changed something in this pollfd...
+ * ... and we're running in a different thread context
+ * than the service thread...
+ * ... and the service thread is waiting in ppoll()...
+ * then fire a SIGUSR2 at the service thread to force it to
+ * restart the ppoll() with our changed events
+ */
+ if (events != context->fds[wsi->position_in_fds_table].events) {
+ sampled_ppoll_tid = lws_idling_ppoll_tid;
+ if (sampled_ppoll_tid) {
+ tid = context->protocols[0].callback(context, NULL,
+ LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
+ if (tid != sampled_ppoll_tid)
+ kill(sampled_ppoll_tid, SIGUSR2);
+ }
+ }
+#endif
+
context->protocols[0].callback(context, wsi,
LWS_CALLBACK_UNLOCK_POLL,
wsi->user_space, (void *)(long)wsi->sock, 0);
@@ -1747,6 +1803,13 @@
return n;
}
+/*
+ * This is just used to interrupt poll waiting
+ * we don't have to do anything with it.
+ */
+static void lws_sigusr2(int sig)
+{
+}
/**
* libwebsocket_create_context() - Create the websocket handler
@@ -2036,6 +2099,8 @@
goto bail;
}
+ signal(SIGUSR2, lws_sigusr2);
+
#ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options(context->ssl_ctx, SSL_OP_NO_COMPRESSION);
#endif
@@ -2349,6 +2414,7 @@
}
}
#endif
+
return context;
bail:
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index f957e54..16f294f 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -63,6 +63,9 @@
#endif
#else // NOT WIN32
+
+/* to get ppoll() */
+#define __USE_GNU
#include <poll.h>
#include <unistd.h>
@@ -169,6 +172,8 @@
LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
LWS_CALLBACK_PROTOCOL_INIT,
LWS_CALLBACK_PROTOCOL_DESTROY,
+ LWS_CALLBACK_GET_THREAD_ID,
+
/* external poll() management support */
LWS_CALLBACK_ADD_POLL_FD,
LWS_CALLBACK_DEL_POLL_FD,
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index a7236c6..332416b 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -93,6 +93,8 @@
#include <netinet/tcp.h>
#include <arpa/inet.h>
+/* to get ppoll() */
+#define __USE_GNU
#include <poll.h>
#include <sys/mman.h>
#include <sys/time.h>
diff --git a/test-server/test-server.c b/test-server/test-server.c
index 42bdf18..eb275c5 100644
--- a/test-server/test-server.c
+++ b/test-server/test-server.c
@@ -474,6 +474,19 @@
break;
#endif
+ case LWS_CALLBACK_GET_THREAD_ID:
+ /*
+ * if you will call "libwebsocket_callback_on_writable"
+ * from a different thread, return the caller thread ID
+ * here so lws can use this information to work out if it
+ * should signal the ppoll() loop to exit and restart early
+ * (only applies if the library has LWS_HAS_PPOLL
+ */
+
+ /* return pthread_getthreadid_np(); */
+
+ break;
+
default:
break;
}