mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
The cross-process session is up. A server process is started on its own with an
endpoint and nothing else, it listens; a client started separately connects to
that name; Hello crosses, Welcome comes back with four SegmentRefs, six
descriptors cross by SCM_RIGHTS, and the client maps memory another process
created. ClientSession::Active() is non-null at the end of it, which is the flag
that arms the thirty-seven wire emitters.
INDEPENDENT, NOT FORKED - and that is a correction to this package's first
shape. The version committed an hour ago forked the server and handed it fds
3..6. It worked, and it was less code, but inheritance can only ever produce a
server that is a CHILD OF ITS CLIENT, which is the one arrangement the end state
cannot use: there the server is a standing application and the client is
somewhere else entirely, possibly on another kernel. CONTRACT-P6 §3.1 called
this Dial == Connect and filed it as a later shape. It is the primary one, and
fork/execve survives only as a way to START a process - every descriptor it used
to carry is gone.
What that costs and buys, concretely: the rendezvous becomes a NAME, so
SocketTransport grows Listen/AcceptPair/ConnectTo, with a bounded connect retry
that is the CLIENT's (a launcher that waited for the server to be ready would be
coupling the two again by the back door). The bells stop being inherited fds and
are created by the server and handed over with the segments - slots 4 and 5 of
six SCM_RIGHTS offers. And the client connects TWICE to the same path: an fd
offer is a sendmsg whose ancillary data rides with specific bytes, so it may not
share a socket with the framed control stream, or the frame reassembler and the
descriptor receiver race for the same bytes.
The pieces behind it:
- SessionSegments::AdoptFromDescriptors, which is the same Adopt+Map loop
AttachInProcess has run on every inproc session since P5. That was the point
of writing the inproc attach as dup+Adopt+Map rather than as an alias: only
the origin of the descriptor is new.
- ServerSession::SetExternalDoorbells, the narrow fix for a6 row A7-1 - the
§3.9 accessors abort for every role but InProcess, so a spawn apply thread
died before popping its first record. lk deletes them; until then an injected
pair is honest about where the bells come from.
- MG_Backend::InitServerRoleForSpawn: steps 1 and 2 of the split bring-up
extracted so the server process runs the SAME code the inproc server role
runs. The capability bits are a statement about the server's own backend and
a second copy would diverge the first time a backend registers a slot.
- ClientSession::FinishStartup: everything below the attach - producer, reply
pool, event ring, segment table, encoder, first CapsSnapshot, emitter tables -
shared verbatim between inproc and spawn. Its one parameter is not a
transport question: under inproc the client starts the apply thread because
there is no server-side main; under spawn there is one, and it started the
thread before it sent the first descriptor.
G1 CAUGHT A REAL LEAK on the way: InitServerRoleForSpawn was guarded on the
inside, so the pull build still DEFINED the symbol - 2 added, 1 removed, .text
+16 bytes. The whole function and its declaration are now inside the guard.
"It returns false there" is not the same as "it is not there", and the gate is
what said so.
Gates: ServerSpawnTest 4/4; unit suite 2344 with the same two pre-existing
environmental failures; G1 pull 0 added / 0 removed, .text 10822147 unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
4.2 KiB
C++
88 lines
4.2 KiB
C++
// MobileGL - MobileGL/MG_Remote/Server/ServerSpawn.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
// LAUNCHING a server process, and the process discipline around it.
|
|
// Package `sm` (CONTRACT-P6.md §3).
|
|
//
|
|
// IT LAUNCHES; IT DOES NOT COUPLE. The two processes are independent: this
|
|
// hands the child an endpoint NAME and nothing else - no inherited descriptors,
|
|
// no shared memory, no handshake. The child listens on that name and the client
|
|
// connects to it, exactly as it would to a server someone started by hand or
|
|
// that an Android Service started minutes earlier.
|
|
//
|
|
// An earlier shape of this file forked the server and handed it fds 3..6.
|
|
// Inheritance works and is less code, but it can only ever produce a server
|
|
// that is a CHILD OF ITS CLIENT - which the end state cannot use, because there
|
|
// the server is a standing application and the client is elsewhere, possibly on
|
|
// another kernel. So fork/execve survives here only as A WAY TO START A
|
|
// PROCESS, which is all it ever needed to be, and every fd it used to carry is
|
|
// gone.
|
|
//
|
|
// ANTI-RECURSION IS STILL TWO INDEPENDENT CATCHES, and CONTRACT-P6 §3.1 is why
|
|
// neither is ARCHITECTURE.md:488's. That rule says the child hard-sets
|
|
// Transport to Monolith; a6 measured that 226 live lines test
|
|
// `Transport != Monolith` to select the SERVER arm, 148 of them in MG_Backend,
|
|
// so obeying it flips the whole backend to frontend glue in the one process
|
|
// with no frontend. Instead:
|
|
// (a) STRUCTURAL: MOBILEGL_IPC_DIAL=no, a separate axis from which role the
|
|
// process plays;
|
|
// (b) ENVIRONMENTAL: MOBILEGL_TRANSPORT and every MOBILEGL_IPC_* removed
|
|
// from the child's envp.
|
|
// The child refuses with exit 64 for (a) and 65 for (b), so the two safeties
|
|
// are distinguishable - two that cannot be told apart are one.
|
|
|
|
#pragma once
|
|
|
|
#include <Includes.h>
|
|
|
|
#include "../Protocol/mg_protocol_base.h"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
namespace MobileGL::MG_Remote::Server {
|
|
|
|
struct LaunchedServer {
|
|
// -1 when nothing was spawned. Non-negative means this process owes the
|
|
// child a wait(): a SpawnedServer that goes out of scope without being
|
|
// reaped leaves a zombie, which is exactly what the process-tree gate
|
|
// (CONTRACT-P6 §9.4) counts.
|
|
// -1 when nothing was launched. Non-negative means this process owes the
|
|
// child a wait(): a LaunchedServer that goes out of scope unreaped leaves
|
|
// a zombie, which is what the process-tree gate (§9.4) counts.
|
|
int pid = -1;
|
|
// The endpoint the child was told to listen on. The launcher's only
|
|
// output besides the pid: everything else the client needs, it gets by
|
|
// connecting.
|
|
std::string endpoint;
|
|
};
|
|
|
|
// Locates the server image and starts it on `endpoint`. Returns as soon as
|
|
// the process exists; the client's bounded connect retry is what waits for
|
|
// it to finish binding (SocketTransport::ConnectTo).
|
|
//
|
|
// `imagePath` empty means "resolve it": MOBILEGL_IPC_SERVER_PATH first, then
|
|
// dladdr on our own library to find libMobileGLServer.so beside it. An
|
|
// unresolvable path is a NAMED REFUSAL and never a monolith fallback -
|
|
// ConfigLoader.cpp names that accident and this is the code that must not
|
|
// repeat it.
|
|
MobileGLResult LaunchServer(const std::string& imagePath, const std::string& endpoint,
|
|
LaunchedServer* out);
|
|
|
|
// Waits for the child, up to `timeoutMs`. Returns MOBILEGL_ERR_TIMEOUT if it
|
|
// is still running, in which case the caller decides whether to escalate -
|
|
// this function never signals a child it did not have to.
|
|
MobileGLResult ReapServer(LaunchedServer& server, std::uint32_t timeoutMs, int* outExitCode);
|
|
|
|
// How many children this process currently has, by scanning for our own
|
|
// pid as a parent. The process-tree gate needs a number, not a promise:
|
|
// "exactly one while running, zero after".
|
|
int CountOwnChildren();
|
|
|
|
} // namespace MobileGL::MG_Remote::Server
|