mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (P11 A1): settle MOBILEGL_IPC_ADOPT_TIER at the handshake from the data plane - a stream refuses T0/T1 by name and runs T2 on either side, a client over shared segments dies by name before any record, the at-use check trusts the settlement; unit gates on a real tcp session and a real inproc handshake
This commit is contained in:
@@ -613,6 +613,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
# Keeps MG_Util/Debug/Log.h - and through it the GL frontend's
|
||||
# umbrella header - out of the header-only wire code (WireLog.h).
|
||||
MobileGL/MG_Remote/Transport/WireLog.cpp
|
||||
# P11 A1: the adopt tier settled at the handshake from the knob and the data plane, by
|
||||
# both roles (so SERVER-partition code in the link ratchet, not the client's tracker).
|
||||
MobileGL/MG_Remote/Transport/AdoptTier.cpp
|
||||
# ---- P5: the three new directories ------------------------------
|
||||
# Wire/ the G3 codec: MGPWireRec_* in and out of SEG_CMD, blobs and
|
||||
# var-tails in and out of SEG_STAGE. [w1]
|
||||
|
||||
@@ -430,8 +430,8 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// torn read rather than a divergence. The batch is therefore off whenever the
|
||||
// shadow comparer is armed.
|
||||
if (MG_Config::Features.PipeVerify) ipc.BatchWaits = 0;
|
||||
// 2 is the only tier P5 implements (R-6). 0 and 1 parse here and are refused at the
|
||||
// point of use, which is where the "P11" in the message belongs.
|
||||
// 2 is the only implemented tier (R-6). 0 and 1 parse here and are settled at the
|
||||
// handshake from the data plane (P11 A1, MG_Remote/Transport/AdoptTier.h).
|
||||
ipc.AdoptTier = QueryEnvUint32("MOBILEGL_IPC_ADOPT_TIER", 2, 0, 2);
|
||||
ipc.VerbBarrier = QueryEnvUint32("MOBILEGL_IPC_VERB_BARRIER", 1, 0, 1);
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1). The wait rule's A/B, parsed here like every
|
||||
|
||||
@@ -257,7 +257,7 @@ namespace {
|
||||
// RESIDENT, residency comes only from AdoptPersistentMap, and every persistent-map
|
||||
// acquisition declines under split by R-6 (PipeApply.cpp's MGPipeApplyMapPersistent:
|
||||
// "A SPLIT BUILD RUNS AT TIER T2 AND DECLINES EVERY ACQUISITION, ALWAYS"; T0/T1 are
|
||||
// P11's and AdoptTierIsEmulate is a named Fatal for them). So the capability is wired
|
||||
// unimplemented, refused by name at the handshake since P11 A1). So the capability is wired
|
||||
// AHEAD of its consumer on purpose, and `rsd=` on the PipeStats line is the counter that
|
||||
// will show the records the day P11 lands a tier that adopts. See notes/p7/magma-c.md.
|
||||
TEST_F(CtWireScenario, TheServerPublishesTheResidentSubDataCapabilityFromItsOwnTable) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "../Server/ServerLoop.h"
|
||||
#include "../Server/ServerSession.h"
|
||||
#include "../Protocol/SurfaceOpCodec.h"
|
||||
#include "../Transport/AdoptTier.h"
|
||||
#include "../Transport/FdPassing.h"
|
||||
#include "../Transport/InProcessTransport.h"
|
||||
#include "WireTables.h"
|
||||
@@ -790,6 +791,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Stop();
|
||||
return result;
|
||||
}
|
||||
// P11 A1: the adopt tier, settled by the side that holds the knob once the link terms
|
||||
// say which plane this is (inproc is shared segments by construction), before any
|
||||
// record. T0/T1 over shared segments die here by name (Transport/AdoptTier.h).
|
||||
(void)Transport::SettleAdoptTierAtHandshake(
|
||||
terms->dataPlane() == ::MobileGL::Wire::DataPlane::Stream, Transport::AdoptTierSide::Client);
|
||||
// The four SegmentRefs are what a spawn client MAPS (P6). Under inproc the mapping
|
||||
// already exists, so what they are good for here is the cross-check that the two
|
||||
// sides agree about the geometry at all - which is the assertion that would
|
||||
@@ -1026,6 +1032,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return result;
|
||||
}
|
||||
stream = terms->dataPlane() == ::MobileGL::Wire::DataPlane::Stream;
|
||||
// P11 A1: as in StartOverTransportPair - the knob against the plane the link terms
|
||||
// settled, before the data connection and before any record. A stream refuses T0/T1
|
||||
// by name and runs T2; shared segments die here by name (Transport/AdoptTier.h).
|
||||
(void)Transport::SettleAdoptTierAtHandshake(stream, Transport::AdoptTierSide::Client);
|
||||
if (stream) {
|
||||
// PH-7 (4). A Stream Welcome without a nonce of exactly the minted width names no
|
||||
// data connection this client could open; refused rather than guessed at.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "PersistentMapTracker.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
#include <MG_Remote/Transport/AdoptTier.h>
|
||||
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||
@@ -1246,27 +1247,23 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Bool AdoptTierIsEmulate() {
|
||||
const Uint32 tier = MG_Config::Ipc.AdoptTier;
|
||||
if (tier == 2) return true;
|
||||
// A NAMED refusal, not a silent fall back to T2. T0 (a real cross-process shared
|
||||
// mapping) and T1 (a server-side staging map) are P11's, and the reason the knob
|
||||
// parses them today is that the negative control needs a spelling before the thing
|
||||
// it controls exists. Falling back would make `MOBILEGL_IPC_ADOPT_TIER=0` look like
|
||||
// a working T0 run and silently produce pmap bytes it must not produce.
|
||||
// 0 and 1 are the two CONTRACT §5 promises - a real cross-process shared mapping and a
|
||||
// server-side staging map - and they name P11. Anything else is not a tier at all, and
|
||||
// saying "P11 implements it" of a 7 would be a lie the operator then repeats. Both die
|
||||
// here rather than at parse, which is late: the abort lands at the first
|
||||
// map_persistent, so a mis-set run gets through EGL bring-up and a frame of setup
|
||||
// first. Moving it to the parse means a knob-validity rule in ConfigLoader, which is
|
||||
// c0's file; filed for the integrator rather than taken here.
|
||||
// `dl` (CONTRACT-P6 5.2): THE FAMILY WORD THIS SITE NEVER CARRIED. a6 found two aborts
|
||||
// under MG_Remote/ with no Fatal{ marker at all, so "the log stays verbatim" was not true
|
||||
// of them and no family grep could see them - this is one. (The other, WireLog.cpp's, is
|
||||
// the sanctioned funnel: every one of its callers passes a Fatal{ string of its own.)
|
||||
// P11 A1 (CONTRACT-P11 §1): THE VERDICT IS THE HANDSHAKE'S, NOT THIS CALL'S. Both roles
|
||||
// settle the tier from the knob and the data plane before the first record
|
||||
// (Transport/AdoptTier.cpp): a stream refuses T0/T1 by name and runs T2, a client over
|
||||
// shared segments dies by name there, and a server over shared segments serves the T2
|
||||
// its client settled. So a settled session reaching this with 0/1 is a T2 session and
|
||||
// says nothing more - the one line was the handshake's.
|
||||
if (tier <= 1 && Transport::AdoptTierSettledAtHandshake()) return true;
|
||||
// A map_persistent NO handshake settled (a session-free caller) is still a NAMED
|
||||
// refusal rather than a silent T2: falling back would make `MOBILEGL_IPC_ADOPT_TIER=0`
|
||||
// look like a working T0 run and silently produce pmap bytes it must not produce.
|
||||
// Anything above 2 is not a tier at all.
|
||||
// `dl` (CONTRACT-P6 5.2): the family word every death here carries.
|
||||
if (tier <= 1) {
|
||||
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
|
||||
"MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u "
|
||||
"names an adoption tier P11 implements and P5 does not; P5 runs at T2 "
|
||||
"(emulate) only.",
|
||||
"reached a map_persistent that no handshake settled; T0 and T1 are not "
|
||||
"implemented and only T2 (emulate) runs.",
|
||||
static_cast<unsigned>(tier), static_cast<unsigned>(tier));
|
||||
} else {
|
||||
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
|
||||
|
||||
@@ -268,10 +268,13 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// draw-path binding walks move to the client.
|
||||
void PushPersistentMapsBeforeVerb();
|
||||
|
||||
// R-6's tier gate, and the ONE spelling of it. True for MOBILEGL_IPC_ADOPT_TIER=2, the
|
||||
// only tier P5 implements; 0 (a real cross-process shared mapping) and 1 (a server-side
|
||||
// staging map) parse - so the negative control has a name before the thing it controls
|
||||
// exists - and are a NAMED refusal here rather than a silent fall back to T2. It is asked
|
||||
// R-6's tier gate, read at use. True for MOBILEGL_IPC_ADOPT_TIER=2, the only implemented
|
||||
// tier. The tiers are design/07's: T0 = the client allocates an AHardwareBuffer and the
|
||||
// server imports it (P11 package B), T1 = the server exports an opaque fd (closed), T2 =
|
||||
// decline and push. 0 and 1 are SETTLED AT THE HANDSHAKE (P11 A1, Transport/AdoptTier.h):
|
||||
// a stream refuses them by name and runs T2, a client over shared segments dies by name
|
||||
// there. So a settled session answers true here; only a map_persistent no handshake
|
||||
// settled is still a named refusal. It is asked
|
||||
// by MGPipeApplyMapPersistent, which is where the decline is decided, so the client's
|
||||
// three adoption call sites keep their existing "null means declined" branch and the
|
||||
// map-persistent-roundtrips counter keeps counting ATTEMPTS in both arms (E3(c) asserts
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../CapsCodec.h"
|
||||
#include "../Handshake.h"
|
||||
#include "../Transport/LinkMetrics.h"
|
||||
#include "../Transport/AdoptTier.h"
|
||||
#include "../Protocol/generated/protocol_generated.h"
|
||||
#include "../Transport/InProcessTransport.h"
|
||||
|
||||
@@ -673,6 +674,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
"backend request differs from server", m_backend == nullptr ? 0 :
|
||||
static_cast<Uint32>(m_backend->GetBackendType()), hello->backendType());
|
||||
|
||||
// ---- P11 A1: this side's adopt tier, from its own knob and the plane the Hello asked
|
||||
// for, before a byte of Welcome. A stream refuses T0/T1 by name and serves T2; over
|
||||
// shared segments the client settles it (Transport/AdoptTier.h). 0/1 never die here.
|
||||
(void)Transport::SettleAdoptTierAtHandshake(stream, Transport::AdoptTierSide::Server);
|
||||
|
||||
// ---- 3. the four segments, both control pages, the rings.
|
||||
MobileGLResult created = MOBILEGL_ERR_UNSUPPORTED;
|
||||
// PH-7 (4), ID-P7-3. Minted HERE - after the token was checked, before a byte of Welcome
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Transport/AdoptTier.cpp
|
||||
// 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
|
||||
|
||||
#include "AdoptTier.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
namespace {
|
||||
std::atomic<bool> g_settled{false};
|
||||
|
||||
const char* SideName(AdoptTierSide side) { return side == AdoptTierSide::Client ? "client" : "server"; }
|
||||
} // namespace
|
||||
|
||||
std::uint32_t SettleAdoptTierAtHandshake(bool streamDataPlane, AdoptTierSide side) {
|
||||
const std::uint32_t knob = MG_Config::Ipc.AdoptTier;
|
||||
if (knob > 2) {
|
||||
// Unreachable through ConfigLoader (it admits 0..2); a direct assignment is the only
|
||||
// way here, and "P11 implements it" of a 7 would be a lie.
|
||||
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
|
||||
"MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is not an "
|
||||
"adoption tier; the values are 0 and 1 (not implemented) and 2 (emulate, the default)",
|
||||
static_cast<unsigned>(knob), static_cast<unsigned>(knob));
|
||||
}
|
||||
if (knob == 2) {
|
||||
g_settled.store(true, std::memory_order_release);
|
||||
return 2;
|
||||
}
|
||||
if (streamDataPlane) {
|
||||
// ONE LINE PER SESSION, on the side that holds the knob. The tcp lane's server is a
|
||||
// shared fixture that never sees the client's environment, so each side says it for
|
||||
// itself; neither side dies, because T2 is the only tier a stream can carry.
|
||||
MGLOG_W("MG_Remote %s: Refuse{AdoptTierOnStream, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u names a "
|
||||
"shared-mapping adoption tier and this session's data plane is a stream (tcp): the two "
|
||||
"sides share no memory to map, so the session runs T2 (emulate: the client keeps the "
|
||||
"shadow and pushes)",
|
||||
SideName(side), static_cast<unsigned>(knob), static_cast<unsigned>(knob));
|
||||
g_settled.store(true, std::memory_order_release);
|
||||
return 2;
|
||||
}
|
||||
if (side == AdoptTierSide::Server) {
|
||||
MGLOG_W("MG_Remote server: MOBILEGL_IPC_ADOPT_TIER=%u is the client's to settle on a shared-segment "
|
||||
"link (a client naming T0/T1 dies by name at its own handshake); this server serves T2",
|
||||
static_cast<unsigned>(knob));
|
||||
g_settled.store(true, std::memory_order_release);
|
||||
return 2;
|
||||
}
|
||||
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
|
||||
"MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u names a "
|
||||
"shared-mapping adoption tier no package implements yet (T0 is P11 package B's, T1 is "
|
||||
"closed); refused at the handshake over shared segments, before any record is emitted. "
|
||||
"Unset it or set 2 (emulate)",
|
||||
static_cast<unsigned>(knob), static_cast<unsigned>(knob));
|
||||
}
|
||||
|
||||
bool AdoptTierSettledAtHandshake() { return g_settled.load(std::memory_order_acquire); }
|
||||
|
||||
void ForgetAdoptTierSettlementForTest() { g_settled.store(false, std::memory_order_release); }
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
@@ -0,0 +1,53 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Transport/AdoptTier.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
|
||||
|
||||
// P11 A1 (MG_Remote/CONTRACT-P11.md §1): THE PERSISTENT-MAP ADOPTION TIER IS SETTLED AT THE
|
||||
// HANDSHAKE, BY THE SIDE THAT KNOWS THE DATA PLANE, BEFORE THE FIRST map_persistent.
|
||||
//
|
||||
// Tier names follow docs/Disaggregated/design/07: T0 = the client allocates an AHardwareBuffer
|
||||
// and the server imports it; T1 = the server exports an opaque fd; T2 = the resource owner
|
||||
// declines and the client keeps the shadow and pushes. Only T2 is implemented (T0 is P11
|
||||
// package B's, T1 is closed), so MOBILEGL_IPC_ADOPT_TIER=0/1 is either refused by name or
|
||||
// fatal - never a silent T2, and never discovered at the first map_persistent:
|
||||
//
|
||||
// data plane side knob 0 / 1
|
||||
// Stream (tcp) either one line `Refuse{AdoptTierOnStream, "T<n>"}`, then T2. Never Fatal:
|
||||
// a stream has no memory the two sides share, so no tier but T2
|
||||
// can exist on it.
|
||||
// SharedSegments client Fatal{UnimplementedAdoptTier, "T<n>"} at the handshake, before any
|
||||
// record is emitted.
|
||||
// SharedSegments server one warning that the client settles the tier on this plane, then
|
||||
// T2. (inproc and fork share the client's environment, so the client
|
||||
// dies by name right after; a unix-socket server launched on its own
|
||||
// serves the T2 its client settled.)
|
||||
//
|
||||
// In MG_Remote/Transport (the link-ratchet's SERVER partition) because both roles call it:
|
||||
// ServerSession::Accept and ClientSession's two Start halves. The at-use check the server's
|
||||
// applier and codec ask (Client::AdoptTierIsEmulate) reads the settlement back.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
enum class AdoptTierSide : std::uint8_t { Client, Server };
|
||||
|
||||
// Settles this session's tier from MOBILEGL_IPC_ADOPT_TIER and the negotiated data plane,
|
||||
// on `side`, and returns the tier the session runs (2 until package B). Logs at most one
|
||||
// line per call; aborts (SessionFail) only on the client over shared segments.
|
||||
std::uint32_t SettleAdoptTierAtHandshake(bool streamDataPlane, AdoptTierSide side);
|
||||
|
||||
// True once a handshake in this process has settled the tier. The at-use check trusts a
|
||||
// settled session and still refuses by name a map_persistent no handshake settled.
|
||||
bool AdoptTierSettledAtHandshake();
|
||||
|
||||
// Test seam: forget the settlement, so one process can drive both answers.
|
||||
void ForgetAdoptTierSettlementForTest();
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
@@ -1840,10 +1840,11 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// used to decline UNCONDITIONALLY and AdoptTier had no reference anywhere on the
|
||||
// codec path, so MOBILEGL_IPC_ADOPT_TIER=0 and =1 - which contract §5 promises
|
||||
// "parse and are Fatal at use, naming P11" - decoded as an ordinary DECLINED and
|
||||
// the operator got a run that looked like a working T0. AdoptTierIsEmulate returns
|
||||
// true at T2 and ABORTS at T0/T1 on its own named diagnostic, so the return value
|
||||
// is deliberately not a branch: P5 declines at every tier it survives (R-6), and
|
||||
// the two forbidden ones never get this far.
|
||||
// the operator got a run that looked like a working T0. Since P11 A1 the tier is
|
||||
// settled at the handshake (Transport/AdoptTier.h): a stream refused T0/T1 there by
|
||||
// name and runs T2, shared segments died there, so a settled session answers true
|
||||
// and only a map_persistent no handshake settled still aborts by name. The return
|
||||
// value is deliberately not a branch: every surviving tier declines (R-6).
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
(void)MG_Remote::Client::AdoptTierIsEmulate();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Remote/Client/GpuWritePending.h>
|
||||
#include <MG_Remote/Client/PersistentMapTracker.h>
|
||||
#include <MG_Remote/Transport/AdoptTier.h>
|
||||
#endif
|
||||
|
||||
using namespace MobileGL;
|
||||
@@ -84,6 +85,7 @@ namespace {
|
||||
// membership assertion in this file would then be about a different code path.
|
||||
MG_State::GLState::SetBufferBackendOps(nullptr);
|
||||
g_mintedPersistentBase = nullptr;
|
||||
MG_Remote::Transport::ForgetAdoptTierSettlementForTest();
|
||||
PersistentMapTracker::Instance().ClearForTest();
|
||||
MG_State::pGLContext = Move(m_context);
|
||||
MG_Config::Transport = m_transport;
|
||||
@@ -536,14 +538,31 @@ TEST_F(SplitBufferSet, UnderSplitTheWritebackClearsThePendingFlagAndNotTheReques
|
||||
EXPECT_FALSE(buffer->HasOutstandingGpuWrite());
|
||||
}
|
||||
|
||||
// R-6's tier gate. T2 is the only tier P5 implements; the other two are a NAMED refusal and
|
||||
// their spelling exists now so the P11 negative control has one.
|
||||
// R-6's tier gate in P11 A1's shape (CONTRACT-P11 §1). T2 is the only implemented tier, and 0 / 1
|
||||
// are SETTLED AT THE HANDSHAKE by the data plane rather than discovered at the first
|
||||
// map_persistent: a stream refuses them by name and runs T2 on either side, a client over shared
|
||||
// segments dies there by name, a server over shared segments serves T2. The at-use check trusts a
|
||||
// settled session and still dies by name for a map_persistent no handshake settled. The session
|
||||
// halves are RemoteClientControls.AdoptTier*OverSharedSegments* and AdoptTierStreamTest.
|
||||
TEST_F(SplitBufferSet, OnlyAdoptTierTwoIsImplemented) {
|
||||
namespace Tier = MG_Remote::Transport;
|
||||
EXPECT_TRUE(MG_Remote::Client::AdoptTierIsEmulate());
|
||||
MG_Config::Ipc.AdoptTier = 0;
|
||||
EXPECT_DEATH(MG_Remote::Client::AdoptTierIsEmulate(), "");
|
||||
MG_Config::Ipc.AdoptTier = 1;
|
||||
EXPECT_DEATH(MG_Remote::Client::AdoptTierIsEmulate(), "");
|
||||
for (const Uint32 tier : {0u, 1u}) {
|
||||
SCOPED_TRACE(tier);
|
||||
MG_Config::Ipc.AdoptTier = tier;
|
||||
Tier::ForgetAdoptTierSettlementForTest();
|
||||
EXPECT_DEATH(MG_Remote::Client::AdoptTierIsEmulate(), "UnimplementedAdoptTier.*no handshake settled");
|
||||
EXPECT_DEATH(Tier::SettleAdoptTierAtHandshake(false, Tier::AdoptTierSide::Client),
|
||||
"UnimplementedAdoptTier.*refused at the handshake over shared segments");
|
||||
for (const auto side : {Tier::AdoptTierSide::Client, Tier::AdoptTierSide::Server}) {
|
||||
Tier::ForgetAdoptTierSettlementForTest();
|
||||
EXPECT_EQ(Tier::SettleAdoptTierAtHandshake(true, side), 2u);
|
||||
EXPECT_TRUE(MG_Remote::Client::AdoptTierIsEmulate());
|
||||
}
|
||||
Tier::ForgetAdoptTierSettlementForTest();
|
||||
EXPECT_EQ(Tier::SettleAdoptTierAtHandshake(false, Tier::AdoptTierSide::Server), 2u);
|
||||
EXPECT_TRUE(MG_Remote::Client::AdoptTierIsEmulate());
|
||||
}
|
||||
MG_Config::Ipc.AdoptTier = 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// MobileGL - MobileGL/MG_Test/Wire/AdoptTierStreamTest.cpp
|
||||
// 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
|
||||
|
||||
// P11 A1 (MG_Remote/CONTRACT-P11.md §1): MOBILEGL_IPC_ADOPT_TIER=0/1 ON A STREAM DATA PLANE.
|
||||
//
|
||||
// Real servers on loopback TCP and forked peers (P12ServerRig.h), so the session is the product's
|
||||
// two-process tcp session and each side reads its OWN knob, as on the tcp lane:
|
||||
// * a client that names T0 or T1 against a `--serve` supervisor comes up, says
|
||||
// `Refuse{AdoptTierOnStream, "T<n>"}` exactly once, gets DECLINED for its map_persistent and
|
||||
// goes on applying records;
|
||||
// * an in-process display server (the render-server Activity's shape) that names T0 says the
|
||||
// same line once per session, never dies, and serves T2.
|
||||
// The shared-segment half (a client naming T0/T1 dies at the handshake, before any map_persistent)
|
||||
// is RemoteClientControls.AdoptTier*OverSharedSegmentsDiesAtTheHandshakeBeforeAnyMapPersistent.
|
||||
//
|
||||
// RED-ONCE: delete the stream branch of Transport::SettleAdoptTierAtHandshake -> the client peers
|
||||
// die at the handshake (started 0, Fatal{UnimplementedAdoptTier}) and the server case finds no
|
||||
// refusal line.
|
||||
|
||||
#include "P12ServerRig.h"
|
||||
|
||||
#include <MG_Remote/Server/InProcessServer.h>
|
||||
#include <MG_Remote/Wire/PipeWireCodec.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <pthread.h>
|
||||
|
||||
using namespace MobileGL;
|
||||
using namespace P12;
|
||||
|
||||
namespace {
|
||||
|
||||
// A SERVER WHOSE OWN KNOB SAYS T0. Not the exec'd `--serve` supervisor: ServerSpawn scrubs
|
||||
// every MOBILEGL_IPC_* but five server-owned knobs from a child it launches, so a knob set here
|
||||
// would never reach it. The in-process display server (InProcessServerTest's shape - the device
|
||||
// render-server Activity FCL reaches over tcp) is a process forked from this one, sets the
|
||||
// knob in its own environment, and RunSession re-reads the environment for every session.
|
||||
[[noreturn]] void RunInProcessServerWithAdoptTier(const std::string& endpoint, const std::string& logBase,
|
||||
const char* adoptTier) {
|
||||
::setenv("MOBILEGL_LOG_FILE_PATH", logBase.c_str(), 1);
|
||||
::setenv("MOBILEGL_IPC_ROLE", "server", 1);
|
||||
::setenv("MOBILEGL_IPC_DIAL", "no", 1);
|
||||
::setenv("MOBILEGL_IPC_ADOPT_TIER", adoptTier, 1);
|
||||
for (const char* name : {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB",
|
||||
"MOBILEGL_IPC_STAGE_MB", "MOBILEGL_IPC_CONTROL", "MOBILEGL_IPC_SURFACE",
|
||||
"MOBILEGL_BACKEND_TYPE"})
|
||||
::unsetenv(name);
|
||||
sigset_t stop;
|
||||
sigemptyset(&stop);
|
||||
sigaddset(&stop, SIGTERM);
|
||||
pthread_sigmask(SIG_BLOCK, &stop, nullptr);
|
||||
std::atomic<bool> returned{false};
|
||||
std::thread server([&] {
|
||||
(void)mobilegl_server_serve_inprocess(endpoint.c_str());
|
||||
returned.store(true);
|
||||
});
|
||||
bool stopping = false;
|
||||
while (!returned.load()) {
|
||||
timespec slice{0, 100 * 1000 * 1000};
|
||||
if (sigtimedwait(&stop, nullptr, &slice) == SIGTERM && !stopping) {
|
||||
stopping = true;
|
||||
mobilegl_server_stop_inprocess();
|
||||
}
|
||||
}
|
||||
server.join();
|
||||
std::fflush(nullptr);
|
||||
::_exit(0);
|
||||
}
|
||||
|
||||
// THE SERVER GETS A LOG BASE OF ITS OWN (`server->logBase`), the peers another (`peerBase`, which
|
||||
// this process exports for the forks): a peer writes the lines the server forwards into
|
||||
// `<its base>.server.log`, opened "w", so sharing one base truncates the server's own file under
|
||||
// it (measured: NUL holes and the handshake's lines gone).
|
||||
bool LaunchInProcessServerWithAdoptTier(const std::string& label, const char* adoptTier, const std::string& peerBase,
|
||||
ServerProcess* server) {
|
||||
server->logBase = "/tmp/mgl-p11-" + label + "-server-" + std::to_string(::getpid()) + ".log";
|
||||
::setenv("MOBILEGL_LOG_FILE_PATH", peerBase.c_str(), 1);
|
||||
Debug::TruncateRoleLogs(peerBase.c_str());
|
||||
PinHeadlessEgl();
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
const int port = FreeLoopbackPort();
|
||||
if (port <= 0) return false;
|
||||
server->endpoint = "tcp://127.0.0.1:" + std::to_string(port);
|
||||
Debug::TruncateRoleLogs(server->logBase.c_str());
|
||||
std::fflush(nullptr);
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) return false;
|
||||
if (pid == 0) RunInProcessServerWithAdoptTier(server->endpoint, server->logBase, adoptTier);
|
||||
server->pid = pid;
|
||||
bool listening = false;
|
||||
(void)WaitFor(
|
||||
[&] {
|
||||
if (server->Log().find("listening on") != std::string::npos) return listening = true;
|
||||
return server->WaitExit(0) && server->pid <= 0;
|
||||
},
|
||||
10000);
|
||||
if (listening) return true;
|
||||
if (server->pid > 0) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The client's own role log for this rig's base (the peers inherit it from LaunchSupervisor).
|
||||
std::string ClientLog(const ServerProcess& server) {
|
||||
return ReadFile(Debug::RoleLogPath(server.logBase.c_str(), Debug::LogRole::Client));
|
||||
}
|
||||
|
||||
std::string RefusalLine(Uint32 tier) {
|
||||
return "Refuse{AdoptTierOnStream, \"T" + std::to_string(tier) + "\"}";
|
||||
}
|
||||
|
||||
// One map_persistent, then one ordinary record: the answer to the first and proof the session
|
||||
// is still applying after it.
|
||||
void MapPersistentThenARecord(Remote::Client::ClientSession& client, PeerReport& r) {
|
||||
P::MGPHandleOnly handle{};
|
||||
handle.Kind = static_cast<Uint32>(P::MGPipeKind::Buffer);
|
||||
Int32 status = Remote::Wire::ReplySink::kStatusError;
|
||||
const Uint64 seq =
|
||||
client.EmitAndWait(P::MGPWireOp::MapPersistent, &handle, sizeof(handle), nullptr, 0, nullptr, 0, &status);
|
||||
r.status = seq == Remote::Wire::kInvalidSeq ? -98 : status;
|
||||
P::MGPMemoryBarrier barrier{};
|
||||
barrier.Bits = 0x2000u;
|
||||
r.applied = Emit(client, P::MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier)) ? 1 : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(AdoptTierStream, AClientNamingT0OrT1IsRefusedOnceAndItsMapPersistentIsDeclined) {
|
||||
ServerProcess server;
|
||||
ASSERT_TRUE(LaunchSupervisor("adopt-client", &server)) << server.Log();
|
||||
for (Uint32 tier : {0u, 1u}) {
|
||||
SCOPED_TRACE(tier);
|
||||
const PeerReport r = RunPeer(server.endpoint, MapPersistentThenARecord, /*stopCleanly=*/true, 30000, 0,
|
||||
nullptr, [tier] { MG_Config::Ipc.AdoptTier = tier; });
|
||||
const std::string client = ClientLog(server);
|
||||
ASSERT_EQ(r.started, 1) << "the session did not come up: " << r.note << "\n" << client;
|
||||
EXPECT_EQ(r.status, Remote::Wire::ReplySink::kStatusDeclined)
|
||||
<< "map_persistent was not DECLINED on a stream session\n" << client;
|
||||
EXPECT_EQ(r.applied, 1) << "the session stopped applying after the map_persistent\n" << client;
|
||||
EXPECT_EQ(Count(client, RefusalLine(tier)), 1u)
|
||||
<< "a stream session names T" << tier << " exactly once, at the handshake\n" << client;
|
||||
EXPECT_EQ(Count(client, "Fatal{"), 0u) << client;
|
||||
EXPECT_EQ(Count(server.Log(), "AdoptTierOnStream"), 0u)
|
||||
<< "the server's own knob is 2; the line is the client's\n" << server.Log();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AdoptTierStream, AServerNamingT0RefusesOncePerSessionAndServesT2) {
|
||||
ServerProcess server;
|
||||
const std::string peerBase = "/tmp/mgl-p11-adopt-server-peers-" + std::to_string(::getpid()) + ".log";
|
||||
ASSERT_TRUE(LaunchInProcessServerWithAdoptTier("adopt-server", "0", peerBase, &server)) << server.Log();
|
||||
// Both role files of the server's own base: which role a session thread of the in-process
|
||||
// server logs under is not what this case is about.
|
||||
const auto serverLog = [&server] { return Debug::ReadRoleLogs(server.logBase.c_str()); };
|
||||
for (Uint32 session = 1; session <= 2; ++session) {
|
||||
SCOPED_TRACE(session);
|
||||
const PeerReport r = RunPeer(server.endpoint, MapPersistentThenARecord, /*stopCleanly=*/true, 30000, 0,
|
||||
nullptr, [] { MG_Config::Ipc.AdoptTier = 2u; });
|
||||
const std::string client = ReadFile(Debug::RoleLogPath(peerBase.c_str(), Debug::LogRole::Client));
|
||||
ASSERT_EQ(r.started, 1) << r.note << "\n" << serverLog();
|
||||
EXPECT_EQ(r.status, Remote::Wire::ReplySink::kStatusDeclined) << serverLog();
|
||||
EXPECT_EQ(r.applied, 1) << serverLog();
|
||||
EXPECT_EQ(Count(client, "AdoptTierOnStream"), 0u) << "the client's knob is 2\n" << client;
|
||||
EXPECT_EQ(Count(serverLog(), RefusalLine(0)), session)
|
||||
<< "one refusal line per server session, at its handshake\n" << serverLog();
|
||||
}
|
||||
EXPECT_EQ(Count(serverLog(), "Fatal{UnimplementedAdoptTier"), 0u) << serverLog();
|
||||
if (!::testing::Test::HasFailure()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(Debug::RoleLogPath(peerBase.c_str(), Debug::LogRole::Client), ec);
|
||||
std::filesystem::remove(Debug::RoleLogPath(peerBase.c_str(), Debug::LogRole::Server), ec);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,10 @@ if (NOT WIN32)
|
||||
# MOBILEGL_IPC_SURFACE=server against a display-less `--serve` supervisor. Both drive real peers
|
||||
# and (the second, and D8's case) the real server image, as PeerLatchTest does.
|
||||
list(APPEND MOBILEGL_WIRE_TESTS InProcessServerTest ServerOwnedSurfaceTest)
|
||||
# P11 A1: MOBILEGL_IPC_ADOPT_TIER=0/1 on a stream data plane, against a real `--serve`
|
||||
# supervisor over loopback TCP (P12ServerRig.h): refused by name once per session on the side
|
||||
# that names it, map_persistent DECLINED, never a Fatal. Needs the server image too.
|
||||
list(APPEND MOBILEGL_WIRE_TESTS AdoptTierStreamTest)
|
||||
endif()
|
||||
|
||||
include(GoogleTest)
|
||||
@@ -63,7 +67,8 @@ foreach (test IN LISTS MOBILEGL_WIRE_TESTS)
|
||||
${MGL_ROOT}/MobileGL
|
||||
${MGL_ROOT}/3rdparty/flatbuffers/include
|
||||
)
|
||||
if (test STREQUAL "PeerLatchTest" OR test STREQUAL "InProcessServerTest" OR test STREQUAL "ServerOwnedSurfaceTest")
|
||||
if (test STREQUAL "PeerLatchTest" OR test STREQUAL "InProcessServerTest" OR test STREQUAL "ServerOwnedSurfaceTest" OR
|
||||
test STREQUAL "AdoptTierStreamTest")
|
||||
# It builds records and a program archive by hand, so it reaches MG_Pipe and MG_State
|
||||
# the way the codec suites do. (P12's two server suites share PeerLatchTest's healthy
|
||||
# session, P12ServerRig.h, and so its include paths.)
|
||||
@@ -81,7 +86,7 @@ foreach (test IN LISTS MOBILEGL_WIRE_TESTS)
|
||||
)
|
||||
|
||||
if (test STREQUAL "ServerSpawnTest" OR test STREQUAL "PeerLatchTest" OR test STREQUAL "InProcessServerTest" OR
|
||||
test STREQUAL "ServerOwnedSurfaceTest")
|
||||
test STREQUAL "ServerOwnedSurfaceTest" OR test STREQUAL "AdoptTierStreamTest")
|
||||
# It execs the real image, so it must be built first and must be told
|
||||
# where it is. MOBILEGL_TEST_SERVER_PATH is read by the test's
|
||||
# ServerImage() helper; without it the test would depend on the working
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include <MG_Pipe/MGPipeRenderStateSpans.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_Remote/CapsCodec.h>
|
||||
#include <MG_Remote/Transport/AdoptTier.h>
|
||||
#include <MG_Remote/Transport/Ring.h>
|
||||
#include <MG_Remote/Wire/PipeWireCodec.h>
|
||||
#include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h>
|
||||
@@ -2947,31 +2948,76 @@ TEST_F(PipeWireCodecTest, ASecondProcessResolverIsFatalRatherThanASilentRace) {
|
||||
EXPECT_NE(r.Log.find("already installed"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// ---- R-6 / contract §5: the two forbidden adoption tiers die ON THE WIRE PATH TOO --------
|
||||
// ---- R-6 / P11 A1 (CONTRACT-P11 §1): the codec's MapPersistent arm and the adopt tier -----
|
||||
//
|
||||
// wave1-codex-verify.md §4: `AdoptTier` had ZERO references anywhere on the codec path, so
|
||||
// MOBILEGL_IPC_ADOPT_TIER=0 and =1 - which contract §5 promises "parse and are Fatal at use,
|
||||
// naming P11" - decoded as an ordinary DECLINED. The verifier set each forbidden tier inside
|
||||
// KReplySlotMapPersistentIsAConstantDecline and watched its successful-decline assertions
|
||||
// still pass, on BOTH tiers.
|
||||
//
|
||||
// THESE ARE FORKED, NOT EXPECT_DEATH, for the reason at the top of this file - and forking is
|
||||
// what lets the case REQUIRE THE DIAGNOSTIC rather than any abort: r.Log is searched for the
|
||||
// exact sentence AdoptTierIsEmulate prints. ID-46 finding 10 is an empty death regex; the
|
||||
// EXPECT_NE lines below are the opposite of that, and a crash for any other reason fails the
|
||||
// case on the log it prints.
|
||||
//
|
||||
// I made both red once, by doing X: X = restoring the unconditional decline in
|
||||
// PipeWireCodec.cpp's MapPersistent arm (deleting the AdoptTierIsEmulate call). Both children
|
||||
// then exit 0 having posted a clean DECLINED, and both cases fail on DiedOfAbort.
|
||||
// wave1-codex-verify.md §4 found `AdoptTier` had ZERO references on the codec path, so a T0/T1
|
||||
// run decoded as an ordinary DECLINED and looked like a working T0. The arm has asked the tier
|
||||
// since. P11 A1 moved the VERDICT to the handshake (Transport/AdoptTier.h): a stream refuses
|
||||
// T0/T1 by name there and runs T2, shared segments die there. So this arm now sees two shapes:
|
||||
// * after a stream handshake settled the tier, T0/T1 DECLINE like T2, with no second refusal
|
||||
// line - the two cases below that used to be "dies at use";
|
||||
// * with no handshake behind it, the arm still dies by name (forked, for the log), which keeps
|
||||
// the wave-1 finding pinned: red once by deleting the arm's AdoptTierIsEmulate call - the
|
||||
// child then exits 0 having posted a clean DECLINED.
|
||||
// The session-level halves are AdoptTierStreamTest and RemoteClientControls.AdoptTier*.
|
||||
|
||||
TEST_F(PipeWireCodecTest, AdoptTierZeroIsFatalOnTheWirePathAndNamesP11) {
|
||||
namespace {
|
||||
void ExpectStreamSettledTierDeclinesOnTheWire(Uint32 tier) {
|
||||
const MG_Config::TransportMode savedTransport = MG_Config::Transport;
|
||||
const Uint32 savedTier = MG_Config::Ipc.AdoptTier;
|
||||
struct Restore {
|
||||
MG_Config::TransportMode T;
|
||||
Uint32 A;
|
||||
~Restore() {
|
||||
MG_Config::Transport = T;
|
||||
MG_Config::Ipc.AdoptTier = A;
|
||||
Transport::ForgetAdoptTierSettlementForTest();
|
||||
}
|
||||
} restore{savedTransport, savedTier};
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
MG_Config::Ipc.AdoptTier = tier;
|
||||
Transport::ForgetAdoptTierSettlementForTest();
|
||||
const std::string before = ReadLog();
|
||||
ASSERT_EQ(Transport::SettleAdoptTierAtHandshake(true, Transport::AdoptTierSide::Server), 2u);
|
||||
|
||||
Wire2 wire;
|
||||
const MGPHandleOnly handle = HandleOnly(5, MGPipeKind::Buffer);
|
||||
for (int record = 0; record < 2; ++record) {
|
||||
ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::MapPersistent, &handle, sizeof(handle)), kInvalidSeq);
|
||||
bool applied = false;
|
||||
ASSERT_TRUE(wire.PumpOne(&applied));
|
||||
EXPECT_TRUE(applied);
|
||||
}
|
||||
ASSERT_EQ(wire.Answers().All.size(), 2u);
|
||||
for (const auto& answer : wire.Answers().All) {
|
||||
EXPECT_EQ(answer.Status, ReplySink::kStatusDeclined);
|
||||
EXPECT_TRUE(answer.Bytes.empty());
|
||||
}
|
||||
const std::string delta = ReadLog().substr(before.size());
|
||||
const std::string line = "Refuse{AdoptTierOnStream, \"T" + std::to_string(tier) + "\"}";
|
||||
std::size_t count = 0;
|
||||
for (auto at = delta.find(line); at != std::string::npos; at = delta.find(line, at + 1)) ++count;
|
||||
EXPECT_EQ(count, 1u) << "one refusal at the handshake and none per map_persistent:\n" << delta;
|
||||
EXPECT_EQ(delta.find("Fatal{"), std::string::npos) << delta;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(PipeWireCodecTest, AdoptTierZeroAfterAStreamHandshakeIsADeclineOnTheWirePath) {
|
||||
ExpectStreamSettledTierDeclinesOnTheWire(0u);
|
||||
}
|
||||
|
||||
TEST_F(PipeWireCodecTest, AdoptTierOneAfterAStreamHandshakeIsADeclineOnTheWirePath) {
|
||||
ExpectStreamSettledTierDeclinesOnTheWire(1u);
|
||||
}
|
||||
|
||||
TEST_F(PipeWireCodecTest, AnUnsettledAdoptTierStillDiesByNameOnTheWirePath) {
|
||||
const ChildResult r = RunInChild([] {
|
||||
// The child dies; nothing needs restoring. The transport half is the same conjunction
|
||||
// MGPipeApplyMapPersistent uses - a monolith TRANSPORT mints like push (ID-42) and is
|
||||
// not the arm this record can arrive on.
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
MG_Config::Ipc.AdoptTier = 0u;
|
||||
Transport::ForgetAdoptTierSettlementForTest();
|
||||
Wire2 wire;
|
||||
const MGPHandleOnly handle = HandleOnly(5, MGPipeKind::Buffer);
|
||||
(void)wire.Encoder().EncodeRecord(MGPWireOp::MapPersistent, &handle, sizeof(handle));
|
||||
@@ -2980,24 +3026,7 @@ TEST_F(PipeWireCodecTest, AdoptTierZeroIsFatalOnTheWirePathAndNamesP11) {
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnimplementedAdoptTier, \"T0\"} - MOBILEGL_IPC_ADOPT_TIER=0 "
|
||||
"names an adoption tier P11 implements"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
}
|
||||
|
||||
TEST_F(PipeWireCodecTest, AdoptTierOneIsFatalOnTheWirePathAndNamesP11) {
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
MG_Config::Ipc.AdoptTier = 1u;
|
||||
Wire2 wire;
|
||||
const MGPHandleOnly handle = HandleOnly(5, MGPipeKind::Buffer);
|
||||
(void)wire.Encoder().EncodeRecord(MGPWireOp::MapPersistent, &handle, sizeof(handle));
|
||||
bool applied = false;
|
||||
(void)wire.PumpOne(&applied);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnimplementedAdoptTier, \"T1\"} - MOBILEGL_IPC_ADOPT_TIER=1 "
|
||||
"names an adoption tier P11 implements"),
|
||||
"reached a map_persistent that no handshake settled"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
}
|
||||
|
||||
@@ -324,6 +324,55 @@ TEST(RemoteClientControls, MapPersistentErrorIsNotADecline) {
|
||||
ExpectNamedAbort(child, "Fatal{ReplyError, \"map_persistent\"}");
|
||||
}
|
||||
|
||||
// P11 A1 (CONTRACT-P11 §1): MOBILEGL_IPC_ADOPT_TIER=0/1 OVER SHARED SEGMENTS DIES AT THE
|
||||
// HANDSHAKE, before any record. inproc is shared segments by construction, and this is the real
|
||||
// Start (the same StartOverTransportPair the lanes run). The child writes 'S' once Start returns
|
||||
// and 'M' once its map_persistent went out, so "died before the first map_persistent" is a fact
|
||||
// the parent reads rather than infers from the log. The stream half is AdoptTierStreamTest.
|
||||
//
|
||||
// RED-ONCE: move the check back to use (drop the client's SessionFail in
|
||||
// Transport::SettleAdoptTierAtHandshake and make Client::AdoptTierIsEmulate abort on 0/1 again)
|
||||
// -> the session comes up ('S'), the map_persistent goes out, and the child dies only when the
|
||||
// server's codec asks the tier.
|
||||
namespace {
|
||||
void ExpectDeathAtTheSharedSegmentHandshake(Uint32 tier) {
|
||||
int marks[2] = {-1, -1};
|
||||
ASSERT_EQ(::pipe(marks), 0);
|
||||
const auto child = RunInChild([&marks, tier] {
|
||||
::close(marks[0]);
|
||||
MG_Config::Ipc.AdoptTier = tier;
|
||||
StartControlSession();
|
||||
const char started = 'S';
|
||||
(void)!::write(marks[1], &started, 1);
|
||||
MGPHandleOnly handle{};
|
||||
handle.Kind = static_cast<Uint32>(MGPipeKind::Buffer);
|
||||
(void)MGPipeRouteMapPersistent(handle, 64, nullptr);
|
||||
const char sent = 'M';
|
||||
(void)!::write(marks[1], &sent, 1);
|
||||
});
|
||||
::close(marks[1]);
|
||||
std::string seen;
|
||||
char c = 0;
|
||||
while (::read(marks[0], &c, 1) == 1) seen.push_back(c);
|
||||
::close(marks[0]);
|
||||
const std::string word = "Fatal{UnimplementedAdoptTier, \"T" + std::to_string(tier) + "\"}";
|
||||
ExpectNamedAbort(child, word.c_str());
|
||||
EXPECT_EQ(seen, "") << "'S' = the session came up, 'M' = a map_persistent went out, both before "
|
||||
"the death; T" << tier << " over shared segments must die at the handshake\n"
|
||||
<< child.Log;
|
||||
EXPECT_NE(child.Log.find("refused at the handshake over shared segments"), std::string::npos)
|
||||
<< child.Log;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(RemoteClientControls, AdoptTierZeroOverSharedSegmentsDiesAtTheHandshakeBeforeAnyMapPersistent) {
|
||||
ExpectDeathAtTheSharedSegmentHandshake(0u);
|
||||
}
|
||||
|
||||
TEST(RemoteClientControls, AdoptTierOneOverSharedSegmentsDiesAtTheHandshakeBeforeAnyMapPersistent) {
|
||||
ExpectDeathAtTheSharedSegmentHandshake(1u);
|
||||
}
|
||||
|
||||
TEST(RemoteClientControls, AnIdenticalRepeatedMakeCurrentRepublishesNothing) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
|
||||
@@ -184,7 +184,7 @@ FAMILY_ROW = re.compile(r"^\s*X\(([A-Za-z][A-Za-z0-9]*)\s*,", re.MULTILINE)
|
||||
REFUSE_ENUM = re.compile(r"enum\s+RefuseCode\s*:[^{]*\{(.*?)\}", re.DOTALL)
|
||||
ENUM_VALUE = re.compile(r"^\s*([A-Za-z][A-Za-z0-9]*)\s*=", re.MULTILINE)
|
||||
|
||||
# Two client-side declines that borrow the Refuse vocabulary for a refusal that never becomes a
|
||||
# Local declines that borrow the Refuse vocabulary for a refusal that never becomes a
|
||||
# wire Refuse frame, so no peer ever reads the word and no RefuseCode can carry it. Listed rather
|
||||
# than renamed: inventing enumerators for them would move `wireFingerprint`, which is a protocol
|
||||
# change and not a wave-0 instrument's to make. Recorded for the integrator.
|
||||
@@ -195,6 +195,9 @@ LOCAL_REFUSAL_WORDS = {
|
||||
"InitialCapsSnapshot":
|
||||
"ClientSession.cpp:1292/:1302 - the CLIENT refusing a Welcome that carried no initial "
|
||||
"capabilities; the handshake has already been answered, so the decline is local",
|
||||
"AdoptTierOnStream":
|
||||
"Transport/AdoptTier.cpp (P11 A1) - either side refusing its OWN MOBILEGL_IPC_ADOPT_TIER=0/1 "
|
||||
"on a stream data plane and running T2; the session goes on, so no Refuse frame is sent",
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user