WebSockets: additional method for event loop integration

Some distro repositories offer versions of libwebsockets that have not been
compiled with LWS_WITH_GLIB or LWS_WITH_EXTERNAL_POLL enabled. For such cases
a different event loop integration method is needed.

True for Ubuntu 20.04 as of Jun '21
This commit is contained in:
Luciano Iam 2021-06-13 18:21:24 +02:00
parent a5a952be2a
commit 2bea7afcd4
2 changed files with 103 additions and 44 deletions

View file

@ -52,6 +52,8 @@ using namespace ArdourSurface;
WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface)
: SurfaceComponent (surface)
, _lws_context (0)
, _fd_callbacks (false)
, _g_source (0)
{
/* keep references to all config for libwebsockets 2 */
lws_protocols proto;
@ -125,13 +127,28 @@ WebsocketsServer::start ()
stop ();
}
/* Event loop integration method depends on how libwebsockets is configured
* for Ardour build environment and how libwebsockets is compiled for the
* system running Ardour. */
#ifdef LWS_WITH_GLIB
void *foreign_loops[1];
foreign_loops[0] = main_loop ()->gobj ();
_lws_info.foreign_loops = foreign_loops;
_lws_info.options = LWS_SERVER_OPTION_GLIB;
_lws_context = lws_create_context (&_lws_info);
#endif
if (_lws_context) {
/* LWS_WITH_GLIB was enabled for Ardour build environment libwebsockets
and also for the version the user has installed in their system */
PBD::info << "ArdourWebsockets: using event loop integration method 1" << endmsg;
} else {
/* Either Ardour build environment libwebsockets was not configured with
LWS_WITH_GLIB enabled or user installed library is missing the feature */
_fd_callbacks = true;
_lws_info.foreign_loops = 0;
_lws_info.options = 0;
_lws_context = lws_create_context (&_lws_info);
if (!_lws_context) {
@ -139,16 +156,17 @@ WebsocketsServer::start ()
return -1;
}
#ifndef LWS_WITH_GLIB
/* sometimes LWS_WITH_EXTERNAL_POLL is missing from lws_config.h
but the feature is still available, hence this runtime check */
if (_fd_ctx.empty ()) {
PBD::error << "ArdourWebsockets: check your libwebsockets was compiled"
" with LWS_WITH_GLIB or LWS_WITH_EXTERNAL_POLL enabled"
<< endmsg;
return -1;
if (!_fd_ctx.empty ()) {
// LWS_CALLBACK_ADD_POLL_FD was called, LWS_WITH_EXTERNAL_POLL is available
PBD::info << "ArdourWebsockets: using event loop integration method 2" << endmsg;
} else {
// Neither LWS_WITH_EXTERNAL_POLL or LWS_WITH_GLIB are available
PBD::info << "ArdourWebsockets: using event loop integration method 3" << endmsg;
_g_source = g_idle_source_new();
g_source_set_callback (_g_source, WebsocketsServer::glib_idle_callback, _lws_context, 0);
g_source_attach (_g_source, g_main_loop_get_context (main_loop ()->gobj ()));
}
}
#endif
PBD::info << "ArdourWebsockets: listening on: http://"
<< lws_canonical_hostname (_lws_context)
@ -163,7 +181,7 @@ WebsocketsServer::start ()
int
WebsocketsServer::stop ()
{
#ifndef LWS_WITH_GLIB
if (!_fd_ctx.empty ()) { // Method 2
for (LwsPollFdGlibSourceMap::iterator it = _fd_ctx.begin (); it != _fd_ctx.end (); ++it) {
it->second.rg_iosrc->destroy ();
@ -173,7 +191,11 @@ WebsocketsServer::stop ()
}
_fd_ctx.clear ();
#endif
}
if (_g_source) { // Method 3
g_source_destroy (_g_source);
}
if (_lws_context) {
lws_context_destroy (_lws_context);
@ -405,19 +427,30 @@ WebsocketsServer::lws_callback (struct lws* wsi, enum lws_callback_reasons reaso
rc = server->send_availsurf_body (wsi);
break;
#ifndef LWS_WITH_GLIB
/* fd callbacks must be skipped for integration method 1 */
case LWS_CALLBACK_ADD_POLL_FD:
if (server->fd_callbacks()) {
rc = server->add_poll_fd (static_cast<struct lws_pollargs*> (in));
} else {
rc = 0;
}
break;
case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
if (server->fd_callbacks()) {
rc = server->mod_poll_fd (static_cast<struct lws_pollargs*> (in));
} else {
rc = 0;
}
break;
case LWS_CALLBACK_DEL_POLL_FD:
if (server->fd_callbacks()) {
rc = server->del_poll_fd (static_cast<struct lws_pollargs*> (in));
} else {
rc = 0;
}
break;
#endif // LWS_WITH_GLIB
#if LWS_LIBRARY_VERSION_NUM >= 2001000
// lws_callback_http_dummy is not available on lws < 2.1.0
@ -453,7 +486,6 @@ WebsocketsServer::lws_callback (struct lws* wsi, enum lws_callback_reasons reaso
return rc;
}
#ifndef LWS_WITH_GLIB
int
WebsocketsServer::add_poll_fd (struct lws_pollargs* pa)
{
@ -597,4 +629,18 @@ WebsocketsServer::ioc_to_events (IOCondition ioc)
return events;
}
#endif // LWS_WITH_GLIB
void
WebsocketsServer::request_write ()
{
// cancel lws_service() in the idle callback to write pending data asap
lws_cancel_service (_lws_context);
}
gboolean
WebsocketsServer::glib_idle_callback (void *data)
{
struct lws_context *lws_ctx = static_cast<struct lws_context *>(data);
lws_service (lws_ctx, 0);
return TRUE;
}

View file

@ -39,16 +39,6 @@
// TO DO: make this configurable
#define WEBSOCKET_LISTEN_PORT 3818
// lws includes integration with the glib event loop starting from v4
#ifndef LWS_WITH_GLIB
struct LwsPollFdGlibSource {
struct lws_pollfd lws_pfd;
Glib::RefPtr<Glib::IOChannel> g_channel;
Glib::RefPtr<Glib::IOSource> rg_iosrc;
Glib::RefPtr<Glib::IOSource> wg_iosrc;
};
#endif
namespace ArdourSurface {
class WebsocketsServer : public SurfaceComponent
@ -87,12 +77,24 @@ private:
static int lws_callback (struct lws*, enum lws_callback_reasons, void*, void*, size_t);
#ifndef LWS_WITH_GLIB
/* Glib event loop integration that requires LWS_WITH_EXTERNAL_POLL */
struct LwsPollFdGlibSource {
struct lws_pollfd lws_pfd;
Glib::RefPtr<Glib::IOChannel> g_channel;
Glib::RefPtr<Glib::IOSource> rg_iosrc;
Glib::RefPtr<Glib::IOSource> wg_iosrc;
};
Glib::RefPtr<Glib::IOChannel> _channel;
typedef boost::unordered_map<lws_sockfd_type, LwsPollFdGlibSource> LwsPollFdGlibSourceMap;
LwsPollFdGlibSourceMap _fd_ctx;
bool _fd_callbacks;
bool fd_callbacks () { return _fd_callbacks; }
int add_poll_fd (struct lws_pollargs*);
int mod_poll_fd (struct lws_pollargs*);
int del_poll_fd (struct lws_pollargs*);
@ -101,7 +103,18 @@ private:
Glib::IOCondition events_to_ioc (int);
int ioc_to_events (Glib::IOCondition);
#endif
/* Glib event loop integration that does NOT require LWS_WITH_EXTERNAL_POLL
but needs a secondary thread for notifying the server when there is
pending data for writing. Unfortunately libwesockets' own approach to
Glib integration cannot be copied because it relies on file descriptors
that are hidden by the 'lws' opaque type. See feedback.cc . */
GSource* _g_source;
void request_write ();
static gboolean glib_idle_callback (void *data);
};
} // namespace ArdourSurface