The open source OpenXR runtime
at mr/scanout-values 415 lines 11 kB view raw
1// Copyright 2020-2023, Collabora, Ltd. 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief Per client thread listening on the socket. 6 * @author Pete Black <pblack@collabora.com> 7 * @author Jakob Bornecrantz <jakob@collabora.com> 8 * @ingroup ipc_server 9 */ 10 11#include "util/u_misc.h" 12#include "util/u_trace_marker.h" 13 14#include "shared/ipc_protocol.h" 15#include "shared/ipc_shmem.h" 16#include "shared/ipc_utils.h" 17#include "server/ipc_server.h" 18#include "ipc_server_generated.h" 19 20#ifndef XRT_OS_WINDOWS 21 22#include <unistd.h> 23#include <fcntl.h> 24#include <errno.h> 25#include <stdio.h> 26#include <sys/stat.h> 27#include <sys/types.h> 28#include <sys/epoll.h> 29#include <sys/socket.h> 30 31#endif // XRT_OS_WINDOWS 32 33 34/* 35 * 36 * Helper functions. 37 * 38 */ 39 40static void * 41delayed_exit_thread(void *_server) 42{ 43 struct ipc_server *s = (struct ipc_server *)_server; 44 45 // Sleep for the delay duration 46 os_nanosleep(s->exit_when_idle_delay_ns); 47 48 // Check if we still have no clients after the delay 49 os_mutex_lock(&s->global_state.lock); 50 bool should_exit = 51 (s->exit_when_idle && s->global_state.connected_client_count == 0 && s->last_client_disconnect_ns > 0); 52 os_mutex_unlock(&s->global_state.lock); 53 54 if (should_exit) { 55 IPC_INFO(s, "All clients disconnected for %u milliseconds, shutting down.", 56 (unsigned int)(s->exit_when_idle_delay_ns / U_TIME_1MS_IN_NS)); 57 s->running = false; 58 } 59 60 return NULL; 61} 62 63static void 64common_shutdown(volatile struct ipc_client_state *ics) 65{ 66 /* 67 * Remove the thread from the server. 68 */ 69 70 // Multiple threads might be looking at these fields. 71 os_mutex_lock(&ics->server->global_state.lock); 72 73 ipc_message_channel_close((struct ipc_message_channel *)&ics->imc); 74 75 ipc_shmem_destroy((xrt_shmem_handle_t *)&ics->ism_handle, (void **)&ics->server->isms[ics->server_thread_index], 76 sizeof(struct ipc_shared_memory)); 77 78 ics->server->threads[ics->server_thread_index].state = IPC_THREAD_STOPPING; 79 ics->server_thread_index = -1; 80 memset((void *)&ics->client_state, 0, sizeof(struct ipc_app_state)); 81 82 // Decrement the connected client counter 83 ics->server->global_state.connected_client_count--; 84 85 os_mutex_unlock(&ics->server->global_state.lock); 86 87 88 /* 89 * Clean up various resources. 90 */ 91 92 // If the session hasn't been stopped, destroy the compositor. 93 ipc_server_client_destroy_session_and_compositor(ics); 94 95 // Make sure undestroyed spaces are unreferenced 96 for (uint32_t i = 0; i < IPC_MAX_CLIENT_SPACES; i++) { 97 // Cast away volatile. 98 xrt_space_reference((struct xrt_space **)&ics->xspcs[i], NULL); 99 } 100 101 if (ics->local_space_overseer_index < IPC_MAX_CLIENT_SPACES) { 102 xrt_space_reference((struct xrt_space **)&ics->server->xso->localspace[ics->local_space_overseer_index], 103 NULL); 104 } 105 106 if (ics->local_floor_space_overseer_index < IPC_MAX_CLIENT_SPACES && // 107 ics->local_floor_space_overseer_index > 0) { 108 xrt_space_reference( 109 (struct xrt_space **)&ics->server->xso->localfloorspace[ics->local_floor_space_overseer_index], 110 NULL); 111 } 112 113 // Mark an still in use reference spaces as no longer used. 114 for (uint32_t i = 0; i < ARRAY_SIZE(ics->ref_space_used); i++) { 115 bool used = ics->ref_space_used[i]; 116 if (!used) { 117 continue; 118 } 119 120 xrt_space_overseer_ref_space_dec(ics->server->xso, (enum xrt_reference_space_type)i); 121 ics->ref_space_used[i] = false; 122 } 123 124 // Make a still in use device features as no longer used. 125 for (uint32_t i = 0; i < ARRAY_SIZE(ics->device_feature_used); i++) { 126 bool used = ics->device_feature_used[i]; 127 if (!used) { 128 continue; 129 } 130 131 xrt_system_devices_feature_dec(ics->server->xsysd, (enum xrt_device_feature_type)i); 132 ics->device_feature_used[i] = false; 133 } 134 135 // Make sure undestroyed plane detections are cleaned up 136 for (uint32_t i = 0; i < ics->plane_detection_count; i++) { 137 xrt_device_destroy_plane_detection_ext(ics->plane_detection_xdev[i], ics->plane_detection_ids[i]); 138 } 139 free(ics->plane_detection_ids); 140 free(ics->plane_detection_xdev); 141 ics->plane_detection_ids = NULL; 142 ics->plane_detection_xdev = NULL; 143 ics->plane_detection_size = 0; 144 ics->plane_detection_count = 0; 145 146 // Should we stop the server when a client disconnects? 147 if (ics->server->exit_on_disconnect) { 148 ics->server->running = false; 149 } 150 // Should we stop when all clients disconnect? 151 if (ics->server->exit_when_idle && ics->server->global_state.connected_client_count == 0) { 152 ics->server->last_client_disconnect_ns = os_monotonic_get_ns(); 153 154 struct os_thread thread; 155 os_thread_start(&thread, delayed_exit_thread, (void *)ics->server); 156 // We intentionally don't join this thread - it's fire and forget 157 } 158 159 160 ipc_server_deactivate_session(ics); 161} 162 163 164/* 165 * 166 * Client loop and per platform helpers. 167 * 168 */ 169 170#ifndef XRT_OS_WINDOWS // Linux & Android 171 172static int 173setup_epoll(volatile struct ipc_client_state *ics) 174{ 175 int listen_socket = ics->imc.ipc_handle; 176 assert(listen_socket >= 0); 177 178 int ret = epoll_create1(EPOLL_CLOEXEC); 179 if (ret < 0) { 180 return ret; 181 } 182 183 int epoll_fd = ret; 184 185 struct epoll_event ev = XRT_STRUCT_INIT; 186 187 ev.events = EPOLLIN; 188 ev.data.fd = listen_socket; 189 ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_socket, &ev); 190 if (ret < 0) { 191 IPC_ERROR(ics->server, "Error epoll_ctl(listen_socket) failed '%i'.", ret); 192 return ret; 193 } 194 195 return epoll_fd; 196} 197 198static void 199client_loop(volatile struct ipc_client_state *ics) 200{ 201 U_TRACE_SET_THREAD_NAME("IPC Client"); 202 203 IPC_INFO(ics->server, "Client %u connected", ics->client_state.id); 204 205 // Claim the client fd. 206 int epoll_fd = setup_epoll(ics); 207 if (epoll_fd < 0) { 208 return; 209 } 210 211 while (ics->server->running) { 212 const int half_a_second_ms = 500; 213 struct epoll_event event = XRT_STRUCT_INIT; 214 int ret = 0; 215 216 // On temporary failures retry. 217 do { 218 // We use epoll here to be able to timeout. 219 ret = epoll_wait(epoll_fd, &event, 1, half_a_second_ms); 220 } while (ret == -1 && errno == EINTR); 221 222 if (ret < 0) { 223 IPC_ERROR(ics->server, "Failed epoll_wait '%i', disconnecting client.", ret); 224 break; 225 } 226 227 // Timed out, loop again. 228 if (ret == 0) { 229 continue; 230 } 231 232 // Detect clients disconnecting gracefully. 233 if (ret > 0 && (event.events & EPOLLHUP) != 0) { 234 IPC_INFO(ics->server, "Client disconnected."); 235 break; 236 } 237 238 // Peek the first 4 bytes to get the command type 239 enum ipc_command cmd; 240 ssize_t len = recv(ics->imc.ipc_handle, &cmd, sizeof(cmd), MSG_PEEK); 241 if (len != sizeof(cmd)) { 242 IPC_ERROR(ics->server, "Invalid command received."); 243 break; 244 } 245 246 size_t cmd_size = ipc_command_size(cmd); 247 if (cmd_size == 0) { 248 IPC_ERROR(ics->server, "Invalid command size."); 249 break; 250 } 251 252 // Read the whole command now that we know its size 253 uint8_t buf[IPC_BUF_SIZE] = {0}; 254 255 len = recv(ics->imc.ipc_handle, &buf, cmd_size, 0); 256 if (len != (ssize_t)cmd_size) { 257 IPC_ERROR(ics->server, "Invalid packet received, disconnecting client."); 258 break; 259 } 260 261 // Check the first 4 bytes of the message and dispatch. 262 ipc_command_t *ipc_command = (ipc_command_t *)buf; 263 264 IPC_TRACE_BEGIN(ipc_dispatch); 265 xrt_result_t result = ipc_dispatch(ics, ipc_command); 266 IPC_TRACE_END(ipc_dispatch); 267 268 if (result != XRT_SUCCESS) { 269 IPC_ERROR(ics->server, "During packet handling, disconnecting client."); 270 break; 271 } 272 } 273 274 close(epoll_fd); 275 epoll_fd = -1; 276 277 // Following code is same for all platforms. 278 common_shutdown(ics); 279} 280 281#else // XRT_OS_WINDOWS 282 283static void 284pipe_print_get_last_error(volatile struct ipc_client_state *ics, const char *func) 285{ 286 // This is the error path. 287 DWORD err = GetLastError(); 288 if (err == ERROR_BROKEN_PIPE) { 289 IPC_INFO(ics->server, "%s: %d %s", func, err, ipc_winerror(err)); 290 } else { 291 IPC_ERROR(ics->server, "%s failed: %d %s", func, err, ipc_winerror(err)); 292 } 293} 294 295static void 296client_loop(volatile struct ipc_client_state *ics) 297{ 298 U_TRACE_SET_THREAD_NAME("IPC Client"); 299 300 IPC_INFO(ics->server, "Client connected"); 301 302 while (ics->server->running) { 303 uint8_t buf[IPC_BUF_SIZE] = {0}; 304 DWORD len = 0; 305 BOOL bret = false; 306 307 /* 308 * The pipe is created in message mode, the client IPC code will 309 * always send the *_msg structs as one message, and any extra 310 * variable length data as a different message. So even if the 311 * command is a variable length the first message will be sized 312 * to the command size, this is what we get here, variable 313 * length data is read in the dispatch function for the command. 314 */ 315 bret = ReadFile(ics->imc.ipc_handle, buf, sizeof(buf), &len, NULL); 316 if (!bret) { 317 pipe_print_get_last_error(ics, "ReadFile"); 318 IPC_ERROR(ics->server, "ReadFile failed, disconnecting client."); 319 break; 320 } 321 322 // All commands are at least 4 bytes. 323 if (len < 4) { 324 IPC_ERROR(ics->server, "Not enough bytes received '%u', disconnecting client.", (uint32_t)len); 325 break; 326 } 327 328 // Now safe to cast into a command pointer, used for dispatch. 329 ipc_command_t *cmd_ptr = (ipc_command_t *)buf; 330 331 // Read the command, we know we have at least 4 bytes. 332 ipc_command_t cmd = *cmd_ptr; 333 334 // Get the command length. 335 size_t cmd_size = ipc_command_size(cmd); 336 if (cmd_size == 0) { 337 IPC_ERROR(ics->server, "Invalid command '%u', disconnecting client.", cmd); 338 break; 339 } 340 341 // Check if the read message has the expected length. 342 if (len != cmd_size) { 343 IPC_ERROR(ics->server, "Invalid packet received, disconnecting client."); 344 break; 345 } 346 347 IPC_TRACE_BEGIN(ipc_dispatch); 348 xrt_result_t result = ipc_dispatch(ics, cmd_ptr); 349 IPC_TRACE_END(ipc_dispatch); 350 351 if (result != XRT_SUCCESS) { 352 IPC_ERROR(ics->server, "During packet handling, disconnecting client."); 353 break; 354 } 355 } 356 357 // Following code is same for all platforms. 358 common_shutdown(ics); 359} 360 361#endif // XRT_OS_WINDOWS 362 363 364/* 365 * 366 * 'Exported' functions. 367 * 368 */ 369 370void 371ipc_server_client_destroy_session_and_compositor(volatile struct ipc_client_state *ics) 372{ 373 // Multiple threads might be looking at these fields. 374 os_mutex_lock(&ics->server->global_state.lock); 375 376 ics->swapchain_count = 0; 377 378 // Destroy all swapchains now. 379 for (uint32_t j = 0; j < IPC_MAX_CLIENT_SWAPCHAINS; j++) { 380 // Drop our reference, does NULL checking. Cast away volatile. 381 xrt_swapchain_reference((struct xrt_swapchain **)&ics->xscs[j], NULL); 382 ics->swapchain_data[j].active = false; 383 IPC_TRACE(ics->server, "Destroyed swapchain %d.", j); 384 } 385 386 for (uint32_t j = 0; j < IPC_MAX_CLIENT_SEMAPHORES; j++) { 387 // Drop our reference, does NULL checking. Cast away volatile. 388 xrt_compositor_semaphore_reference((struct xrt_compositor_semaphore **)&ics->xcsems[j], NULL); 389 IPC_TRACE(ics->server, "Destroyed compositor semaphore %d.", j); 390 } 391 392 for (uint32_t j = 0; j < IPC_MAX_CLIENT_FUTURES; j++) { 393 // Drop our reference, does NULL checking. Cast away volatile. 394 xrt_future_reference((struct xrt_future **)&ics->xfts[j], NULL); 395 IPC_TRACE(ics->server, "Destroyed future %d.", j); 396 } 397 398 os_mutex_unlock(&ics->server->global_state.lock); 399 400 // Cast away volatile. 401 xrt_comp_destroy((struct xrt_compositor **)&ics->xc); 402 403 // Cast away volatile. 404 xrt_session_destroy((struct xrt_session **)&ics->xs); 405} 406 407void * 408ipc_server_client_thread(void *_ics) 409{ 410 volatile struct ipc_client_state *ics = (volatile struct ipc_client_state *)_ics; 411 412 client_loop(ics); 413 414 return NULL; 415}