[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.
This commit is contained in:
2026-09-27 07:43:56 -04:00
parent e3e890ed5d
commit c16fb5a8ac
3 changed files with 123 additions and 19 deletions
+8 -6
View File
@@ -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
+114 -4
View File
@@ -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<SocketTransport> 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<int>(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<int>(::getpid()), code,
code == kSessionLatchedExitCode ? " (latched fault)" : "", m_sessionsFaulted);
return true;
}
void StartSessionThread(std::unique_ptr<SocketTransport> 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<int>(::getpid()));
}
int StopServing() {
while (m_active > 0) {
if (!Reap()) ::usleep(10000);
}
WireLogError("MG_Remote server: pid=%d in-process display server stopped", static_cast<int>(::getpid()));
return 0;
}
int m_listener;
unsigned long& m_sessionsFaulted;
std::thread m_sessionThread;
std::atomic<bool> m_sessionDone{false};
std::atomic<int> 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;
@@ -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://")) {