ipc/server: Factor out the mainloop code that may vary between platforms

This commit is contained in:
Ryan Pavlik 2021-02-08 12:50:21 -06:00
parent d829fac08c
commit f15a14b193
6 changed files with 393 additions and 210 deletions

View file

@ -0,0 +1 @@
Factor out the IPC server mainloop into a per-platform structure.

View file

@ -126,4 +126,8 @@ if(ANDROID)
aux_android
ipc_android
)
elseif(XRT_HAVE_LINUX)
target_sources(ipc_server PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/server/ipc_server_mainloop_linux.c
)
endif()

View file

@ -64,6 +64,7 @@ lib_ipc_server = static_library(
'server/ipc_server_handler.c',
'server/ipc_server_per_client_thread.c',
'server/ipc_server_process.c',
'server/ipc_server_mainloop_linux.c',
],
include_directories: [
xrt_include,

View file

@ -1,10 +1,11 @@
// Copyright 2020, Collabora, Ltd.
// Copyright 2020-2021, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Common server side code.
* @author Pete Black <pblack@collabora.com>
* @author Jakob Bornecrantz <jakob@collabora.com>
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup ipc_server
*/
@ -147,6 +148,61 @@ struct ipc_device
bool io_active;
};
/*!
* Platform-specific mainloop object for the IPC server.
*
* Contents are essentially implementation details, but are listed in full here so they may be included by value in the
* main ipc_server struct.
*
* @ingroup ipc_server
*/
struct ipc_server_mainloop
{
#if defined(XRT_OS_ANDROID)
int _unused;
#elif defined(XRT_OS_LINUX)
//! Socket that we accept connections on.
int listen_socket;
//! For waiting on various events in the main thread.
int epoll_fd;
//! Were we launched by socket activation, instead of explicitly?
bool launched_by_socket;
//! The socket filename we bound to, if any.
char *socket_filename;
#else
#error "Need port"
#endif
};
/*!
* De-initialize the mainloop object.
* @public @memberof ipc_server_mainloop
*/
void
ipc_server_mainloop_deinit(struct ipc_server_mainloop *ml);
/*!
* Initialize the mainloop object.
*
* @return <0 on error.
* @public @memberof ipc_server_mainloop
*/
int
ipc_server_mainloop_init(struct ipc_server_mainloop *ml);
/*!
* @brief Poll the mainloop.
*
* Any errors are signalled by calling ipc_server_handle_failure()
* @public @memberof ipc_server_mainloop
*/
void
ipc_server_mainloop_poll(struct ipc_server *vs, struct ipc_server_mainloop *ml);
/*!
* Main IPC object for the server.
*
@ -172,11 +228,7 @@ struct ipc_server
struct ipc_shared_memory *ism;
xrt_shmem_handle_t ism_handle;
//! Socket that we accept connections on.
int listen_socket;
//! For waiting on various events in the main thread.
int epoll_fd;
struct ipc_server_mainloop ml;
// Is the mainloop supposed to run.
volatile bool running;
@ -184,12 +236,6 @@ struct ipc_server
// Should we exit when a client disconnects.
bool exit_on_disconnect;
//! Were we launched by socket activation, instead of explicitly?
bool launched_by_socket;
//! The socket filename we bound to, if any.
char *socket_filename;
enum u_logging_level ll;
struct ipc_thread threads[IPC_MAX_CLIENTS];
@ -201,6 +247,8 @@ struct ipc_server
struct os_mutex global_state_lock;
};
#ifndef XRT_OS_ANDROID
/*!
* Main entrypoint to the compositor process.
*
@ -208,6 +256,7 @@ struct ipc_server
*/
int
ipc_server_main(int argc, char **argv);
#endif
/*!
* Android entry point to the IPC server process.
@ -235,6 +284,35 @@ update_server_state(struct ipc_server *vs);
void *
ipc_server_client_thread(void *_cs);
/*!
* @defgroup ipc_server_internals Server Internals
* @brief These are only called by the platform-specific mainloop polling code.
* @ingroup ipc_server
* @{
*/
/*!
* Start a thread for a client connected at the other end of the file descriptor @p fd.
* @memberof ipc_server
*/
void
ipc_server_start_client_listener_thread(struct ipc_server *vs, int fd);
/*!
* Perform whatever needs to be done when the mainloop polling encounters a failure.
* @memberof ipc_server
*/
void
ipc_server_handle_failure(struct ipc_server *vs);
/*!
* Perform whatever needs to be done when the mainloop polling identifies that the server should be shut down.
*
* Does something like setting a flag or otherwise signalling for shutdown: does not itself explicitly exit.
* @memberof ipc_server
*/
void
ipc_server_handle_shutdown_signal(struct ipc_server *vs);
//! @}
/*
*

View file

@ -0,0 +1,261 @@
// Copyright 2020-2021, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Server mainloop details on Linux.
* @author Pete Black <pblack@collabora.com>
* @author Jakob Bornecrantz <jakob@collabora.com>
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup ipc_server
*/
#include "xrt/xrt_device.h"
#include "xrt/xrt_instance.h"
#include "xrt/xrt_compositor.h"
#include "xrt/xrt_config_have.h"
#include "xrt/xrt_config_os.h"
#include "os/os_time.h"
#include "util/u_var.h"
#include "util/u_misc.h"
#include "util/u_debug.h"
#include "shared/ipc_shmem.h"
#include "server/ipc_server.h"
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#ifdef XRT_HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
/*
*
* Static functions.
*
*/
static int
get_systemd_socket(struct ipc_server_mainloop *ml, int *out_fd)
{
#ifdef XRT_HAVE_SYSTEMD
// We may have been launched with socket activation
int num_fds = sd_listen_fds(0);
if (num_fds > 1) {
U_LOG_E("Too many file descriptors passed by systemd.");
return -1;
}
if (num_fds == 1) {
*out_fd = SD_LISTEN_FDS_START + 0;
ml->launched_by_socket = true;
U_LOG_D("Got existing socket from systemd.");
}
#endif
return 0;
}
static int
create_listen_socket(struct ipc_server_mainloop *ml, int *out_fd)
{
// no fd provided
struct sockaddr_un addr;
int fd, ret;
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
U_LOG_E("Message Socket Create Error!");
return fd;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, IPC_MSG_SOCK_FILE);
ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
U_LOG_E(
"Could not bind socket to path %s: is the "
"service running already?",
IPC_MSG_SOCK_FILE);
#ifdef XRT_HAVE_SYSTEMD
U_LOG_E(
"Or, is the systemd unit monado.socket or "
"monado-dev.socket active?");
#endif
close(fd);
return ret;
}
// Save for later
ml->socket_filename = strdup(IPC_MSG_SOCK_FILE);
ret = listen(fd, IPC_MAX_CLIENTS);
if (ret < 0) {
close(fd);
return ret;
}
U_LOG_D("Created listening socket.");
*out_fd = fd;
return 0;
}
static int
init_listen_socket(struct ipc_server_mainloop *ml)
{
int fd = -1, ret;
ml->listen_socket = -1;
ret = get_systemd_socket(ml, &fd);
if (ret < 0) {
return ret;
}
if (fd == -1) {
ret = create_listen_socket(ml, &fd);
if (ret < 0) {
return ret;
}
}
// All ok!
ml->listen_socket = fd;
U_LOG_D("Listening socket is fd %d", ml->listen_socket);
return fd;
}
static int
init_epoll(struct ipc_server_mainloop *ml)
{
int ret = epoll_create1(EPOLL_CLOEXEC);
if (ret < 0) {
return ret;
}
ml->epoll_fd = ret;
struct epoll_event ev = {0};
if (!ml->launched_by_socket) {
// Can't do this when launched by systemd socket activation by
// default.
// This polls stdin.
ev.events = EPOLLIN;
ev.data.fd = 0; // stdin
ret = epoll_ctl(ml->epoll_fd, EPOLL_CTL_ADD, 0, &ev);
if (ret < 0) {
U_LOG_E("epoll_ctl(stdin) failed '%i'", ret);
return ret;
}
}
ev.events = EPOLLIN;
ev.data.fd = ml->listen_socket;
ret = epoll_ctl(ml->epoll_fd, EPOLL_CTL_ADD, ml->listen_socket, &ev);
if (ret < 0) {
U_LOG_E("epoll_ctl(listen_socket) failed '%i'", ret);
return ret;
}
return 0;
}
static void
handle_listen(struct ipc_server *vs, struct ipc_server_mainloop *ml)
{
int ret = accept(ml->listen_socket, NULL, NULL);
if (ret < 0) {
U_LOG_E("accept '%i'", ret);
ipc_server_handle_failure(vs);
return;
}
ipc_server_start_client_listener_thread(vs, ret);
}
#define NUM_POLL_EVENTS 8
#define NO_SLEEP 0
/*
*
* Exported functions
*
*/
void
ipc_server_mainloop_poll(struct ipc_server *vs, struct ipc_server_mainloop *ml)
{
int epoll_fd = ml->epoll_fd;
struct epoll_event events[NUM_POLL_EVENTS] = {0};
// No sleeping, returns immediately.
int ret = epoll_wait(epoll_fd, events, NUM_POLL_EVENTS, NO_SLEEP);
if (ret < 0) {
U_LOG_E("epoll_wait failed with '%i'.", ret);
ipc_server_handle_failure(vs);
return;
}
for (int i = 0; i < ret; i++) {
// If we get data on stdin, stop.
if (events[i].data.fd == 0) {
ipc_server_handle_shutdown_signal(vs);
return;
}
// Somebody new at the door.
if (events[i].data.fd == ml->listen_socket) {
handle_listen(vs, ml);
}
}
}
int
ipc_server_mainloop_init(struct ipc_server_mainloop *ml)
{
int ret = init_listen_socket(ml);
if (ret < 0) {
ipc_server_mainloop_deinit(ml);
return ret;
}
ret = init_epoll(ml);
if (ret < 0) {
ipc_server_mainloop_deinit(ml);
return ret;
}
return 0;
}
void
ipc_server_mainloop_deinit(struct ipc_server_mainloop *ml)
{
if (ml == NULL) {
return;
}
if (ml->listen_socket > 0) {
// Close socket on exit
close(ml->listen_socket);
ml->listen_socket = -1;
if (!ml->launched_by_socket && ml->socket_filename) {
// Unlink it too, but only if we bound it.
unlink(ml->socket_filename);
free(ml->socket_filename);
ml->socket_filename = NULL;
}
}
//! @todo close epoll_fd?
}

View file

@ -1,10 +1,11 @@
// Copyright 2020, Collabora, Ltd.
// Copyright 2020-2021, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Server process functions.
* @author Pete Black <pblack@collabora.com>
* @author Jakob Bornecrantz <jakob@collabora.com>
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup ipc_server
*/
@ -31,17 +32,12 @@
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#ifdef XRT_HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
/* ---- HACK ---- */
extern int
oxr_sdl2_hack_create(void **out_hack);
@ -93,6 +89,24 @@ teardown_idev(struct ipc_device *idev)
*
*/
#ifdef XRT_OS_ANDROID
// Stub
void
ipc_server_mainloop_deinit(struct ipc_server_mainloop *ml)
{}
// Stub
int
ipc_server_mainloop_init(struct ipc_server_mainloop *ml)
{
return 0;
}
// Stub
void
ipc_server_mainloop_poll(struct ipc_server *vs, struct ipc_server_mainloop *ml)
{}
#endif
static void
teardown_all(struct ipc_server *s)
{
@ -108,17 +122,7 @@ teardown_all(struct ipc_server *s)
xrt_instance_destroy(&s->xinst);
if (s->listen_socket > 0) {
// Close socket on exit
close(s->listen_socket);
s->listen_socket = -1;
if (!s->launched_by_socket && s->socket_filename) {
// Unlink it too, but only if we bound it.
unlink(s->socket_filename);
free(s->socket_filename);
s->socket_filename = NULL;
}
}
ipc_server_mainloop_deinit(&s->ml);
os_mutex_destroy(&s->global_state_lock);
}
@ -328,131 +332,21 @@ init_shm(struct ipc_server *s)
return 0;
}
static int
get_systemd_socket(struct ipc_server *s, int *out_fd)
void
ipc_server_handle_failure(struct ipc_server *vs)
{
#ifdef XRT_HAVE_SYSTEMD
// We may have been launched with socket activation
int num_fds = sd_listen_fds(0);
if (num_fds > 1) {
U_LOG_E("Too many file descriptors passed by systemd.");
return -1;
}
if (num_fds == 1) {
*out_fd = SD_LISTEN_FDS_START + 0;
s->launched_by_socket = true;
U_LOG_D("Got existing socket from systemd.");
}
#endif
return 0;
// Right now handled just the same as a graceful shutdown.
vs->running = false;
}
static int
create_listen_socket(struct ipc_server *s, int *out_fd)
void
ipc_server_handle_shutdown_signal(struct ipc_server *vs)
{
// no fd provided
struct sockaddr_un addr;
int fd, ret;
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
U_LOG_E("Message Socket Create Error!");
return fd;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, IPC_MSG_SOCK_FILE);
ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
U_LOG_E(
"Could not bind socket to path %s: is the "
"service running already?",
IPC_MSG_SOCK_FILE);
#ifdef XRT_HAVE_SYSTEMD
U_LOG_E(
"Or, is the systemd unit monado.socket or "
"monado-dev.socket active?");
#endif
close(fd);
return ret;
}
// Save for later
s->socket_filename = strdup(IPC_MSG_SOCK_FILE);
ret = listen(fd, IPC_MAX_CLIENTS);
if (ret < 0) {
close(fd);
return ret;
}
U_LOG_D("Created listening socket.");
*out_fd = fd;
return 0;
vs->running = false;
}
static int
init_listen_socket(struct ipc_server *s)
{
int fd = -1, ret;
s->listen_socket = -1;
ret = get_systemd_socket(s, &fd);
if (ret < 0) {
return ret;
}
if (fd == -1) {
ret = create_listen_socket(s, &fd);
if (ret < 0) {
return ret;
}
}
// All ok!
s->listen_socket = fd;
U_LOG_D("Listening socket is fd '%d'.", s->listen_socket);
return fd;
}
static int
init_epoll(struct ipc_server *s)
{
int ret = epoll_create1(EPOLL_CLOEXEC);
if (ret < 0) {
return ret;
}
s->epoll_fd = ret;
struct epoll_event ev = {0};
if (!s->launched_by_socket) {
// Can't do this when launched by systemd socket activation by
// default
ev.events = EPOLLIN;
ev.data.fd = 0; // stdin
ret = epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, 0, &ev);
if (ret < 0) {
U_LOG_E("Error epoll_ctl(stdin) failed '%i'!", ret);
return ret;
}
}
ev.events = EPOLLIN;
ev.data.fd = s->listen_socket;
ret = epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->listen_socket, &ev);
if (ret < 0) {
U_LOG_E("Error epoll_ctl(listen_socket) failed '%i'!", ret);
return ret;
}
return 0;
}
static void
start_client_listener_thread(struct ipc_server *vs, int fd)
void
ipc_server_start_client_listener_thread(struct ipc_server *vs, int fd)
{
volatile struct ipc_client_state *ics = NULL;
int32_t cs_index = -1;
@ -573,20 +467,12 @@ init_all(struct ipc_server *s)
return ret;
}
#ifndef XRT_OS_ANDROID
ret = init_listen_socket(s);
ret = ipc_server_mainloop_init(&s->ml);
if (ret < 0) {
teardown_all(s);
return ret;
}
ret = init_epoll(s);
if (ret < 0) {
teardown_all(s);
return ret;
}
#endif
// Init all of the render timing helpers.
for (size_t i = 0; i < ARRAY_SIZE(s->threads); i++) {
u_rt_helper_init((struct u_rt_helper *)&s->threads[i].ics.urth);
@ -608,49 +494,6 @@ init_all(struct ipc_server *s)
return 0;
}
static void
handle_listen(struct ipc_server *vs)
{
int ret = accept(vs->listen_socket, NULL, NULL);
if (ret < 0) {
U_LOG_E("Error accept failed: '%i'!", ret);
vs->running = false;
}
start_client_listener_thread(vs, ret);
}
#define NUM_POLL_EVENTS 8
#define NO_SLEEP 0
static void
check_epoll(struct ipc_server *vs)
{
int epoll_fd = vs->epoll_fd;
struct epoll_event events[NUM_POLL_EVENTS] = {0};
// No sleeping, returns immediately.
int ret = epoll_wait(epoll_fd, events, NUM_POLL_EVENTS, NO_SLEEP);
if (ret < 0) {
U_LOG_E("Error epoll_wait failed: '%i'.", ret);
vs->running = false;
return;
}
for (int i = 0; i < ret; i++) {
// If we get data on stdin, stop.
if (events[i].data.fd == 0) {
vs->running = false;
return;
}
// Somebody new at the door.
if (events[i].data.fd == vs->listen_socket) {
handle_listen(vs);
}
}
}
static uint32_t
find_event_slot(volatile struct ipc_client_state *ics)
{
@ -1046,10 +889,8 @@ main_loop(struct ipc_server *s)
xrt_comp_layer_commit(xc, frame_id, XRT_GRAPHICS_SYNC_HANDLE_INVALID);
#ifndef XRT_OS_ANDROID
// Check polling last, so we know we have valid timing data.
check_epoll(s);
#endif
ipc_server_mainloop_poll(s, &s->ml);
}
return 0;
@ -1222,17 +1063,16 @@ update_server_state(struct ipc_server *s)
os_mutex_unlock(&s->global_state_lock);
}
#ifndef XRT_OS_ANDROID
int
ipc_server_main(int argc, char **argv)
{
struct ipc_server *s = U_TYPED_CALLOC(struct ipc_server);
#ifndef XRT_OS_ANDROID
/* ---- HACK ---- */
// need to create early before any vars are added
oxr_sdl2_hack_create(&s->hack);
/* ---- HACK ---- */
#endif
int ret = init_all(s);
if (ret < 0) {
@ -1242,19 +1082,15 @@ ipc_server_main(int argc, char **argv)
init_server_state(s);
#ifndef XRT_OS_ANDROID
/* ---- HACK ---- */
oxr_sdl2_hack_start(s->hack, s->xinst);
/* ---- HACK ---- */
#endif
ret = main_loop(s);
#ifndef XRT_OS_ANDROID
/* ---- HACK ---- */
oxr_sdl2_hack_stop(&s->hack);
/* ---- HACK ---- */
#endif
teardown_all(s);
free(s);
@ -1264,6 +1100,8 @@ ipc_server_main(int argc, char **argv)
return ret;
}
#endif // !XRT_OS_ANDROID
#ifdef XRT_OS_ANDROID
int
ipc_server_main_android(int fd)
@ -1278,7 +1116,7 @@ ipc_server_main_android(int fd)
}
init_server_state(s);
start_client_listener_thread(s, fd);
ipc_server_start_client_listener_thread(s, fd);
ret = main_loop(s);
teardown_all(s);