[
https://issues.apache.org/jira/browse/IGNITE-28935?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Anton Vinogradov updated IGNITE-28935:
--------------------------------------
Description:
h3. Goal
Make it clear which marshaller is used for each message field. Today this is
set in 60 places. It should be set in none.
h3. Why
An object field of a message becomes bytes either by {{JdkMarshaller}} or by
{{BinaryMarshaller}}. The marshaller is chosen per message class: the
{{@UseBinaryMarshaller}} annotation plus the call in {{CoreMessagesProvider}}.
Numbers from master, counted in the generated companions:
* core registers 297 messages: 58 binary, 239 jdk;
* 195 {{*Marshaller}} companions are generated, but only 30 keep a
{{Marshaller}};
* the choice changes the result for 24 classes only;
* there are 60 annotations, and only 13 of them matter. The other 47 do nothing.
The annotation should mean "this message carries user classes". It does not.
User data goes with jdk in {{TcpDiscoveryNode#attrs}},
{{LazyServiceConfigurationMessage#affKey}}, {{StoredCacheData#ccfg}} and
{{QueryEntityMessage#dfltFieldValues}}.
The real rule is different. When binary sees a class for the first time, it
registers the class name in the whole cluster:
{{MarshallerContextImpl#registerClassName}} -> {{proposeMapping}} ->
{{fut.get()}}. This call waits for discovery. So it cannot be used on the
discovery thread, during node join, or for files that must be read without a
cluster. This depends on where we marshal, not on the message type.
Binding the marshaller to the class gives three problems:
# The annotation is not {{@Inherited}}, but the generator builds a companion
from all fields, including inherited ones. Nine parent/child pairs are
registered in different groups. Checked with an experiment: add a blob field to
{{GridDistributedTxPrepareRequest}} (binary), and the generated
{{GridNearTxPrepareRequestMarshaller}} (jdk) marshals the same field with jdk.
No such field exists today, so this is a trap, not a live bug.
# A nested message overrides the outer one, because the marshaller is taken by
{{directType}}. {{ErrorMessage}} (jdk) travels inside binary messages.
{{BinaryMetadataVersionInfo}} (binary) travels inside the jdk discovery data
bag. The same {{BinaryMetadata}} in {{MetadataUpdateProposedMessage}} uses jdk.
This works only because such classes are listed in
{{META-INF/classnames.properties}} and need no cluster call.
# {{MarshallableMessage}} means two things at once: "call me before send" and
"I need a marshaller". 8 of its 21 implementations use neither the marshaller
nor the class loader.
h3. Plan
Three subtasks change the bytes on the wire. They should be done *first*, while
2.19 is not released yet. After the release the same changes would break
rolling upgrade and would need a feature flag plus support for both formats.
*Do first - these change the wire format:*
# IGNITE-28936 - *remove dead marshalling in {{GridDhtAtomicUpdateRequest}}.*
The flag is always {{false}}, so this code never runs. Result: 4 fields less on
the wire.
# IGNITE-28939 - *turn blob fields of internal types into normal message
fields* ({{BinaryMetadata}}, {{BaselineTopology}}, job siblings, query
entities). Result: 24 blob fields become about 19, and the {{BinaryMetadata}}
mismatch is gone.
# IGNITE-28940 - *pass the marshaller from the call site.* This is the main
change. Communication passes binary, discovery passes jdk. Four classes change
their format on purpose. Result: no annotations left, and a wrong marshaller
becomes impossible.
*Can be done any time - no wire format change:*
# IGNITE-28937 - *one registration form in {{CoreMessagesProvider}}.*
{{withSchema}} and {{withNoSchema}} are the same code now. Result: the
marshaller is written in one place, generated code does not change.
# IGNITE-28938 - *split {{MarshallableMessage}}.* 8 of 21 classes only need a
callback. Result: they stop getting a marshaller. Small task: if it is done
before IGNITE-28940, that one becomes smaller.
# IGNITE-28941 - *add a test for the discovery path.* Result: the rule is
checked by CI, not kept in mind.
h3. Expected result
|| || now || after ||
| places that choose the marshaller | 60 annotations | 0 |
| companions that keep a {{Marshaller}} | 30 | about 12 |
| classes that get a marshaller and never use it | 6 | 0 |
| blob fields of internal types | 24 | about 19 |
| wrong marshaller from inheritance or nesting | possible | not possible |
All three marshaller implementations are still needed. jdk works without a
cluster. binary reads fields without the class. optimized is used by binary
itself for {{Externalizable}} classes, and by the REST protocol.
was:
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.
# IGNITE-28936 - *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.
# IGNITE-28937 - *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.
# IGNITE-28938 - *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.
# IGNITE-28939 - *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.
# IGNITE-28940 - *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.
# IGNITE-28941 - *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).
Summary: Simplify how the marshaller is chosen for message fields
(was: Simplify marshaller selection for message object fields)
> Simplify how the marshaller is chosen for message 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
> Priority: Major
> Fix For: 2.19
>
>
> h3. Goal
> Make it clear which marshaller is used for each message field. Today this is
> set in 60 places. It should be set in none.
> h3. Why
> An object field of a message becomes bytes either by {{JdkMarshaller}} or by
> {{BinaryMarshaller}}. The marshaller is chosen per message class: the
> {{@UseBinaryMarshaller}} annotation plus the call in {{CoreMessagesProvider}}.
> Numbers from master, counted in the generated companions:
> * core registers 297 messages: 58 binary, 239 jdk;
> * 195 {{*Marshaller}} companions are generated, but only 30 keep a
> {{Marshaller}};
> * the choice changes the result for 24 classes only;
> * there are 60 annotations, and only 13 of them matter. The other 47 do
> nothing.
> The annotation should mean "this message carries user classes". It does not.
> User data goes with jdk in {{TcpDiscoveryNode#attrs}},
> {{LazyServiceConfigurationMessage#affKey}}, {{StoredCacheData#ccfg}} and
> {{QueryEntityMessage#dfltFieldValues}}.
> The real rule is different. When binary sees a class for the first time, it
> registers the class name in the whole cluster:
> {{MarshallerContextImpl#registerClassName}} -> {{proposeMapping}} ->
> {{fut.get()}}. This call waits for discovery. So it cannot be used on the
> discovery thread, during node join, or for files that must be read without a
> cluster. This depends on where we marshal, not on the message type.
> Binding the marshaller to the class gives three problems:
> # The annotation is not {{@Inherited}}, but the generator builds a companion
> from all fields, including inherited ones. Nine parent/child pairs are
> registered in different groups. Checked with an experiment: add a blob field
> to {{GridDistributedTxPrepareRequest}} (binary), and the generated
> {{GridNearTxPrepareRequestMarshaller}} (jdk) marshals the same field with
> jdk. No such field exists today, so this is a trap, not a live bug.
> # A nested message overrides the outer one, because the marshaller is taken
> by {{directType}}. {{ErrorMessage}} (jdk) travels inside binary messages.
> {{BinaryMetadataVersionInfo}} (binary) travels inside the jdk discovery data
> bag. The same {{BinaryMetadata}} in {{MetadataUpdateProposedMessage}} uses
> jdk. This works only because such classes are listed in
> {{META-INF/classnames.properties}} and need no cluster call.
> # {{MarshallableMessage}} means two things at once: "call me before send" and
> "I need a marshaller". 8 of its 21 implementations use neither the marshaller
> nor the class loader.
> h3. Plan
> Three subtasks change the bytes on the wire. They should be done *first*,
> while 2.19 is not released yet. After the release the same changes would
> break rolling upgrade and would need a feature flag plus support for both
> formats.
> *Do first - these change the wire format:*
> # IGNITE-28936 - *remove dead marshalling in {{GridDhtAtomicUpdateRequest}}.*
> The flag is always {{false}}, so this code never runs. Result: 4 fields less
> on the wire.
> # IGNITE-28939 - *turn blob fields of internal types into normal message
> fields* ({{BinaryMetadata}}, {{BaselineTopology}}, job siblings, query
> entities). Result: 24 blob fields become about 19, and the {{BinaryMetadata}}
> mismatch is gone.
> # IGNITE-28940 - *pass the marshaller from the call site.* This is the main
> change. Communication passes binary, discovery passes jdk. Four classes
> change their format on purpose. Result: no annotations left, and a wrong
> marshaller becomes impossible.
> *Can be done any time - no wire format change:*
> # IGNITE-28937 - *one registration form in {{CoreMessagesProvider}}.*
> {{withSchema}} and {{withNoSchema}} are the same code now. Result: the
> marshaller is written in one place, generated code does not change.
> # IGNITE-28938 - *split {{MarshallableMessage}}.* 8 of 21 classes only need a
> callback. Result: they stop getting a marshaller. Small task: if it is done
> before IGNITE-28940, that one becomes smaller.
> # IGNITE-28941 - *add a test for the discovery path.* Result: the rule is
> checked by CI, not kept in mind.
> h3. Expected result
> || || now || after ||
> | places that choose the marshaller | 60 annotations | 0 |
> | companions that keep a {{Marshaller}} | 30 | about 12 |
> | classes that get a marshaller and never use it | 6 | 0 |
> | blob fields of internal types | 24 | about 19 |
> | wrong marshaller from inheritance or nesting | possible | not possible |
> All three marshaller implementations are still needed. jdk works without a
> cluster. binary reads fields without the class. optimized is used by binary
> itself for {{Externalizable}} classes, and by the REST protocol.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)