The open source OpenXR runtime
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}