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