From c16fb5a8ace11b67b9f14c674003823dddf89986 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 27 Sep 2026 07:43:56 -0400 Subject: [PATCH] [Feat] (MG_Remote): the in-process display server serves unix endpoints too mobilegl_server_serve_inprocess refused anything but tcp:// because its only supervisor was the TCP one's thread hand-off. A unix endpoint (@abstract name or filesystem path) now runs a supervisor of the same shape minus the TCP exposure machinery: AcceptPair pairs a client's control and SCM_RIGHTS connections by arrival order, there is no DataBind to route and no pre-auth gate, sessions run on threads one at a time with Busy for a second client, the latch resets between sessions and g_inProcessStop ends it. That gives the on-screen server the on-device fast path: ServerOwned windows over a unix socket with shm data, no TCP copies. The control screen's tcp-only guard for the on-screen mode goes with the refusal it mirrored. --- MobileGL/MG_Remote/Server/InProcessServer.h | 14 ++- MobileGL/MG_Remote/Server/ServerMain.cpp | 118 +++++++++++++++++- .../plugin/ServerControlActivity.java | 10 +- 3 files changed, 123 insertions(+), 19 deletions(-) diff --git a/MobileGL/MG_Remote/Server/InProcessServer.h b/MobileGL/MG_Remote/Server/InProcessServer.h index a133136b..0eda5e4a 100644 --- a/MobileGL/MG_Remote/Server/InProcessServer.h +++ b/MobileGL/MG_Remote/Server/InProcessServer.h @@ -32,12 +32,14 @@ extern "C" { -// Serves `endpoint` (tcp://host:port only) on the CALLING thread until mobilegl_server_stop_inprocess -// is called and the live session (if any) has ended, then returns 0. Other returns, all logged by -// name: 64 / 65 (the environment preconditions above), 71 (not a tcp:// endpoint), 72 (could not -// listen - EADDRINUSE is retried for up to ~5 s first, the previous server may still be dying), 73 -// (the listener failed), 74 (a server is already serving in this process). Re-callable after it -// returned. +// Serves `endpoint` on the CALLING thread until mobilegl_server_stop_inprocess is called and the +// live session (if any) has ended, then returns 0. The endpoint is tcp://host:port, or - for a +// same-device client - a unix @abstract name or filesystem path, whose control+data pair and +// SCM_RIGHTS shm segments make it the fastest on-device shape. Other returns, all logged by +// name: 64 / 65 (the environment preconditions above), 71 (empty endpoint), 72 (could not +// listen - EADDRINUSE is retried for up to ~5 s first, the previous server may still be dying), +// 73 (the listener failed), 74 (a server is already serving in this process). Re-callable after +// it returned. __attribute__((visibility("default"))) int mobilegl_server_serve_inprocess(const char* endpoint); // Asks the in-process server to stop and returns at once (it does not wait: call it from a UI diff --git a/MobileGL/MG_Remote/Server/ServerMain.cpp b/MobileGL/MG_Remote/Server/ServerMain.cpp index 42d0ea8b..9e7e2c42 100644 --- a/MobileGL/MG_Remote/Server/ServerMain.cpp +++ b/MobileGL/MG_Remote/Server/ServerMain.cpp @@ -1246,6 +1246,108 @@ private: Clock::time_point m_acceptResumeAt{}; }; +// THE UNIX SHAPE OF THE IN-PROCESS DISPLAY SERVER (P12 D5 extended to unix endpoints). The +// endpoint is an @abstract name or a filesystem path. A client's control AND data connections +// pair by arrival order (AcceptPair) and the shm segments' descriptors cross by SCM_RIGHTS, so +// there is no DataBind to route and no pre-auth gate - both are TCP exposure machinery, and a +// unix peer is local by construction (the forked unix supervisor runs without either). Sessions +// take the TCP in-process shape's thread hand-off: one at a time, Busy for a second client, the +// latch reset between sessions, stop by g_inProcessStop. +class UnixInProcessSupervisor { +public: + UnixInProcessSupervisor(int listener, unsigned long& sessionsFaulted) + : m_listener(listener), m_sessionsFaulted(sessionsFaulted) {} + + ~UnixInProcessSupervisor() { + // Run() only returns with the session thread joined; a listener failure (73) is the + // one way out with it live, and it is never left running behind the supervisor. + if (m_sessionThread.joinable()) m_sessionThread.join(); + } + + int Run() { + for (;;) { + if (g_inProcessStop.load(std::memory_order_acquire)) return StopServing(); + Reap(); + std::unique_ptr control; + const auto accepted = SocketTransport::AcceptPair(m_listener, 250, control); + if (accepted == MOBILEGL_ERR_TIMEOUT) continue; + if (accepted != MOBILEGL_OK) { + WireLogError("MG_Remote server: the in-process unix listener failed (rc=%d)", + static_cast(accepted)); + return 73; + } + // exit_group's close window, as the forked loop's: a client reconnecting the moment + // its session ended must not be told Busy by a session that is exiting. + const auto reapDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(50); + while (m_active > 0 && std::chrono::steady_clock::now() < reapDeadline) { + if (Reap()) break; + ::usleep(1000); + } + if (m_active > 0) { + RefuseBusy(*control, "one session is already active"); + continue; + } + StartSessionThread(std::move(control)); + } + } + +private: + // The same reap as the TCP shape's ReapSessionThread: join, count faults, reset the latch + // for the next session in this process. + bool Reap() { + if (m_active <= 0 || !m_sessionDone.load(std::memory_order_acquire)) return false; + if (m_sessionThread.joinable()) m_sessionThread.join(); + const int code = m_sessionExit.load(std::memory_order_acquire); + m_active = -1; + if (code != 0) ++m_sessionsFaulted; + ResetSessionLatch(); + WireLogError("MG_Remote server: in-process unix session #%lu pid=%d reaped exit=%d%s sessionsFaulted=%lu", + m_sessionOrdinal, static_cast(::getpid()), code, + code == kSessionLatchedExitCode ? " (latched fault)" : "", m_sessionsFaulted); + return true; + } + + void StartSessionThread(std::unique_ptr connection) { + if (m_sessionThread.joinable()) m_sessionThread.join(); // reaped already; never live here + m_sessionDone.store(false, std::memory_order_release); + m_sessionExit.store(0, std::memory_order_release); + ++m_sessionOrdinal; + try { + // No hand-off pair and no pre-read Hello: the session reads its own first frame and + // binds its shm segments from the fds the pair's second connection carries. + m_sessionThread = std::thread([this, conn = std::move(connection)]() mutable { + const int code = RunSession(std::move(conn), {}, {}, -1, DataSource::None, 10000, + /*inProcess=*/true); + m_sessionExit.store(code, std::memory_order_release); + m_sessionDone.store(true, std::memory_order_release); + }); + } catch (...) { + WireLogError("MG_Remote server: could not start the in-process unix session thread; the " + "connection is closed (it cannot be refused: it moved into the failed thread)"); + return; + } + m_active = ::getpid(); + WireLogError("MG_Remote server: in-process unix session #%lu started on a thread (pid=%d)", + m_sessionOrdinal, static_cast(::getpid())); + } + + int StopServing() { + while (m_active > 0) { + if (!Reap()) ::usleep(10000); + } + WireLogError("MG_Remote server: pid=%d in-process display server stopped", static_cast(::getpid())); + return 0; + } + + int m_listener; + unsigned long& m_sessionsFaulted; + std::thread m_sessionThread; + std::atomic m_sessionDone{false}; + std::atomic m_sessionExit{0}; + unsigned long m_sessionOrdinal = 0; + pid_t m_active = -1; +}; + // The server's environment preconditions, shared by the exec'd supervisor and the in-process // display server (P12 D5: "same env preconditions as mobilegl_server_main"). 0 when they hold. int CheckServerEnvironment() { @@ -1373,11 +1475,14 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int a // P12 (on-screen server window), D5. See InProcessServer.h for the contract. extern "C" __attribute__((visibility("default"))) int mobilegl_server_serve_inprocess(const char* endpoint) { if (const int refused = CheckServerEnvironment(); refused != 0) return refused; - if (endpoint == nullptr || std::strncmp(endpoint, "tcp://", 6) != 0) { - WireLogError("MG_Remote server: the in-process display server serves tcp:// only; '%s' is not one", - endpoint == nullptr ? "(null)" : endpoint); + if (endpoint == nullptr || endpoint[0] == '\0') { + WireLogError("MG_Remote server: the in-process display server needs an endpoint (tcp://, " + "@abstract or a filesystem path)"); return 71; } + // tcp:// takes the TCP supervisor's thread hand-off; anything else is a unix endpoint + // (@abstract name or filesystem path), taken by the unix supervisor with the same threads. + const bool tcpEndpoint = std::strncmp(endpoint, "tcp://", 6) == 0; bool idle = false; if (!g_inProcessServing.compare_exchange_strong(idle, true, std::memory_order_acq_rel)) { WireLogError("MG_Remote server: an in-process display server is already serving in this process; " @@ -1411,13 +1516,18 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_serve_inpr Server::ServerDisplayInstance().HasDisplay() ? "installed" : "NOT installed - offscreen only"); unsigned long sessionsFaulted = 0; int result = 0; - { + if (tcpEndpoint) { TcpSupervisor supervisor(listener, Server::PreAuthKnobs::FromEnvironment(), sessionsFaulted, TcpSupervisor::Handoff::Thread, &g_inProcessStop); result = supervisor.Run(); + } else { + UnixInProcessSupervisor supervisor(listener, sessionsFaulted); + result = supervisor.Run(); } LogSupervisorSummary(sessionsFaulted); ::close(listener); + // A filesystem unix endpoint leaves a node behind; an abstract name has none. + if (!tcpEndpoint && endpoint[0] != '@') ::unlink(endpoint); g_inProcessStop.store(false, std::memory_order_release); g_inProcessServing.store(false, std::memory_order_release); return result; diff --git a/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java b/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java index efe5af6b..c90b528c 100644 --- a/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java +++ b/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java @@ -98,7 +98,7 @@ public final class ServerControlActivity extends Activity { envField = addField(contentLayout, "Extra env (optional)", "", dp(8)); onScreenBox = new CheckBox(this); - onScreenBox.setText("On-screen window (render client frames onto this screen; tcp:// only)"); + onScreenBox.setText("On-screen window (render client frames onto this screen)"); onScreenBox.setTextColor(COLOR_TEXT); onScreenBox.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13); LinearLayout.LayoutParams boxParams = new LinearLayout.LayoutParams( @@ -155,14 +155,6 @@ public final class ServerControlActivity extends Activity { String token = tokenField.getText().toString(); String env = envField.getText().toString().trim(); - // The in-process display server only accepts tcp:// (ServerMain's in-process arm); - // the unix forms belong to the offscreen supervisor. - if (onScreenBox.isChecked() && !endpoint.startsWith("tcp://")) { - setStatus("The on-screen display server serves tcp:// only; use a tcp:// endpoint " - + "or uncheck On-screen window.", COLOR_FAIL); - return; - } - String probeDisplay; Probe probe; if (endpoint.startsWith("tcp://")) {