Anton Vinogradov created IGNITE-28935:
-----------------------------------------

             Summary: Simplify marshaller selection for message object fields
                 Key: IGNITE-28935
                 URL: https://issues.apache.org/jira/browse/IGNITE-28935
             Project: Ignite
          Issue Type: Task
          Components: messaging
            Reporter: Anton Vinogradov
            Assignee: Anton Vinogradov
             Fix For: 2.19


h3. Goal

Make it obvious - and mechanically checkable - which marshaller every message 
field is serialized with, and cut the number of places where that decision is 
taken from 60 to zero.

h3. Why

Object fields of messages are turned into bytes either by the schema-less 
{{JdkMarshaller}} or by the schema-aware {{BinaryMarshaller}}. The marshaller 
is bound to the message *class*: the {{@UseBinaryMarshaller}} annotation plus 
the registration call in {{CoreMessagesProvider}}.

Measured on master over the generated companions:
* 297 messages are registered in core (58 binary, 239 jdk), 195 {{*Marshaller}} 
companions are generated;
* only 30 companions hold a {{Marshaller}} at all, and the choice affects 24 
classes;
* of 60 {{@UseBinaryMarshaller}} annotations only 13 mean anything - the other 
47 are noise.

The annotation is meant to say "the message carries user classes", but that is 
not what it does. User data travels with the jdk marshaller in 
{{TcpDiscoveryNode#attrs}}, {{LazyServiceConfigurationMessage#affKey}}, 
{{StoredCacheData#ccfg}}, {{QueryEntityMessage#dfltFieldValues}}, 
{{PartitionHashRecord#consistentId}}. What actually decides is whether the 
marshalling may block on a cluster-wide class registration: binary - and 
{{OptimizedMarshaller}} inside it - goes 
{{MarshallerContextImpl#registerClassName}} -> {{proposeMapping}} -> 
{{fut.get()}}, which is impossible on the discovery thread, during join, and 
for on-disk formats read by offline tools. That is a property of the call site, 
not of the class.

Binding it to the class produces three defects:

# The annotation is not {{@Inherited}}, while the generator builds a companion 
from all fields including inherited ones 
({{MessageCompanionGenerator#enclosedFields}}). Nine parent/child pairs are 
registered in different categories. Verified by adding a blob field to 
{{GridDistributedTxPrepareRequest}} (binary): in the generated 
{{GridNearTxPrepareRequestMarshaller}} (jdk) the very same inherited field is 
marshalled with jdk. No such field exists today, so this is a latent trap 
rather than a live bug.
# A nested message overrides the enclosing one, because the marshaller is 
resolved per {{directType}}: {{ErrorMessage}} (jdk) travels inside binary 
messages, and {{BinaryMetadataVersionInfo}} (binary) travels inside the jdk 
discovery data bag {{CacheBinaryDataBagItem}} - while the same 
{{BinaryMetadata}} in {{MetadataUpdateProposedMessage}} is marshalled with jdk. 
It works only because those classes are pre-registered in 
{{META-INF/classnames.properties}} and need no ring round-trip.
# {{MarshallableMessage}} conflates "needs a pre-marshal hook" with "needs a 
marshaller": 8 of its 21 implementations use neither the marshaller nor the 
class loader.

h3. Plan

Each subtask is independently mergeable and useful on its own.

# (pending) - *Remove dead marshalling code from 
{{GridDhtAtomicUpdateRequest}}.* {{forceTransformBackups}} is {{false}} at both 
creation sites, so the hand-written marshalling and the fields it fills are 
unreachable. Result: one boolean and three always-null collection fields leave 
the wire, and one of the 11 meaningful annotations goes away.
# (pending) - *Register core messages uniformly.* 
{{withSchema}}/{{withNoSchema}} are identical after IGNITE-28929, and the 
marshaller is threaded into serializer and deployer lookups that never take 
one. Result: a single registration form, the marshaller stated once, generated 
code byte-identical.
# (pending) - *Split {{MarshallableMessage}} into a hook and a marshalling 
contract.* 8 of its 21 implementations use neither the marshaller nor the class 
loader - they only reshape their own fields. Result: 'needs a marshaller' 
becomes visible in the type, and 8 companions lose the field.
# (pending) - *Replace marshaller blobs of internal types with regular message 
fields* - {{BinaryMetadata}} (two messages), {{BaselineTopology}}, job 
siblings, query entities. Binary yields no schema for them anyway, since 
{{Externalizable}} types fall back to {{OptimizedMarshaller}} inside it. 
Result: blob fields 24 -> ~19, and the {{BinaryMetadata}} inconsistency 
disappears.
# (pending) - *Select the marshaller by transport instead of per message class* 
- the core change. Communication call sites pass binary, discovery call sites 
pass jdk, and the generator passes it down the message tree. Result: zero 
annotations, the inherited-field trap and the nested-message override become 
impossible, and the wire format changes for four classes deliberately.
# (pending) - *Test that discovery messages never require cluster-wide class 
registration.* Today the rule holds by luck in at least one place - only 
because the classes involved are pre-registered in {{classnames.properties}}. 
Result: the rule is checked in CI instead of remembered.

Order: the first two are cheap and independent of everything else; the hook 
split and the blob removal both shrink the set of classes the transport change 
has to touch, so they come before it; the test closes the loop last.

h3. Expected result

|| || before || after ||
| places where the marshaller is chosen | 60 annotations, two registration 
helpers | 0 annotations, marshaller comes from the call site |
| companions holding a {{Marshaller}} | 30 | ~12, hand-written marshalling only 
|
| classes given a marshaller they never use | 6 | 0 |
| blob fields of internal types | 24 | ~19 |
| inherited-field and nested-message mismatches | possible | impossible by 
construction |

The number of marshaller implementations does not change - all three are 
load-bearing: jdk serializes without any cluster context, binary gives field 
access without the class, and optimized is the fallback binary itself relies on 
for {{Externalizable}} and {{writeObject}}/{{readObject}} types (and the REST 
client protocol).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to