[Feat] (Remote): give inproc T0 the server's own store and never an AHB

This commit is contained in:
2026-10-10 22:32:19 -04:00
parent 5f9c3d6947
commit 745f59bc3f
18 changed files with 302 additions and 30 deletions
@@ -164,6 +164,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return VkBufferManager::SelfTestWireImport(why, whyBytes);
},
#endif
// T0 in one process: the client gets the store's own mapping, as monolith's
// donation does; the client seeds it from its shadow once this returns.
.DonateInProcess = [](MG_Pipe::MGPipeHandle res, Uint64 size) -> void* {
auto* m = WireManager("donate");
return m != nullptr ? m->DonateWireBuffer(res, size, nullptr) : nullptr;
},
};
} // namespace
+31 -13
View File
@@ -2574,8 +2574,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
list(APPEND MGL_SPLIT_ARM_TEST_LISTS "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}")
math(EXPR MGL_SPLIT_ARM_INDEX "${MGL_SPLIT_ARM_INDEX} + 1")
# P11 B2 (CONTRACT-P11 B2): THE SHARED-SEGMENT ARMS WITH MOBILEGL_IPC_ADOPT_TIER=0, which A1 made
# die at the handshake and B2 turned into an ASK. A host has no AHardwareBuffer, so the server
# refuses T0 by name at its first native bind, publishes no kCapAdoptT0, and the client falls
# die at the handshake and B2 turned into an ASK. A host has no AHardwareBuffer (and Espryt cannot
# hand an in-process client its stores), so the server refuses T0 by name at its first native bind, publishes no kCapAdoptT0, and the client falls
# back to T2 with one named line at its first map_persistent: every case green, the arena
# emulated (SplitLogPaths declares it), ASessionThatCannotRunT0FallsBackToT2OnceByName reading
# the two lines. `.AdoptT0Disallowed.` is the same on the spawn arm with the server's allow switch
@@ -2583,6 +2583,13 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
foreach(mglT0Arm IN ITEMS Split Spawn)
string(TOUPPER "${mglT0Arm}" mglT0ArmUpper)
string(TOLOWER "${mglT0Arm}" mglT0ArmLower)
# Split is one process: Espryt cannot hand its client its stores, and an in-process session
# never takes the AHardwareBuffer arm, so the refusal names that rather than the platform.
if(mglT0Arm STREQUAL "Split")
set(mglT0Fallback "in process")
else()
set(mglT0Fallback "no AHardwareBuffer")
endif()
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectGLES.${mglT0Arm}.AdoptTier0."
TEST_LIST "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}"
@@ -2591,7 +2598,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
PROPERTIES
LABELS "integration-gpu\;integration-${mglT0ArmLower}"
TIMEOUT ${MGL_ITEST_TIMEOUT}
ENVIRONMENT "${MGL_ITEST_GLES_${mglT0ArmUpper}_ENVIRONMENT}\;MOBILEGL_IPC_ADOPT_TIER=0\;MGITEST_ADOPT_T0_FALLBACK=no AHardwareBuffer"
ENVIRONMENT "${MGL_ITEST_GLES_${mglT0ArmUpper}_ENVIRONMENT}\;MOBILEGL_IPC_ADOPT_TIER=0\;MGITEST_ADOPT_T0_FALLBACK=${mglT0Fallback}"
)
list(APPEND MGL_SPLIT_ARM_TEST_LISTS "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}")
math(EXPR MGL_SPLIT_ARM_INDEX "${MGL_SPLIT_ARM_INDEX} + 1")
@@ -4090,6 +4097,14 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
mgl_itest_magma_arm_shape(${mglItestArm})
mgl_itest_join_environment(mglItestMagmaBandEnvironment
${MGL_ITEST_MAGMA_BAND_FLAVOUR_ENV} ${mglItestArmTransportEnv})
# Where the >= 16 MiB arena lands on this arm (P11 A2). Split is one process: T0 (the default
# tier) is granted in process and the arena is the store Magma hands its client - adopted.
# Across processes a host has no shareable memory, so the session runs T2 - emulated.
if("${mglItestArm}" STREQUAL "Split")
set(mglItestArenaArm adopted)
else()
set(mglItestArenaArm emulated)
endif()
foreach(bandCase RenderbufferReadOfExactlyTwoMiBIsBandedNotRefused
TwoMiBPlusOneRowIsBanded
AReadLargerThanTheWholeReplySegmentIsBanded
@@ -4137,9 +4152,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p10-fence${mglItestArmLogTag}-${fenceCase}.log")
endforeach()
# P11 A2 (MG_Remote/CONTRACT-P11.md §2): the >= 16 MiB arena on Magma's three arms, in the
# same flavour. Declared emulated: every split acquisition declines (the wire resource
# table's MapPersistent returns null, VkBufferManager.cpp), so the tier case asserts T2
# on every arm; DirectVulkan.ArenaArm. is its monolith (adopted) half.
# same flavour, declared per arm (mglItestArenaArm above); DirectVulkan.ArenaArm. is its
# monolith (adopted) half.
foreach(arenaCase IN LISTS MGL_ITEST_ARENA_CASES)
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectVulkan.${mglItestArm}.Arena."
@@ -4147,7 +4161,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
DISCOVERY_TIMEOUT 30
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
${mglItestArmProperties}
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MGITEST_PERSISTENT_MAP_ARM=emulated\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-arena${mglItestArmLogTag}-${arenaCase}.log")
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MGITEST_PERSISTENT_MAP_ARM=${mglItestArenaArm}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-arena${mglItestArmLogTag}-${arenaCase}.log")
endforeach()
# P11 B2 addendum: DeletedBoundBufferScenario on Magma's three arms, same flavour (the
# Espryt arms come from mgl_itest_register_split_arms). Before the frontend fix Magma did
@@ -4167,7 +4181,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
endforeach()
# ...and the one-process arena pair (the Espryt half is beside mgl_itest_register_split_arms).
string(REPLACE "\\;" ";" mglItestPairEnv "${mglItestMagmaBandEnvironment}")
list(APPEND mglItestPairEnv "MGITEST_PERSISTENT_MAP_ARM=emulated"
list(APPEND mglItestPairEnv "MGITEST_PERSISTENT_MAP_ARM=${mglItestArenaArm}"
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-arena-pair${mglItestArmLogTag}.log")
string(REPLACE "\\;" ";" mglItestPairLabels "${mglItestMagmaLabels}")
set(mglItestPairName "DirectVulkan.${mglItestArm}.Arena.LargeArenaAdoptionScenario.GpuWriteThenTheTierCaseInOneProcess")
@@ -4181,13 +4195,17 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
${mglItestArmProperties})
# P11 A1 + B2 (CONTRACT-P11 §1, B2): MOBILEGL_IPC_ADOPT_TIER=0 on every arm, one case set.
# On tcp a stream refuses the tier by name at the client's handshake (A1, the stream case
# reads the line); over shared segments (split, spawn) the knob ASKS T0, a host has no
# AHardwareBuffer, and the session falls back to T2 by name (B2, the fallback case reads
# both lines). Register only the assertion applicable to each transport; the
# parity check names these two transport-specific exceptions explicitly.
# reads the line); on spawn the knob ASKS T0, a host has no AHardwareBuffer, and the session
# falls back to T2 by name (B2, the fallback case reads both lines); on split the client is
# the server's process and T0 is granted in process (the tier case reads `adopted`).
# Register only the assertion applicable to each transport; the parity check names these
# transport-specific exceptions explicitly.
if(mglItestArm STREQUAL "Tcp")
set(mglItestAdoptTier0Env "MGITEST_ADOPT_TIER_REFUSAL=T0")
set(mglItestAdoptTier0Case AStreamSessionRefusesTheAdoptTierOnceByName)
elseif(mglItestArm STREQUAL "Split")
set(mglItestAdoptTier0Env "MOBILEGL_IPC_ALLOW_ADOPT_T0=1")
set(mglItestAdoptTier0Case)
else()
set(mglItestAdoptTier0Env "MGITEST_ADOPT_T0_FALLBACK=no AHardwareBuffer")
set(mglItestAdoptTier0Case ASessionThatCannotRunT0FallsBackToT2OnceByName)
@@ -4199,7 +4217,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
DISCOVERY_TIMEOUT 30
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
${mglItestArmProperties}
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MOBILEGL_IPC_ADOPT_TIER=0\;${mglItestAdoptTier0Env}\;MGITEST_PERSISTENT_MAP_ARM=emulated\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-adopt-tier0${mglItestArmLogTag}-${arenaCase}.log")
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MOBILEGL_IPC_ADOPT_TIER=0\;${mglItestAdoptTier0Env}\;MGITEST_PERSISTENT_MAP_ARM=${mglItestArenaArm}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-adopt-tier0${mglItestArmLogTag}-${arenaCase}.log")
endforeach()
endforeach()
endif()
@@ -224,7 +224,9 @@ def specialized_required():
}
for backend in ("DirectGLES", "DirectVulkan"):
names.add(f"{backend}.Tcp.AdoptTier0.LargeArenaAdoptionScenario.AStreamSessionRefusesTheAdoptTierOnceByName")
for arm in ("Split", "Spawn"):
# Magma's split arm is granted T0 in process (its tier case reads `adopted`), so only Espryt,
# which cannot hand an in-process client its stores, falls back there.
for arm in (("Split", "Spawn") if backend == "DirectGLES" else ("Spawn",)):
names.add(f"{backend}.{arm}.AdoptTier0.LargeArenaAdoptionScenario.ASessionThatCannotRunT0FallsBackToT2OnceByName")
for arm in ("Split", "Spawn"):
for case in ("Rgb16fDrawnBaseWithARefusedReadbackDeclinesByName",
@@ -100,7 +100,8 @@ void main() { word = 0xC0FFEEu; }
// which also set MOBILEGL_IPC_ADOPT_TIER=0; a harness marker, never read by the library.
constexpr const char* kAdoptTierRefusalMarker = "MGITEST_ADOPT_TIER_REFUSAL";
// P11 B2: set by the shared-segment `.AdoptTier0.` / `.AdoptT0Disallowed.` entries to the reason word the
// server gives for not granting T0 ("no AHardwareBuffer" on a host, "disallowed" with its allow switch off).
// server gives for not granting T0 ("no AHardwareBuffer" on a host, "in process" where the backend cannot hand an
// in-process client its stores, "disallowed" with its allow switch off).
constexpr const char* kAdoptT0FallbackMarker = "MGITEST_ADOPT_T0_FALLBACK";
// Draws issued against the arena inside the counted window. One definition, many draws:
// "one per definition" (1) and "one per draw" (kDrawsInTheWindow) have to be different
+5
View File
@@ -157,6 +157,11 @@ namespace MobileGL::MG_Pipe {
// CapsSnapshot Accept sends (no context exists yet); set in the one the first native
// bind republishes. The client adopts through T0 only while it reads this bit.
kCapAdoptT0 = 1ull << 11,
// T0 IN ONE PROCESS. Set with kCapAdoptT0 when the client is this process (inproc) and the
// backend can donate a store: map_persistent's OK answer carries the address of the
// server's own mapped store, and the client allocates no AHardwareBuffer - the store is
// the one memory both sides already share, as monolith's donation is.
kCapAdoptInProcess = 1ull << 12,
};
// Readiness is supplied independently by each backend's integration gate.
+17
View File
@@ -2860,6 +2860,23 @@ namespace MobileGL::MG_Pipe {
if (g_resourceOps == nullptr || g_resourceOps->ImportExternal == nullptr) return false;
return g_resourceOps->ImportExternal(handle.Handle, ahb, size);
}
void* MGPipeApplyDonateInProcess(const MGPHandleOnly& handle) {
if (static_cast<MGPipeKind>(handle.Kind) != MGPipeKind::Buffer) {
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
" donate_in_process {slot=%u, gen=%u}: a T0 store is the buffer family's "
"and the handle names another kind (%u)",
handle.Handle.Slot, handle.Handle.Gen, handle.Kind);
return nullptr;
}
MGPipeResourceRecord* record = ResolveResource("donate_in_process", handle.Handle);
if (record == nullptr || record->Desc.Width == 0) return nullptr;
// adopt_external's pin, for adopt_external's reason: the client writes the donated store
// directly, as a monolith client writes its adopted map.
PinNoLiveHostWrites(*record, handle.Handle, "donate_in_process");
if (g_resourceOps == nullptr || g_resourceOps->DonateInProcess == nullptr) return nullptr;
return g_resourceOps->DonateInProcess(handle.Handle, record->Desc.Width);
}
#endif
// ================================================================================
+7
View File
@@ -142,6 +142,10 @@ namespace MobileGL::MG_Pipe {
// pattern of its own, and read that back through the held pointer (and through the
// backend's own host map of the import). False with the reason in `why`.
Bool (*SelfTestExternal)(char* why, Uint64 whyBytes);
// T0 for a client in this process: the store's own host-visible coherent mapping (the
// pointer a monolith map_persistent donates), handed to the client as its persistent map.
// The store takes T0's semantics from here on. Null = not donated: the store runs T2.
void* (*DonateInProcess)(MGPipeHandle res, Uint64 size);
};
// Install / read the table. A null argument uninstalls, which is what a backend does at
@@ -1541,6 +1545,9 @@ namespace MobileGL::MG_Pipe {
// without the member, declines (false); the codec answers DECLINED and the store runs T2.
// MapPersistentRoundtrips is not moved: under split the server never counted map_persistent.
Bool MGPipeApplyAdoptExternal(const MGPHandleOnly& handle, void* ahb, Uint64 size);
// T0 in one process: buffer `handle`'s store, donated through the backend's DonateInProcess.
// Null declines (not a live buffer, or a backend without the member); the store runs T2.
void* MGPipeApplyDonateInProcess(const MGPHandleOnly& handle);
// ---------------------------------------------------------------------------------
// P3a: the five vertex-input entry points (D-G, D-H, D-I)
+4 -1
View File
@@ -75,7 +75,10 @@ namespace MobileGL::MG_Pipe {
// handled it and `status` is the answer (OK: the client's AHardwareBuffer is now the
// store; DECLINED: the store runs T2); false = not a T0 session, and the codec gives
// today's constant decline. `seq` is the record's, which is what the store's Offer names.
virtual Bool OnMapPersistent(const MGPHandleOnly&, Uint64 /*seq*/, Int32& /*status*/) {
// `inProcessBase`: a client in this process gets the donated store's address in the OK
// answer (kCapAdoptInProcess); 0 = the answer carries no bytes (the AHardwareBuffer arm).
virtual Bool OnMapPersistent(const MGPHandleOnly&, Uint64 /*seq*/, Int32& /*status*/,
Uint64& /*inProcessBase*/) {
return false;
}
virtual Bool OnFenceCreate(const MGPHandleOnly&) { return false; }
+65 -4
View File
@@ -44,6 +44,7 @@
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <mutex>
@@ -3011,11 +3012,26 @@ namespace MobileGL::MG_Remote::Client {
Bool ClientSession::AdoptT0() {
if (!m_adoptT0Asked) return false;
if (m_adoptT0Decision >= 0) return m_adoptT0Decision == 1;
if (Caps().HasCap(MG_Pipe::kCapAdoptT0)) {
const Bool peerIsThisProcess = m_transport != nullptr &&
Transport::AdoptT0::PeerIsThisProcess(m_transport->Role() == Transport::TransportRole::InProcess);
if (Caps().HasCap(MG_Pipe::kCapAdoptT0) && peerIsThisProcess &&
!Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) {
// A server in this process offered the AHardwareBuffer arm. The stores are this
// process's memory already; an in-process session never allocates one.
m_adoptT0Decision = 0;
++m_t0Fallbacks;
MGLOG_W("MG_Remote client: Refuse{AdoptT0Unavailable, \"in process\"} - the server is this process "
"and published kCapAdoptT0 without kCapAdoptInProcess; the session runs T2");
} else if (Caps().HasCap(MG_Pipe::kCapAdoptT0)) {
m_adoptT0Decision = 1;
MGLOG_I("MG_Remote client: T0 - the server published kCapAdoptT0 (its POST self-test of the "
"sustained-lock pattern passed), so this session adopts persistent stores as "
"AHardwareBuffers the server imports");
if (Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) {
MGLOG_I("MG_Remote client: T0 in process - the server is this process, so this session's "
"persistent maps are the server's own store mappings (no AHardwareBuffer)");
} else {
MGLOG_I("MG_Remote client: T0 - the server published kCapAdoptT0 (its POST self-test of the "
"sustained-lock pattern passed), so this session adopts persistent stores as "
"AHardwareBuffers the server imports");
}
} else {
m_adoptT0Decision = 0;
++m_t0Fallbacks;
@@ -3036,6 +3052,7 @@ namespace MobileGL::MG_Remote::Client {
void* ClientSession::AdoptPersistentT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 size,
const void* seed) {
using namespace Transport::AdoptT0;
if (Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) return AdoptPersistentInProcess(handle, size, seed);
// Everything that can fail runs BEFORE the record is encoded, so the record never waits on
// a step that has not happened: either the Offer carries the handle or it says it does not.
HeldStore store;
@@ -3141,6 +3158,50 @@ namespace MobileGL::MG_Remote::Client {
return nullptr;
}
// T0 IN ONE PROCESS (kCapAdoptInProcess): the OK answer carries the address of the server's own
// mapping of the store, which becomes this buffer's persistent map - the memory both sides
// already share, as monolith's donation is. The server has drained the GPU's use of the store
// before answering and applies nothing of this client's until this call returns, so the
// shadow is copied in here, by the side that owns it.
void* ClientSession::AdoptPersistentInProcess(const MG_Pipe::MGPHandleOnly& handle, Uint64 size,
const void* seed) {
Uint64 base = 0;
Uint64 returned = 0;
Int32 status = Wire::ReplySink::kStatusError;
EmitAndWait(MG_Pipe::MGPWireOp::MapPersistent, &handle, sizeof(handle), nullptr, 0, &base, sizeof(base),
&status, &returned);
if (status == Wire::ReplySink::kStatusError) {
SessionFail(MGFatalFamily::ReplyError,
"MGPipe: Fatal{ReplyError, \"map_persistent\"} - the in-process T0 row answered ERROR, "
"which is not an acceptance answer");
}
if (status != Wire::ReplySink::kStatusOk) {
++m_t0Declined;
return nullptr;
}
if (returned != sizeof(base) || base == 0) {
SessionFail(MGFatalFamily::UnexpectedMapAccept,
"MGPipe: Fatal{UnexpectedMapAccept, \"map_persistent\"} - the server accepted an "
"in-process T0 store without the store's address");
}
void* ptr = reinterpret_cast<void*>(static_cast<std::uintptr_t>(base));
if (seed != nullptr) std::memcpy(ptr, seed, static_cast<SizeT>(size));
++m_t0Imported;
m_t0Bytes += size;
// No AHardwareBuffer to hold: the entry only marks the handle T0 (its release is a no-op).
const Uint64 key = T0Key(handle.Handle);
if (auto it = m_t0Stores.find(key); it != m_t0Stores.end()) {
Transport::AdoptT0::ReleaseHeld(it->second);
++m_t0Released;
}
m_t0Stores[key] = Transport::AdoptT0::HeldStore{nullptr, ptr, size};
MGLOG_D("MG_Remote client: T0 store {slot=%u, gen=%u} donated in process - %llu bytes; session total "
"%llu stores / %llu bytes",
handle.Handle.Slot, handle.Handle.Gen, static_cast<unsigned long long>(size),
static_cast<unsigned long long>(m_t0Imported), static_cast<unsigned long long>(m_t0Bytes));
return ptr;
}
Bool ClientSession::EmitSharedImage(const MG_Pipe::MGPSharedImageOp& op, int sendFd,
MG_Pipe::MGPSharedImageReply* reply, int* receivedFd) {
if (receivedFd != nullptr) *receivedFd = -1;
@@ -293,6 +293,7 @@ namespace MobileGL::MG_Remote::Client {
// server's inbox relies on), and wait for the answer. The held pointer on OK - the caller
// adopts it as the store's mapping - or null on DECLINED (the store stays T2).
void* AdoptPersistentT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, const void* seed);
void* AdoptPersistentInProcess(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, const void* seed);
// shared_image (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md): emits `op` and waits
// for its answer. `sendFd` >= 0 (an Import) is queued on the aux socket for the record's
// seq BEFORE the record is published; `receivedFd` non-null (an Allocate) takes the
+3 -2
View File
@@ -116,9 +116,10 @@ namespace MobileGL::MG_Remote::Server {
return it->second;
}
Bool ServerVerbSink::OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status) {
Bool ServerVerbSink::OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
Uint64& inProcessBase) {
ServerSession* session = ServerSession::Active();
return session != nullptr && session->AdoptStoreT0(handle, seq, status);
return session != nullptr && session->AdoptStoreT0(handle, seq, status, inProcessBase);
}
Bool ServerVerbSink::OnFenceCreate(const MG_Pipe::MGPHandleOnly& desc) {
+2 -1
View File
@@ -156,7 +156,8 @@ namespace MobileGL::MG_Remote::Server {
// queries, present, the T0 map, the control records and shared images.
// P11 B2: T0's map_persistent - ServerSession::AdoptStoreT0 (it owns the aux socket).
Bool OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status) override;
Bool OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
Uint64& inProcessBase) override;
Bool OnFenceCreate(const MG_Pipe::MGPHandleOnly&) override;
Bool OnFenceDestroy(const MG_Pipe::MGPHandleOnly&) override;
Bool OnFenceStatus(const MG_Pipe::MGPHandleOnly&, Uint32&) override;
+49 -4
View File
@@ -30,6 +30,7 @@
#include <atomic>
#include <string>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <thread>
@@ -726,6 +727,7 @@ namespace MobileGL::MG_Remote::Server {
m_adoptT0Quiet = m_adoptAsk == Transport::kAdoptAskT0Default;
m_adoptT0Settled = false;
m_adoptT0 = false;
m_adoptT0InProcess = false;
m_t0Stores = m_t0Bytes = m_t0Declined = m_t0Refusals = 0;
m_adoptInbox.Clear();
m_adoptInbox.Attach(&transport);
@@ -966,6 +968,7 @@ namespace MobileGL::MG_Remote::Server {
// P11 B2: this session's T0 grant, never part of the process-wide m_capBits - it is a
// statement about THIS client's ask and this server's self-test.
if (m_adoptT0) callMask |= static_cast<Uint64>(MG_Pipe::kCapAdoptT0);
if (m_adoptT0InProcess) callMask |= static_cast<Uint64>(MG_Pipe::kCapAdoptInProcess);
const auto timerSupported = m_backend->GetBackendFunctions().GL.IsTimerQuerySupported;
if ((callMask & MG_Pipe::kCapTimerQuery) && (!timerSupported || !timerSupported()))
callMask &= ~static_cast<Uint64>(MG_Pipe::kCapTimerQuery);
@@ -1113,7 +1116,7 @@ namespace MobileGL::MG_Remote::Server {
m_adoptInbox.Clear();
m_adoptInbox.Attach(nullptr);
m_sharedImages.Clear();
m_adoptT0Asked = m_adoptT0Settled = m_adoptT0 = m_adoptT0Quiet = false;
m_adoptT0Asked = m_adoptT0Settled = m_adoptT0 = m_adoptT0Quiet = m_adoptT0InProcess = false;
m_adoptAsk = 2;
m_consumer.Detach();
*m_commands = Transport::RingConsumer();
@@ -1153,7 +1156,27 @@ namespace MobileGL::MG_Remote::Server {
const char* backendName = m_backend == nullptr ? "server"
: m_backend->GetBackendType() == BackendType::DirectVulkan ? "Magma" : "Espryt";
std::string why;
if (MG_Config::Ipc.AllowAdoptT0 == 0) {
// A CLIENT IN THIS PROCESS SHARES THE SERVER'S MEMORY ALREADY: its persistent maps are the
// stores' own mappings, donated as monolith donates them, and no AHardwareBuffer, import
// or POST is involved. A backend that cannot donate leaves the session on T2 - an
// in-process session never takes the AHardwareBuffer arm below.
const Bool inProcess = m_transport != nullptr &&
Transport::AdoptT0::PeerIsThisProcess(m_transport->Role() == Transport::TransportRole::InProcess);
if (inProcess) {
const MG_Pipe::MGPipeResourceOps* ops = MG_Pipe::MGPipeGetResourceOps();
if (MG_Config::Ipc.AllowAdoptT0 == 0) {
why = "this server's MOBILEGL_IPC_ALLOW_ADOPT_T0=0 disallows it";
} else if (ops == nullptr || ops->DonateInProcess == nullptr) {
why = std::string("the ") + backendName + " backend cannot hand its stores to a client in this process";
} else {
m_adoptT0 = true;
m_adoptT0InProcess = true;
MGLOG_I("MG_Remote server: T0 granted in process - the client is this process, so each "
"map_persistent hands it the %s store's own mapping (no AHardwareBuffer)",
backendName);
return;
}
} else if (MG_Config::Ipc.AllowAdoptT0 == 0) {
why = "this server's MOBILEGL_IPC_ALLOW_ADOPT_T0=0 disallows it";
} else if (!Transport::AdoptT0::PlatformHasAhb()) {
why = "no AHardwareBuffer on this platform";
@@ -1182,7 +1205,9 @@ namespace MobileGL::MG_Remote::Server {
if (!why.empty()) {
++m_t0Refusals;
const char* word = MG_Config::Ipc.AllowAdoptT0 == 0 ? "disallowed"
: !Transport::AdoptT0::PlatformHasAhb() ? "no AHardwareBuffer" : "POST";
: inProcess ? "in process"
: !Transport::AdoptT0::PlatformHasAhb() ? "no AHardwareBuffer"
: "POST";
if (m_adoptT0Quiet) {
// THE DEFAULT ASK (MOBILEGL_IPC_ADOPT_TIER unset) FALLS BACK QUIETLY: every host
// session would otherwise carry this line. Counted (T0Refusals, the totals line).
@@ -1202,9 +1227,29 @@ namespace MobileGL::MG_Remote::Server {
backendName);
}
Bool ServerSession::AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status) {
Bool ServerSession::AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
Uint64& inProcessBase) {
if (!m_adoptT0) return false;
status = Wire::ReplySink::kStatusDeclined;
inProcessBase = 0;
if (m_adoptT0InProcess) {
void* base = MG_Pipe::MGPipeApplyDonateInProcess(handle);
if (base == nullptr) {
++m_t0Declined;
MGLOG_W("MG_Remote server: T0 map_persistent {slot=%u, gen=%u} (record %llu) DECLINED - the "
"backend did not donate the store (its log names why); the store runs T2",
handle.Handle.Slot, handle.Handle.Gen, static_cast<unsigned long long>(seq));
return true;
}
inProcessBase = static_cast<Uint64>(reinterpret_cast<std::uintptr_t>(base));
++m_t0Stores;
status = Wire::ReplySink::kStatusOk;
MGLOG_D("MG_Remote server: T0 donated store {slot=%u, gen=%u} in process (record %llu); session "
"total %llu stores",
handle.Handle.Slot, handle.Handle.Gen, static_cast<unsigned long long>(seq),
static_cast<unsigned long long>(m_t0Stores));
return true;
}
int hop = -1;
Transport::AdoptT0::Offer offer{};
std::string why;
+7 -1
View File
@@ -320,7 +320,12 @@ namespace MobileGL::MG_Remote::Server {
// hang), read the AHB off its hop, and import it as `handle`'s store. False = this session is
// not T0 (the codec's T2 decline answers); true = handled and `status` is the answer (OK, or
// DECLINED with a named line).
Bool AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status);
// A client in this process (inproc) takes no AHardwareBuffer: the backend donates the
// store's own mapping and `inProcessBase` carries its address in the OK answer.
Bool AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
Uint64& inProcessBase);
// T0 granted for a client in this process: map_persistent donates (kCapAdoptInProcess).
Bool AdoptT0InProcess() const { return m_adoptT0InProcess; }
// The arm's proof for this session (B2): stores imported and their bytes, declined.
Uint64 T0StoresImported() const { return m_t0Stores; }
Uint64 T0BytesImported() const { return m_t0Bytes; }
@@ -426,6 +431,7 @@ namespace MobileGL::MG_Remote::Server {
Uint8 m_adoptAsk = 2;
Bool m_adoptT0Settled = false;
Bool m_adoptT0 = false;
Bool m_adoptT0InProcess = false;
AdoptInbox m_adoptInbox;
SharedImages::SessionHolder m_sharedImages;
Uint64 m_t0Stores = 0;
+12
View File
@@ -184,6 +184,18 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 {
return test != nullptr ? test->HasAhb() : RealHasAhb();
}
namespace {
std::atomic<bool> g_peerInAnotherProcessForTest{false};
} // namespace
bool PeerIsThisProcess(bool inProcessTransport) {
return inProcessTransport && !g_peerInAnotherProcessForTest.load(std::memory_order_acquire);
}
void SetPeerInAnotherProcessForTest(bool asIfCrossProcess) {
g_peerInAnotherProcessForTest.store(asIfCrossProcess, std::memory_order_release);
}
bool AllocateHeld(std::uint64_t size, const void* seed, HeldStore& out, std::string& why) {
const PlatformForTest* test = g_testPlatform.load(std::memory_order_acquire);
return test != nullptr ? test->AllocateHeld(size, seed, out, why) : RealAllocateHeld(size, seed, out, why);
+8
View File
@@ -56,6 +56,14 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 {
// True where AHardwareBuffer exists (Android). Everywhere else T0 is never granted.
bool PlatformHasAhb();
// True when a session's peer is this very process (an in-process transport). Such a session
// never takes this header's arm: its stores are this process's memory already, so T0 there is
// the backend handing over its own store (kCapAdoptInProcess) or nothing (T2).
bool PeerIsThisProcess(bool inProcessTransport);
// Test seam: the protocol tests run over an in-process transport and stand for a peer in
// another process while this is set.
void SetPeerInAnotherProcessForTest(bool asIfCrossProcess);
// ---- client half ----------------------------------------------------------------------
// A store the client allocated. `ahb` is an AHardwareBuffer*, `ptr` its CPU lock, held until
+4 -2
View File
@@ -1852,9 +1852,11 @@ namespace MobileGL::MG_Remote::Wire {
// it (Server/AdoptInbox.h says why that wait cannot hang the apply thread); it answers
// for itself or says "not T0", and everything below is the T2 arm, unchanged.
Int32 t0Status = ReplySink::kStatusDeclined;
Uint64 inProcessBase = 0;
if (m_verbs != nullptr &&
m_verbs->OnMapPersistent(*static_cast<const MGPHandleOnly*>(payload), seq, t0Status)) {
PostReply(op, seq, t0Status, nullptr, 0);
m_verbs->OnMapPersistent(*static_cast<const MGPHandleOnly*>(payload), seq, t0Status, inProcessBase)) {
if (inProcessBase != 0) PostReply(op, seq, t0Status, &inProcessBase, sizeof(inProcessBase));
else PostReply(op, seq, t0Status, nullptr, 0);
return true;
}
}
@@ -38,7 +38,9 @@ void FakeDrop(FakeAhb* f) {
}
}
bool FakeHasAhb() { return true; }
Uint64 g_fakeAllocations = 0;
bool FakeAllocate(std::uint64_t size, const void* seed, T0::HeldStore& out, std::string& why) {
++g_fakeAllocations;
auto* f = new FakeAhb;
f->fd = static_cast<int>(::syscall(SYS_memfd_create, "t0-fake", 1u /*MFD_CLOEXEC*/));
if (f->fd < 0 || ::ftruncate(f->fd, static_cast<off_t>(size)) != 0) {
@@ -144,6 +146,20 @@ MGPipeResourceOps MakeFakeOps() {
}
MGPipeResourceOps g_fakeOps = MakeFakeOps();
// ---- the stand-in donor: a backend that hands an in-process client its store's own mapping ---
alignas(64) Uint8 g_donatedStore[kT0Bytes];
Uint64 g_donations = 0;
void* FakeDonateInProcess(MGPipeHandle res, Uint64 size) {
++g_donations;
return res.Slot == 5 && size == kT0Bytes ? g_donatedStore : nullptr;
}
MGPipeResourceOps MakeDonatingOps() {
MGPipeResourceOps ops = MakeFakeOps();
ops.DonateInProcess = &FakeDonateInProcess;
return ops;
}
MGPipeResourceOps g_donatingOps = MakeDonatingOps();
// What the server's first native bind does for T0, on the apply thread like the real one: the
// grant (and its POST), then the caps that bind republishes - which the client's make-current
// adopts, as here.
@@ -184,6 +200,7 @@ size_t CountOf(const std::string& log, const std::string& needle) {
// The grant with the stand-ins, the store, and the map_persistent: returns the adopted pointer.
void* AdoptOneStore(Uint8 salt, std::vector<Uint8>& seed) {
MG_Config::Ipc.AdoptTier = 0;
T0::SetPeerInAnotherProcessForTest(true);
T0::SetPlatformForTest(&kFakePlatform);
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps);
StartControlSession();
@@ -223,6 +240,7 @@ TEST(RemoteClientAdoptT0, KnobZeroOverSharedSegmentsAsksT0AndFallsBackToT2ByName
TEST(RemoteClientAdoptT0, KnobTwoIsTodaysT2WithNoT0Line) {
const auto child = RunInChild([] {
MG_Config::Ipc.AdoptTier = 2;
T0::SetPeerInAnotherProcessForTest(true);
T0::SetPlatformForTest(&kFakePlatform);
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps);
StartControlSession();
@@ -251,6 +269,7 @@ TEST(RemoteClientAdoptT0, KnobTwoIsTodaysT2WithNoT0Line) {
TEST(RemoteClientAdoptT0, AnUnsetKnobFallsBackToT2QuietlyAndCountsIt) {
const auto child = RunInChild([] {
MG_Config::Ipc.AdoptTier = MG_Config::kAdoptTierUnset;
T0::SetPeerInAnotherProcessForTest(true);
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps); // a backend WITH the members: the platform decides
StartControlSession();
if (!Srv::ServerSessionInstance().AdoptT0Asked()) ::_exit(210); // the Hello asked T0 by default
@@ -292,6 +311,7 @@ TEST(RemoteClientAdoptT0, KnobOneIsRefusedByNameAndAsksT2) {
TEST(RemoteClientAdoptT0, NoAhardwareBufferOnThisPlatformIsT2AndTwoNamedLines) {
const auto child = RunInChild([] {
MG_Config::Ipc.AdoptTier = 0;
T0::SetPeerInAnotherProcessForTest(true);
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps); // a backend WITH the members: the platform decides
StartControlSession();
GrantAtFirstBind();
@@ -383,4 +403,60 @@ TEST(RemoteClientAdoptT0, AnOfferThatNeverArrivesIsANamedDeclineNotAHang) {
EXPECT_EQ(CountOf(child.Log, "Refuse{AdoptT0NoStore, \"record"), 1u) << child.Log;
EXPECT_EQ(child.Log.find("Fatal{"), std::string::npos) << child.Log;
}
// T0 IN ONE PROCESS: the client is this process, so a backend that can donate hands it the store's
// own mapping - no AHardwareBuffer is allocated, no Offer, no import, no POST - and the client's
// pointer is the server's store, seeded with the shadow.
// RED-ONCE (host): grant the AHardwareBuffer arm for an in-process client (drop the in-process
// branch of ServerSession::SettleAdoptT0AtBind) -> the stand-in platform allocates, 223.
TEST(RemoteClientAdoptT0, AnInProcessClientGetsTheServersStoreAndNoAhardwareBuffer) {
const auto child = RunInChild([] {
MG_Config::Ipc.AdoptTier = 0;
T0::SetPlatformForTest(&kFakePlatform);
MG_Pipe::MGPipeSetResourceOps(&g_donatingOps);
StartControlSession();
GrantAtFirstBind();
if (!Srv::ServerSessionInstance().AdoptT0InProcess()) ::_exit(220);
const auto seed = Pattern(kT0Bytes, 0x77);
const MGPHandleOnly handle = MakeBufferStore(5, kT0Bytes);
void* adopted = MGPipeRouteMapPersistent(handle, kT0Bytes, seed.data());
if (adopted != g_donatedStore) ::_exit(221);
if (std::memcmp(g_donatedStore, seed.data(), kT0Bytes) != 0) ::_exit(222);
if (g_fakeAllocations != 0 || g_fakeImports.count != 0) ::_exit(223);
if (g_donations != 1) ::_exit(224);
if (Srv::ServerSessionInstance().T0StoresImported() != 1 || ClientSessionInstance().T0StoresImported() != 1)
::_exit(225);
ClientSessionInstance().Stop();
});
ExpectChildSuccess(child);
EXPECT_NE(child.Log.find("T0 granted in process"), std::string::npos) << child.Log;
EXPECT_EQ(child.Log.find("T0 POST"), std::string::npos) << child.Log;
EXPECT_EQ(child.Log.find("Fatal{"), std::string::npos) << child.Log;
}
// T0 IN ONE PROCESS NEVER TAKES THE AHardwareBuffer ARM: a backend that cannot hand an in-process
// client its store leaves the session on T2 by name - no allocation, no Offer, no import, no POST -
// even where the transfer arm would work (the stand-in platform and importer are installed).
// RED-ONCE (host): let SettleAdoptT0AtBind fall through to the AHardwareBuffer arm for an in-process
// client -> the stand-in POST passes and the session is granted T0, 230.
TEST(RemoteClientAdoptT0, AnInProcessClientWhoseBackendCannotDonateRunsT2AndNeverTheTransferArm) {
const auto child = RunInChild([] {
MG_Config::Ipc.AdoptTier = 0;
T0::SetPlatformForTest(&kFakePlatform);
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps); // the transfer arm's members, no DonateInProcess
StartControlSession();
GrantAtFirstBind();
if (Srv::ServerSessionInstance().AdoptT0()) ::_exit(230);
if (Srv::ServerSessionInstance().T0Refusals() != 1) ::_exit(231);
const auto seed = Pattern(kT0Bytes, 0x88);
const MGPHandleOnly handle = MakeBufferStore(5, kT0Bytes);
if (MGPipeRouteMapPersistent(handle, kT0Bytes, seed.data()) != nullptr) ::_exit(232);
if (g_fakeAllocations != 0 || g_fakeImports.count != 0) ::_exit(233);
if (ClientSessionInstance().AdoptT0()) ::_exit(234);
ClientSessionInstance().Stop();
});
ExpectChildSuccess(child);
EXPECT_EQ(CountOf(child.Log, "Refuse{AdoptT0Unavailable, \"in process\"}"), 1u) << child.Log;
EXPECT_EQ(child.Log.find("T0 POST"), std::string::npos) << child.Log;
EXPECT_EQ(child.Log.find("T0 granted"), std::string::npos) << child.Log;
EXPECT_EQ(child.Log.find("Fatal{"), std::string::npos) << child.Log;
}
#endif