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