[ 
https://issues.apache.org/jira/browse/IGNITE-28520?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Alex Abashev updated IGNITE-28520:
----------------------------------
    Description: 
h2. Summary

The CacheObject pre-marshal pass already runs on the user thread — 
{{GridCacheIoManager.onSend}} has been calling {{msg.prepareMarshal(cctx)}} on 
the sender thread since 2014 (commit by Anton Irinev, before {{ignite-63}}). 
This ticket is *not* about moving work off the NIO worker; it is about 
replacing the per-class hand-written CO traversal with a generated, 
auto-invoked equivalent.

Today every {{GridCacheMessage}} subclass that carries {{CacheObject}} fields 
has a hand-written {{prepareMarshal(GridCacheSharedContext)}} override that 
walks those fields and calls {{co.prepareMarshal(ctx)}} on each. Adding a new 
{{@Order CO}} field to such a class requires a matching update of the override. 
Forgetting that update silently moves that field's {{Marshaller.marshal}} call 
onto the NIO writer (it runs lazily inside {{writeTo}} when {{valBytes == 
null}}), which is the regression mode this ticket exists to prevent.

After this ticket lands, the same traversal is generated by APT and 
auto-invoked from the framework — so coverage is structural rather than 
maintained by hand.

h2. What changes

* {{MessageSerializer}} interface gets a new {{default void 
prepareMarshalCacheObjects(M msg, CacheObjectValueContext ctx)}} (no-op 
default).
* {{MessageSerializerGenerator}} (APT) emits this method on every concrete 
serializer with at least one traversable {{@Order}} field; recurses into nested 
concrete {{Message}} types via per-class {{*_SER}} static fields. Recursion is 
blocked only for {{GridCacheIdMessage}} subclasses (top-level routable 
messages) — helper messages with {{@Order int cacheId}} ({{GridCacheReturn}}, 
{{IgniteTxKey}}, {{IgniteTxEntry}}, {{GridCacheEntryInfo}}, {{UpdateErrors}}) 
are recursable.
* {{GridCacheIoManager.onSend}} adds 
{{prepareMarshalGeneratedCacheObjects(msg)}} after the existing 
{{msg.prepareMarshal(cctx)}}. Helper is gated on {{msg instanceof 
GridCacheIdMessage}} and resolves {{cacheObjectContext}} from 
{{((GridCacheIdMessage)msg).cacheId()}}. Both calls run on the user thread, 
both are idempotent for any given {{CacheObject}} ({{if (valBytes == null)}}).
* Hand-written {{prepareMarshal(GridCacheSharedContext)}} overrides are removed 
from {{GridCacheMessage}} subclasses where the body was a pure {{@Order}}-CO 
traversal that the generator now performs:
** {{GridCacheTtlUpdateRequest}}, {{GridDhtForceKeysRequest}}, 
{{GridNearUnlockRequest}}, {{GridDhtUnlockRequest}}, 
{{GridDistributedLockRequest}}, {{GridDistributedLockResponse}}, 
{{GridDhtAtomicUpdateResponse}}, {{GridDhtAtomicNearResponse}}, 
{{GridNearGetRequest}}.
** Also dropped: {{ret.prepareMarshal(cctx)}} call inside 
{{GridNearAtomicUpdateResponse.prepareMarshal}} (covered by nested-message 
recursion in the generated serializer).
* Generator hygiene aligned with Ignite coding guidelines: {{private static 
final}} modifier order, no trailing space after {{/**}} in class javadoc, no 
trailing newline at EOF, empty line between top-level {{if}}-blocks in 
{{prepareMarshalCacheObjects}}, empty line between map keys/values traversal 
blocks.

h2. Why it is worth doing

* *Structural coverage.* New {{@Order CO}} fields are picked up automatically; 
no risk of a missed override.
* *Less boilerplate.* Phase 2 cleanup deletes ~9 hand-written methods that were 
doing the same loop the generator now writes.
* *One source of truth* for "which fields participate in pre-marshal traversal" 
— the {{@Order}} annotations.
* *No behavioural change* for messages whose hand-written override stays — the 
auto-invoke is a no-op on already-marshalled {{CacheObject}}s thanks to the 
{{valBytes == null}} guard inside {{CacheObjectImpl.prepareMarshal}}.

h2. Out of scope

* Receive-side counterpart ({{finishUnmarshalCacheObjects}}) — split into a 
separate ticket (IGNITE-YYYYY).
* Extending the auto-invoke to {{GridCacheMessage}} subclasses that are not 
{{GridCacheIdMessage}} (e.g. {{GridDhtTxFinishResponse}}), or to nested 
messages whose embedded {{cacheId}} differs from the outer's (e.g. 
{{GridNearTxPrepareResponse}}'s {{retVal.cacheId()}}) — split into a separate 
ticket (IGNITE-ZZZZZ). Until that lands, {{GridCacheReturn}} keeps its {{static 
SER}} field and {{prepareMarshal(GridCacheContext)}} bridge for the two 
responses that fall outside the auto-invoke gate.

h2. Acceptance criteria

* {{GridCacheIoManager.onSend}} calls 
{{prepareMarshalGeneratedCacheObjects(msg)}} for every {{GridCacheIdMessage}} 
subclass with a non-{{null}} {{cacheObjectContext}}.
* {{MessageProcessorTest}} green; fixture serializers byte-identical with 
generator output.
* Targeted suites green: {{GridCacheReturnValueTransferSelfTest}}, 
{{IgniteCacheEntryProcessorCallTest}}, 
{{GridCachePartitionedTxMultiThreadedSelfTest}}, 
{{GridCacheRebalancingSyncSelfTest}} (force-keys path), 
{{GridCachePutAllFailoverSelfTest}} (atomic update / unlock path), 
{{IgniteCacheQueryMultiThreadedSelfTest}} (query/eviction path).
* Possible-blockers list from the TC bot for the PR contains no real 
regressions (flakes only).

  was:
h2. Summary

Move generic {{CacheObject}} marshalling for outgoing cache messages from the 
NIO communication worker to the user thread, so the IO loop is no longer 
blocked on serialisation work that has nothing to do with the wire protocol 
itself.

The send path now does:

# {{msg.prepareMarshal(cctx)}} — existing hand-written hook on 
{{GridCacheMessage}} subclasses (legacy);
# {{ser.prepareMarshalCacheObjects(msg, cacheObjCtx)}} — new auto-invoke of the 
generated serializer's traversal of {{@Order}}-annotated {{CacheObject}} / 
{{KeyCacheObject}} / nested {{Message}} fields.

Both run on the user (sender) thread inside {{GridCacheIoManager.onSend}} 
before the message is enqueued for NIO; the NIO worker only does the actual 
byte writes.

h2. Scope of this ticket

* New {{default}} method {{MessageSerializer#prepareMarshalCacheObjects(M, 
CacheObjectValueContext)}} (no-op default).
* {{MessageProcessor}} / {{MessageSerializerGenerator}} emit this method on 
every concrete serializer with at least one traversable {{@Order}} field; 
recursion into nested concrete {{Message}} types via per-class {{*_SER}} static 
fields.
* {{GridCacheIoManager.onSend}} calls 
{{prepareMarshalGeneratedCacheObjects(msg)}} — gated on {{msg instanceof 
GridCacheIdMessage}}, takes {{cacheObjectContext}} from 
{{((GridCacheIdMessage)msg).cacheId()}}.
* Phase 2 cleanup: the hand-written {{prepareMarshal(GridCacheSharedContext)}} 
override is removed from {{GridCacheMessage}} subclasses where its body was a 
pure {{@Order}}-CO traversal that the generator now performs:
** {{GridCacheTtlUpdateRequest}}, {{GridDhtForceKeysRequest}}, 
{{GridNearUnlockRequest}}, {{GridDhtUnlockRequest}}, 
{{GridDistributedLockRequest}}, {{GridDistributedLockResponse}}, 
{{GridDhtAtomicUpdateResponse}}, {{GridDhtAtomicNearResponse}}, 
{{GridNearGetRequest}}.
** Also removed: {{ret.prepareMarshal(cctx)}} call in 
{{GridNearAtomicUpdateResponse.prepareMarshal}} (covered by nested-message 
recursion in the generated serializer).
* {{MessageSerializerGenerator.isRecursableMessage}} narrowed: 
nested-{{Message}} recursion is blocked only for {{GridCacheIdMessage}} 
subclasses (top-level routable). Helper messages with {{@Order int cacheId}} 
({{GridCacheReturn}}, {{IgniteTxKey}}, {{IgniteTxEntry}}, 
{{GridCacheEntryInfo}}, {{UpdateErrors}}) become recursable.
* Generator hygiene: emits {{private static final}} (modern modifier order); 
{{/**}} class javadoc starts without trailing space; no trailing newline; empty 
line between top-level {{if}}-blocks in {{prepareMarshalCacheObjects}} per 
Ignite coding guidelines.

h2. Out of scope

* Mirror work for the receive direction ({{finishUnmarshalCacheObjects}}) — 
split into a separate ticket (IGNITE-YYYYY).
* Extending the auto-invoke beyond {{GridCacheIdMessage}} subclasses or to 
messages whose retVal carries its own {{cacheId}} — split into a separate 
ticket (IGNITE-ZZZZZ); blocks removal of the {{GridCacheReturn.SER}} bridge and 
the explicit {{retVal.prepareMarshal(cctx)}} calls in 
{{GridDhtTxFinishResponse}} / {{GridNearTxPrepareResponse}}.

h2. Acceptance criteria

* {{GridCacheIoManager.onSend}} invokes 
{{prepareMarshalGeneratedCacheObjects(msg)}} for {{GridCacheIdMessage}} 
subclasses with non-{{null}} {{cacheObjectContext}}.
* {{MessageProcessorTest}} green; fixture serializers byte-identical with 
generator output.
* Targeted suites green: {{GridCacheReturnValueTransferSelfTest}}, 
{{IgniteCacheEntryProcessorCallTest}}, 
{{GridCachePartitionedTxMultiThreadedSelfTest}}, 
{{GridCacheRebalancingSyncSelfTest}} (force-keys path), 
{{GridCachePutAllFailoverSelfTest}} (atomic update / unlock path), 
{{IgniteCacheQueryMultiThreadedSelfTest}} (query/eviction path).
* No regression in possible-blockers from the TC bot for the PR.


> Move prepareMarshal / finishUnmarshal out of NIO communication thread — Phase 
> 1: CacheObjects
> ---------------------------------------------------------------------------------------------
>
>                 Key: IGNITE-28520
>                 URL: https://issues.apache.org/jira/browse/IGNITE-28520
>             Project: Ignite
>          Issue Type: Task
>            Reporter: Alex Abashev
>            Assignee: Alex Abashev
>            Priority: Minor
>              Labels: IEP-132, ise
>             Fix For: 2.19
>
>          Time Spent: 3h 20m
>  Remaining Estimate: 0h
>
> h2. Summary
> The CacheObject pre-marshal pass already runs on the user thread — 
> {{GridCacheIoManager.onSend}} has been calling {{msg.prepareMarshal(cctx)}} 
> on the sender thread since 2014 (commit by Anton Irinev, before 
> {{ignite-63}}). This ticket is *not* about moving work off the NIO worker; it 
> is about replacing the per-class hand-written CO traversal with a generated, 
> auto-invoked equivalent.
> Today every {{GridCacheMessage}} subclass that carries {{CacheObject}} fields 
> has a hand-written {{prepareMarshal(GridCacheSharedContext)}} override that 
> walks those fields and calls {{co.prepareMarshal(ctx)}} on each. Adding a new 
> {{@Order CO}} field to such a class requires a matching update of the 
> override. Forgetting that update silently moves that field's 
> {{Marshaller.marshal}} call onto the NIO writer (it runs lazily inside 
> {{writeTo}} when {{valBytes == null}}), which is the regression mode this 
> ticket exists to prevent.
> After this ticket lands, the same traversal is generated by APT and 
> auto-invoked from the framework — so coverage is structural rather than 
> maintained by hand.
> h2. What changes
> * {{MessageSerializer}} interface gets a new {{default void 
> prepareMarshalCacheObjects(M msg, CacheObjectValueContext ctx)}} (no-op 
> default).
> * {{MessageSerializerGenerator}} (APT) emits this method on every concrete 
> serializer with at least one traversable {{@Order}} field; recurses into 
> nested concrete {{Message}} types via per-class {{*_SER}} static fields. 
> Recursion is blocked only for {{GridCacheIdMessage}} subclasses (top-level 
> routable messages) — helper messages with {{@Order int cacheId}} 
> ({{GridCacheReturn}}, {{IgniteTxKey}}, {{IgniteTxEntry}}, 
> {{GridCacheEntryInfo}}, {{UpdateErrors}}) are recursable.
> * {{GridCacheIoManager.onSend}} adds 
> {{prepareMarshalGeneratedCacheObjects(msg)}} after the existing 
> {{msg.prepareMarshal(cctx)}}. Helper is gated on {{msg instanceof 
> GridCacheIdMessage}} and resolves {{cacheObjectContext}} from 
> {{((GridCacheIdMessage)msg).cacheId()}}. Both calls run on the user thread, 
> both are idempotent for any given {{CacheObject}} ({{if (valBytes == null)}}).
> * Hand-written {{prepareMarshal(GridCacheSharedContext)}} overrides are 
> removed from {{GridCacheMessage}} subclasses where the body was a pure 
> {{@Order}}-CO traversal that the generator now performs:
> ** {{GridCacheTtlUpdateRequest}}, {{GridDhtForceKeysRequest}}, 
> {{GridNearUnlockRequest}}, {{GridDhtUnlockRequest}}, 
> {{GridDistributedLockRequest}}, {{GridDistributedLockResponse}}, 
> {{GridDhtAtomicUpdateResponse}}, {{GridDhtAtomicNearResponse}}, 
> {{GridNearGetRequest}}.
> ** Also dropped: {{ret.prepareMarshal(cctx)}} call inside 
> {{GridNearAtomicUpdateResponse.prepareMarshal}} (covered by nested-message 
> recursion in the generated serializer).
> * Generator hygiene aligned with Ignite coding guidelines: {{private static 
> final}} modifier order, no trailing space after {{/**}} in class javadoc, no 
> trailing newline at EOF, empty line between top-level {{if}}-blocks in 
> {{prepareMarshalCacheObjects}}, empty line between map keys/values traversal 
> blocks.
> h2. Why it is worth doing
> * *Structural coverage.* New {{@Order CO}} fields are picked up 
> automatically; no risk of a missed override.
> * *Less boilerplate.* Phase 2 cleanup deletes ~9 hand-written methods that 
> were doing the same loop the generator now writes.
> * *One source of truth* for "which fields participate in pre-marshal 
> traversal" — the {{@Order}} annotations.
> * *No behavioural change* for messages whose hand-written override stays — 
> the auto-invoke is a no-op on already-marshalled {{CacheObject}}s thanks to 
> the {{valBytes == null}} guard inside {{CacheObjectImpl.prepareMarshal}}.
> h2. Out of scope
> * Receive-side counterpart ({{finishUnmarshalCacheObjects}}) — split into a 
> separate ticket (IGNITE-YYYYY).
> * Extending the auto-invoke to {{GridCacheMessage}} subclasses that are not 
> {{GridCacheIdMessage}} (e.g. {{GridDhtTxFinishResponse}}), or to nested 
> messages whose embedded {{cacheId}} differs from the outer's (e.g. 
> {{GridNearTxPrepareResponse}}'s {{retVal.cacheId()}}) — split into a separate 
> ticket (IGNITE-ZZZZZ). Until that lands, {{GridCacheReturn}} keeps its 
> {{static SER}} field and {{prepareMarshal(GridCacheContext)}} bridge for the 
> two responses that fall outside the auto-invoke gate.
> h2. Acceptance criteria
> * {{GridCacheIoManager.onSend}} calls 
> {{prepareMarshalGeneratedCacheObjects(msg)}} for every {{GridCacheIdMessage}} 
> subclass with a non-{{null}} {{cacheObjectContext}}.
> * {{MessageProcessorTest}} green; fixture serializers byte-identical with 
> generator output.
> * Targeted suites green: {{GridCacheReturnValueTransferSelfTest}}, 
> {{IgniteCacheEntryProcessorCallTest}}, 
> {{GridCachePartitionedTxMultiThreadedSelfTest}}, 
> {{GridCacheRebalancingSyncSelfTest}} (force-keys path), 
> {{GridCachePutAllFailoverSelfTest}} (atomic update / unlock path), 
> {{IgniteCacheQueryMultiThreadedSelfTest}} (query/eviction path).
> * Possible-blockers list from the TC bot for the PR contains no real 
> regressions (flakes only).



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

Reply via email to