From 7cbac80172b65eaa5ab459bdcf3257e5d5ff6d3c Mon Sep 17 00:00:00 2001 From: Tim Claridge Date: Sun, 5 Jul 2026 22:10:13 +1200 Subject: [PATCH] Phase 5(b) fix: InvocationMarshaller explicit streaming preserves _invdir The flatten-mode codegen gave each *Marshaller subclass a self-contained read/writeObject, which overrode InvocationMarshaller.readObject and thus skipped its side-effect of binding the transient _invdir from the stream's client -> null _invdir -> NPE on the first invocation request (hit by ART AND HotSpot clients). Fix: give InvocationMarshaller explicit read/writeObject (GenStreamUtil, deterministic order, byte-identical to the old reflective default) that keeps the _invdir binding; field-free subclasses inherit it. narya 1.22. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HhfHCXYp2ctWi76Z91y9Ut --- core/pom.xml | 2 +- .../presents/data/InvocationMarshaller.java | 26 +++++++++++++++++-- pom.xml | 2 +- tools/pom.xml | 2 +- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 261add1a4..16c4a19db 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ com.threerings narya-parent - 1.21 + 1.22 narya diff --git a/core/src/main/java/com/threerings/presents/data/InvocationMarshaller.java b/core/src/main/java/com/threerings/presents/data/InvocationMarshaller.java index f6147343f..720088b1d 100644 --- a/core/src/main/java/com/threerings/presents/data/InvocationMarshaller.java +++ b/core/src/main/java/com/threerings/presents/data/InvocationMarshaller.java @@ -7,7 +7,9 @@ package com.threerings.presents.data; import java.io.IOException; +import com.threerings.io.GenStreamUtil; import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; import com.threerings.io.Streamable; import com.threerings.presents.client.Client; @@ -244,17 +246,37 @@ public class InvocationMarshaller } /** - * A custom reader method for {@link Streamable}. + * A custom reader method for {@link Streamable}. Reads {@code _invOid}/{@code _invCode} + * explicitly (by name, in declaration order) rather than via {@code defaultReadObject()} so the + * field order is deterministic across ART and HotSpot (Epic A1 Phase 5, approach b) — byte + * identical to the old reflective read. Marshaller SUBCLASSES intentionally have NO generated + * read/writeObject (they are field-free) so they inherit this method, which also performs the + * side-effect the generated flatten-mode streamers would otherwise skip: binding {@link #_invdir} + * from the stream's client. That binding is the whole reason this is a hand-written custom + * reader; a subclass overriding it would leave {@code _invdir} null and NPE on the first request. */ public void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); + GenStreamUtil.readField(InvocationMarshaller.class, "_invOid", this, in); + GenStreamUtil.readField(InvocationMarshaller.class, "_invCode", this, in); if (in instanceof ClientObjectInputStream) { _invdir = ((ClientObjectInputStream)in).client.getInvocationDirector(); } } + /** + * A custom writer method for {@link Streamable}; mirrors {@link #readObject} (explicit, + * deterministic field order) so field-free marshaller subclasses inherit it. Byte identical to + * the previous reflective default write. + */ + public void writeObject (ObjectOutputStream out) + throws IOException + { + GenStreamUtil.writeField(InvocationMarshaller.class, "_invOid", this, out); + GenStreamUtil.writeField(InvocationMarshaller.class, "_invCode", this, out); + } + @Override public String toString () { diff --git a/pom.xml b/pom.xml index a027437b0..27edaf7cf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.threerings narya-parent pom - 1.21 + 1.22 Narya Parent Facilities for making networked multiplayer games. diff --git a/tools/pom.xml b/tools/pom.xml index b4f1e55ad..3eab7bfdd 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -4,7 +4,7 @@ com.threerings narya-parent - 1.21 + 1.22 narya-tools