From 6bfa2948268a256fae5b54d805019902049ea6d1 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 01:43:00 +0300 Subject: [PATCH 01/19] Deployment descriptor: resolve by the descriptor itself The sender of the classes is already inside the descriptor: the class loader id carries it, and the loader asserts as much. So getGlobalDeployment no longer asks for it, and the dead nodeFilter parameter is gone with it. Co-Authored-By: Claude Opus 5 --- .../internal/GridEventConsumeHandler.java | 6 +--- .../internal/GridMessageListenHandler.java | 8 +---- .../managers/communication/GridIoManager.java | 3 +- .../deployment/GridDeploymentManager.java | 33 ++++++++++++++++--- .../deployment/GridDeploymentMetadata.java | 20 ----------- .../eventstorage/GridEventStorageManager.java | 3 +- .../affinity/GridAffinityUtils.java | 3 +- .../cache/GridCacheDeploymentManager.java | 3 +- .../CacheContinuousQueryDeployableObject.java | 6 +--- .../continuous/StartRequestData.java | 13 +------- .../datastreamer/DataStreamProcessor.java | 3 +- .../processors/job/GridJobProcessor.java | 3 +- 12 files changed, 39 insertions(+), 65 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java index cad29b7b12909..300e93a43771c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java @@ -417,11 +417,7 @@ private boolean filterDropsEvent(Event evt) { if (filterBytes != null) { try { - GridDeployment dep = ctx.deploy().getGlobalDeployment(depInfo.deployMode(), clsName, clsName, - depInfo.userVersion(), nodeId, depInfo.classLoaderId(), depInfo.participants(), null); - - if (dep == null) - throw new IgniteDeploymentCheckedException("Failed to obtain deployment for class: " + clsName); + GridDeployment dep = ctx.deploy().globalDeployment(depInfo, clsName); filter = U.unmarshal(ctx, filterBytes, U.resolveClassLoader(dep.classLoader(), ctx.config())); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java index e59873f4b449f..0226e982d51fb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java @@ -178,13 +178,7 @@ public GridMessageListenHandler(@Nullable Object topic, IgniteBiPredicate participants, - @Nullable IgnitePredicate nodeFilter) { + Map participants) { if (locDep != null) return locDep; @@ -439,7 +465,6 @@ private GridDeployment checkDeployment(GridDeployment deployment, String store) meta.senderNodeId(sndNodeId); meta.classLoaderId(clsLdrId); meta.participants(participants); - meta.nodeFilter(nodeFilter); if (!ctx.config().isPeerClassLoadingEnabled()) { meta.record(true); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentMetadata.java index 370825c5fee71..c5f6857be9cb9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentMetadata.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentMetadata.java @@ -19,11 +19,9 @@ import java.util.Map; import java.util.UUID; -import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.DeploymentMode; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.lang.IgniteUuid; /** @@ -61,9 +59,6 @@ public class GridDeploymentMetadata { /** */ private boolean record; - /** */ - private IgnitePredicate nodeFilter; - /** * */ @@ -87,7 +82,6 @@ public class GridDeploymentMetadata { participants = meta.participants(); parentLdr = meta.parentLoader(); record = meta.record(); - nodeFilter = meta.nodeFilter(); } /** @@ -271,20 +265,6 @@ public void classLoader(ClassLoader clsLdr) { this.clsLdr = clsLdr; } - /** - * @param nodeFilter Node filter. - */ - public void nodeFilter(IgnitePredicate nodeFilter) { - this.nodeFilter = nodeFilter; - } - - /** - * @return Node filter. - */ - public IgnitePredicate nodeFilter() { - return nodeFilter; - } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridDeploymentMetadata.class, this, "seqNum", clsLdrId != null ? clsLdrId.localId() : "n/a"); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java index 1c883d1474aa3..50176adc8aada 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java @@ -1255,8 +1255,7 @@ private class RequestListener implements GridMessageListener { req.userVersion(), nodeId, req.classLoaderId(), - req.loaderParticipants(), - null); + req.loaderParticipants()); if (dep == null) throw new IgniteDeploymentCheckedException("Failed to obtain deployment for event filter " + diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java index 3937be9b792a4..2eda968819377 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java @@ -103,8 +103,7 @@ static Object unmarshall(GridKernalContext ctx, UUID sndNodeId, GridAffinityMess msg.userVersion(), sndNodeId, msg.classLoaderId(), - msg.loaderParticipants(), - null); + msg.loaderParticipants()); if (dep == null) throw new IgniteDeploymentCheckedException("Failed to obtain affinity object (is peer class loading turned on?): " + diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java index 125fe4ce94743..992523b9faf09 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java @@ -644,8 +644,7 @@ else if (err == null) userVer, sndId, ldrId, - participants, - F.alwaysTrue()); + participants); return d != null ? d.deployedClass(name) : null; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java index c4e4005095ff4..647098ee50e7b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java @@ -88,11 +88,7 @@ protected CacheContinuousQueryDeployableObject(Object obj, GridKernalContext ctx T unmarshal(UUID nodeId, GridKernalContext ctx) throws IgniteCheckedException { assert ctx != null; - GridDeployment dep = ctx.deploy().getGlobalDeployment(depInfo.deployMode(), clsName, clsName, - depInfo.userVersion(), nodeId, depInfo.classLoaderId(), depInfo.participants(), null); - - if (dep == null) - throw new IgniteDeploymentCheckedException("Failed to obtain deployment for class: " + clsName); + GridDeployment dep = ctx.deploy().globalDeployment(depInfo, clsName); return U.unmarshal(ctx, bytes, U.resolveClassLoader(dep.classLoader(), ctx.config())); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index 9571c231bbf9a..9792739d13c55 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -21,7 +21,6 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.internal.IgniteDeploymentCheckedException; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.managers.deployment.GridDeployment; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; @@ -188,17 +187,7 @@ public void marshal(GridKernalContext ctx) throws IgniteCheckedException { /** */ public void unmarshal(GridKernalContext ctx, UUID sndId) throws IgniteCheckedException { if (ctx.config().isPeerClassLoadingEnabled() && clsName != null) { - GridDeployment dep = ctx.deploy().getGlobalDeployment(depInfo.deployMode(), - clsName, - clsName, - depInfo.userVersion(), - sndId, - depInfo.classLoaderId(), - depInfo.participants(), - null); - - if (dep == null) - throw new IgniteDeploymentCheckedException("Failed to obtain deployment for class: " + clsName); + GridDeployment dep = ctx.deploy().globalDeployment(depInfo, clsName); nodeFilter = U.unmarshal(ctx.marshaller(), nodeFilterBytes, diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java index 371b0487d65cb..338bcd59b8bda 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java @@ -235,8 +235,7 @@ private void processRequest(final UUID nodeId, final DataStreamerRequest req) { req.userVersion(), nodeId, req.classLoaderId(), - req.participants(), - null); + req.participants()); if (dep == null) { sendResponse(nodeId, diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java index b81a8b43bf21e..7058c751c0191 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java @@ -1216,8 +1216,7 @@ public void processJobExecuteRequest(ClusterNode node, final GridJobExecuteReque req.userVersion(), node.id(), req.classLoaderId(), - req.loaderParticipants(), - null); + req.loaderParticipants()); if (tmpDep == null) { if (log.isDebugEnabled()) From 48146013d8c45867e6c37b90dcb9727179becf36 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 01:57:27 +0300 Subject: [PATCH 02/19] Deployment descriptor: carry it whole in the event request GridEventStorageRequest kept the deployment as four flat fields, so it had nothing to pass as a descriptor. It now carries GridDeploymentInfoBean, like the messages that already do, and resolves through the descriptor. Co-Authored-By: Claude Opus 5 --- .../eventstorage/GridEventStorageManager.java | 21 +------ .../eventstorage/GridEventStorageRequest.java | 63 ++++--------------- 2 files changed, 13 insertions(+), 71 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java index 50176adc8aada..38c6be5aa6738 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java @@ -1070,13 +1070,7 @@ private List query(IgnitePredicate p, Collection evts; try { - GridDeployment dep = ctx.deploy().getGlobalDeployment( - req.deploymentMode(), - req.filterClassName(), - req.filterClassName(), - req.userVersion(), - nodeId, - req.classLoaderId(), - req.loaderParticipants()); - - if (dep == null) - throw new IgniteDeploymentCheckedException("Failed to obtain deployment for event filter " + - "(is peer class loading turned on?): " + req); + GridDeployment dep = ctx.deploy().globalDeployment(req.deploymentInfo(), req.filterClassName()); MessageMarshalling.unmarshal(req, ctx, null, U.resolveClassLoader(dep.classLoader(), ctx.config())); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java index a2fd6a0c26c3c..9e039cc12690b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java @@ -17,19 +17,15 @@ package org.apache.ignite.internal.managers.eventstorage; -import java.util.Collections; -import java.util.Map; -import java.util.UUID; -import org.apache.ignite.configuration.DeploymentMode; import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.UseBinaryMarshaller; -import org.apache.ignite.internal.util.tostring.GridToStringInclude; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.lang.IgniteUuid; -import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.GridTopic.TOPIC_EVENT; @@ -48,27 +44,14 @@ public class GridEventStorageRequest implements DeferredUnmarshalMessage { @Order(1) byte[] filterBytes; - /** */ + /** Deployment of the filter classes. */ @Order(2) - IgniteUuid clsLdrId; + GridDeploymentInfoBean depInfo; /** */ @Order(3) - DeploymentMode depMode; - - /** */ - @Order(4) String filterClsName; - /** */ - @Order(5) - String userVer; - - /** Node class loader participants. */ - @GridToStringInclude - @Order(6) - Map ldrParties; - /** */ public GridEventStorageRequest() { // No-op. @@ -77,24 +60,12 @@ public GridEventStorageRequest() { /** * @param resTopicId Id of the node waiting for the response. * @param filter Query filter. - * @param clsLdrId Class loader ID. - * @param depMode Deployment mode. - * @param userVer User version. - * @param ldrParties Node loader participant map. + * @param depInfo Deployment of the filter classes. */ - GridEventStorageRequest( - IgniteUuid resTopicId, - IgnitePredicate filter, - IgniteUuid clsLdrId, - DeploymentMode depMode, - String userVer, - Map ldrParties) { + GridEventStorageRequest(IgniteUuid resTopicId, IgnitePredicate filter, GridDeploymentInfo depInfo) { this.resTopicId = resTopicId; this.filter = filter; - this.clsLdrId = clsLdrId; - this.depMode = depMode; - this.userVer = userVer; - this.ldrParties = ldrParties; + this.depInfo = new GridDeploymentInfoBean(depInfo); filterClsName = filter.getClass().getName(); } @@ -109,14 +80,11 @@ IgnitePredicate filter() { return filter; } - /** @return Class loader ID. */ - IgniteUuid classLoaderId() { - return clsLdrId; - } - /** @return Deployment mode. */ - DeploymentMode deploymentMode() { - return depMode; + + /** @return Deployment of the filter classes. */ + GridDeploymentInfo deploymentInfo() { + return depInfo; } /** @return Filter class name. */ @@ -124,15 +92,6 @@ String filterClassName() { return filterClsName; } - /** @return User version. */ - String userVersion() { - return userVer; - } - - /** @return Node class loader participant map. */ - @Nullable Map loaderParticipants() { - return ldrParties != null ? Collections.unmodifiableMap(ldrParties) : null; - } /** {@inheritDoc} */ @Override public String toString() { From bc3074288a81d360d20ecbf09d8b9c0014b26eb7 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 02:16:05 +0300 Subject: [PATCH 03/19] Deployment descriptor: carry it whole in the user message Co-Authored-By: Claude Opus 5 --- .../managers/communication/GridIoManager.java | 20 +---- .../communication/GridIoUserMessage.java | 75 ++++--------------- .../eventstorage/GridEventStorageRequest.java | 3 - 3 files changed, 17 insertions(+), 81 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java index ae969bdf6b770..de740ed7da5cb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java @@ -2420,10 +2420,7 @@ public void sendUserMessage(Collection nodes, depClsName, topic, serTopic, - dep != null ? dep.classLoaderId() : null, - dep != null ? dep.deployMode() : null, - dep != null ? dep.userVersion() : null, - dep != null ? dep.participants() : null); + dep); if (ordered) sendOrderedMessageToGridTopic(nodes, TOPIC_COMM_USER, ioMsg, PUBLIC_POOL, timeout, true); @@ -3633,20 +3630,7 @@ private class GridUserMessageListener implements GridMessageListener { if (dep == null && ctx.config().isPeerClassLoadingEnabled() && ioMsg.deploymentClassName() != null) { - dep = ctx.deploy().getGlobalDeployment( - ioMsg.deploymentMode(), - ioMsg.deploymentClassName(), - ioMsg.deploymentClassName(), - ioMsg.userVersion(), - nodeId, - ioMsg.classLoaderId(), - ioMsg.loaderParticipants()); - - if (dep == null) - throw new IgniteDeploymentCheckedException( - "Failed to obtain deployment information for user message. " + - "If you are using custom message or topic class, try implementing " + - "GridPeerDeployAware interface. [msg=" + ioMsg + ']'); + dep = ctx.deploy().globalDeployment(ioMsg.deploymentInfo(), ioMsg.deploymentClassName()); ioMsg.deployment(dep); // Cache deployment. } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java index afe4db51c2b5b..41dc0d6b82034 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java @@ -17,14 +17,11 @@ package org.apache.ignite.internal.managers.communication; -import java.util.Collections; -import java.util.Map; -import java.util.UUID; -import org.apache.ignite.configuration.DeploymentMode; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.UseBinaryMarshaller; import org.apache.ignite.internal.managers.deployment.GridDeployment; -import org.apache.ignite.internal.util.tostring.GridToStringInclude; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.plugin.extensions.communication.Message; @@ -42,34 +39,21 @@ public class GridIoUserMessage implements Message { @Order(0) byte[] bodyBytes; - /** Class loader ID. */ - @Order(1) - IgniteUuid clsLdrId; - /** Message topic. */ private Object topic; /** Serialized message topic. */ - @Order(2) + @Order(1) byte[] topicBytes; - /** Deployment mode. */ - @Order(3) - DeploymentMode depMode; + /** Deployment of the message classes. */ + @Order(2) + GridDeploymentInfoBean depInfo; /** Deployment class name. */ - @Order(4) + @Order(3) String depClsName; - /** User version. */ - @Order(5) - String userVer; - - /** Node class loader participants. */ - @Order(6) - @GridToStringInclude - Map ldrParties; - /** Message deployment. */ private GridDeployment dep; @@ -79,10 +63,7 @@ public class GridIoUserMessage implements Message { * @param depClsName Message body class name. * @param topic Message topic. * @param topicBytes Serialized message topic bytes. - * @param clsLdrId Class loader ID. - * @param depMode Deployment mode. - * @param userVer User version. - * @param ldrParties Node loader participant map. + * @param depInfo Deployment of the message classes. */ GridIoUserMessage( Object body, @@ -90,19 +71,13 @@ public class GridIoUserMessage implements Message { @Nullable String depClsName, @Nullable Object topic, @Nullable byte[] topicBytes, - @Nullable IgniteUuid clsLdrId, - @Nullable DeploymentMode depMode, - @Nullable String userVer, - @Nullable Map ldrParties) { + @Nullable GridDeploymentInfo depInfo) { this.body = body; this.bodyBytes = bodyBytes; this.depClsName = depClsName; this.topic = topic; this.topicBytes = topicBytes; - this.depMode = depMode; - this.clsLdrId = clsLdrId; - this.userVer = userVer; - this.ldrParties = ldrParties; + this.depInfo = depInfo != null ? new GridDeploymentInfoBean(depInfo) : null; } /** @@ -119,19 +94,7 @@ public GridIoUserMessage() { return bodyBytes; } - /** - * @return the Class loader ID. - */ - @Nullable public IgniteUuid classLoaderId() { - return clsLdrId; - } - /** - * @return Deployment mode. - */ - @Nullable public DeploymentMode deploymentMode() { - return depMode; - } /** * @return Message body class name. @@ -140,19 +103,6 @@ public GridIoUserMessage() { return depClsName; } - /** - * @return User version. - */ - @Nullable public String userVersion() { - return userVer; - } - - /** - * @return Node class loader participant map. - */ - @Nullable public Map loaderParticipants() { - return ldrParties != null ? Collections.unmodifiableMap(ldrParties) : null; - } /** * @return Serialized message topic. @@ -182,6 +132,11 @@ public void body(Object body) { this.body = body; } + /** @return Deployment of the message classes, or {@code null} when peer class loading is off. */ + @Nullable public GridDeploymentInfo deploymentInfo() { + return depInfo; + } + /** * @return Message body. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java index 9e039cc12690b..8c9eb0f698506 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java @@ -80,8 +80,6 @@ IgnitePredicate filter() { return filter; } - - /** @return Deployment of the filter classes. */ GridDeploymentInfo deploymentInfo() { return depInfo; @@ -92,7 +90,6 @@ String filterClassName() { return filterClsName; } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridEventStorageRequest.class, this); From 6e50b153e0c8f38ffebdbb0a746f24c2c69cdb08 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 02:36:00 +0300 Subject: [PATCH 04/19] Deployment descriptor: carry it whole in the streamer request Co-Authored-By: Claude Opus 5 --- .../communication/GridIoUserMessage.java | 1 - .../datastreamer/DataStreamProcessor.java | 24 +++---- .../datastreamer/DataStreamerImpl.java | 5 +- .../datastreamer/DataStreamerRequest.java | 72 ++++--------------- .../DataStreamerImplSelfTest.java | 5 +- 5 files changed, 23 insertions(+), 84 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java index 41dc0d6b82034..0600af942e8ce 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java @@ -23,7 +23,6 @@ import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.plugin.extensions.communication.Message; import org.jetbrains.annotations.Nullable; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java index 338bcd59b8bda..a6872d8198a5b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java @@ -22,6 +22,7 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteDeploymentCheckedException; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; @@ -228,21 +229,14 @@ private void processRequest(final UUID nodeId, final DataStreamerRequest req) { if (req.forceLocalDeployment()) clsLdr = U.gridClassLoader(); else { - GridDeployment dep = ctx.deploy().getGlobalDeployment( - req.deploymentMode(), - req.sampleClassName(), - req.sampleClassName(), - req.userVersion(), - nodeId, - req.classLoaderId(), - req.participants()); - - if (dep == null) { - sendResponse(nodeId, - topic, - req.requestId(), - new IgniteCheckedException("Failed to get deployment for request [sndId=" + nodeId + - ", req=" + req + ']')); + GridDeployment dep; + + try { + dep = ctx.deploy().globalDeployment(req.deploymentInfo(), req.sampleClassName()); + } + catch (IgniteDeploymentCheckedException e) { + // The sender waits for an answer, so a missing deployment is reported back, not thrown. + sendResponse(nodeId, topic, req.requestId(), e); return; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java index 9a030b8e3354f..becc47a7e1da5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImpl.java @@ -1995,11 +1995,8 @@ private void submit( true, skipStore, keepBinary, - dep != null ? dep.deployMode() : null, + dep, dep != null ? jobPda0.deployClass().getName() : null, - dep != null ? dep.userVersion() : null, - dep != null ? dep.participants() : null, - dep != null ? dep.classLoaderId() : null, dep == null, topVer, (rcvr == ISOLATED_UPDATER) ? diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java index 0167489c254ee..ef31a50506000 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java @@ -18,17 +18,14 @@ package org.apache.ignite.internal.processors.datastreamer; import java.util.Collection; -import java.util.Map; -import java.util.UUID; -import org.apache.ignite.configuration.DeploymentMode; import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.GridTopicMessage; import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.GridCacheUtils; -import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.plugin.extensions.communication.CacheIdAware; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -70,9 +67,9 @@ public class DataStreamerRequest implements DeferredUnmarshalMessage, CacheIdAwa @Order(7) boolean keepBinary; - /** */ + /** Deployment of the streamed classes. */ @Order(8) - DeploymentMode depMode; + GridDeploymentInfoBean depInfo; /** */ @Order(9) @@ -80,27 +77,14 @@ public class DataStreamerRequest implements DeferredUnmarshalMessage, CacheIdAwa /** */ @Order(10) - String userVer; - - /** Node class loader participants. */ - @GridToStringInclude - @Order(11) - Map ldrParticipants; - - /** */ - @Order(12) - IgniteUuid clsLdrId; - - /** */ - @Order(13) boolean forceLocDep; /** Topology version. */ - @Order(14) + @Order(11) AffinityTopologyVersion topVer; /** */ - @Order(15) + @Order(12) int partId; /** @@ -119,11 +103,8 @@ public DataStreamerRequest() { * @param ignoreDepOwnership Ignore ownership. * @param skipStore Skip store flag. * @param keepBinary Keep binary flag. - * @param depMode Deployment mode. + * @param depInfo Deployment of the streamed classes. * @param sampleClsName Sample class name. - * @param userVer User version. - * @param ldrParticipants Loader participants. - * @param clsLdrId Class loader ID. * @param forceLocDep Force local deployment. * @param topVer Topology version. * @param partId Partition ID. @@ -137,11 +118,8 @@ public DataStreamerRequest( boolean ignoreDepOwnership, boolean skipStore, boolean keepBinary, - DeploymentMode depMode, + GridDeploymentInfo depInfo, String sampleClsName, - String userVer, - Map ldrParticipants, - IgniteUuid clsLdrId, boolean forceLocDep, @NotNull AffinityTopologyVersion topVer, int partId @@ -156,11 +134,8 @@ public DataStreamerRequest( this.ignoreDepOwnership = ignoreDepOwnership; this.skipStore = skipStore; this.keepBinary = keepBinary; - this.depMode = depMode; + this.depInfo = depInfo != null ? new GridDeploymentInfoBean(depInfo) : null; this.sampleClsName = sampleClsName; - this.userVer = userVer; - this.ldrParticipants = ldrParticipants; - this.clsLdrId = clsLdrId; this.forceLocDep = forceLocDep; this.topVer = topVer; this.partId = partId; @@ -222,11 +197,9 @@ boolean keepBinary() { return keepBinary; } - /** - * @return Deployment mode. - */ - DeploymentMode deploymentMode() { - return depMode; + /** @return Deployment of the streamed classes. */ + GridDeploymentInfo deploymentInfo() { + return depInfo; } /** @@ -236,27 +209,6 @@ String sampleClassName() { return sampleClsName; } - /** - * @return User version. - */ - String userVersion() { - return userVer; - } - - /** - * @return Participants. - */ - Map participants() { - return ldrParticipants; - } - - /** - * @return Class loader ID. - */ - IgniteUuid classLoaderId() { - return clsLdrId; - } - /** * @return {@code True} to force local deployment. */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImplSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImplSelfTest.java index f99462429d5bb..6363f761cc922 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImplSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerImplSelfTest.java @@ -697,11 +697,8 @@ private static class StaleTopologyCommunicationSpi extends TcpCommunicationSpi { req.ignoreDeploymentOwnership(), req.skipStore(), req.keepBinary(), - req.deploymentMode(), + req.deploymentInfo(), req.sampleClassName(), - req.userVersion(), - req.participants(), - req.classLoaderId(), req.forceLocalDeployment(), staleTop, -1); From 69cdb79a1dff8485bf0b5ba2876250261313a538 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 02:50:37 +0300 Subject: [PATCH 05/19] Deployment descriptor: carry it whole in the job request Co-Authored-By: Claude Opus 5 --- .../internal/GridJobExecuteRequest.java | 102 +++++------------- .../deployment/GridDeploymentManager.java | 28 +++-- .../processors/job/GridJobProcessor.java | 17 +-- .../processors/task/GridTaskWorker.java | 5 +- 4 files changed, 53 insertions(+), 99 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java index 098e8514f681c..5ddd444fe030d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java @@ -24,10 +24,10 @@ import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.compute.ComputeJob; import org.apache.ignite.compute.ComputeJobSibling; -import org.apache.ignite.configuration.DeploymentMode; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.util.tostring.GridToStringExclude; -import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgnitePredicate; @@ -70,22 +70,17 @@ public class GridJobExecuteRequest implements ExecutorAwareMessage, DeferredUnma @Order(5) String taskName; - /** */ + /** Deployment of the task classes. */ @Order(6) - String userVer; + GridDeploymentInfoBean depInfo; /** */ @Order(7) String taskClsName; - /** Node class loader participants. */ - @GridToStringInclude - @Order(8) - Map ldrParticipants; - /** */ @GridToStringExclude - @Order(9) + @Order(8) byte[] sesAttrsBytes; /** */ @@ -95,7 +90,7 @@ public class GridJobExecuteRequest implements ExecutorAwareMessage, DeferredUnma /** */ @GridToStringExclude - @Order(10) + @Order(9) byte[] jobAttrsBytes; /** */ @@ -104,7 +99,7 @@ public class GridJobExecuteRequest implements ExecutorAwareMessage, DeferredUnma Map jobAttrs; /** Checkpoint SPI name. */ - @Order(11) + @Order(10) String cpSpi; /** Left unset for a continuous task: such a job requests its siblings from the task node instead. */ @@ -112,38 +107,30 @@ public class GridJobExecuteRequest implements ExecutorAwareMessage, DeferredUnma Collection siblings; /** */ - @Order(12) + @Order(11) byte[] siblingsBytes; /** Transient since needs to hold local creation time. */ private final long createTime = U.currentTimeMillis(); /** */ - @Order(13) - IgniteUuid clsLdrId; - - /** */ - @Order(14) - DeploymentMode depMode; - - /** */ - @Order(15) + @Order(12) boolean dynamicSiblings; /** */ - @Order(16) + @Order(13) boolean forceLocDep; /** */ - @Order(17) + @Order(14) boolean sesFullSup; /** */ - @Order(18) + @Order(15) boolean internal; /** */ - @Order(19) + @Order(16) Collection top; /** */ @@ -151,23 +138,23 @@ public class GridJobExecuteRequest implements ExecutorAwareMessage, DeferredUnma IgnitePredicate topPred; /** */ - @Order(20) + @Order(17) byte[] topPredBytes; /** */ - @Order(21) + @Order(18) int[] cacheIds; /** */ - @Order(22) + @Order(19) int part; /** */ - @Order(23) + @Order(20) AffinityTopologyVersion topVer; /** */ - @Order(24) + @Order(21) String execName; /** @@ -181,7 +168,7 @@ public GridJobExecuteRequest() { * @param sesId Task session ID. * @param jobId Job ID. * @param taskName Task name. - * @param userVer Code version. + * @param depInfo Deployment of the task classes. * @param taskClsName Fully qualified task name. * @param job Job. * @param startTaskTime Task execution start time. @@ -192,10 +179,7 @@ public GridJobExecuteRequest() { * @param sesAttrs Session attributes. * @param jobAttrs Job attributes. * @param cpSpi Collision SPI. - * @param clsLdrId Task local class loader id. - * @param depMode Task deployment mode. * @param dynamicSiblings {@code True} if siblings are dynamic. - * @param ldrParticipants Other node class loader IDs that can also load classes. * @param forceLocDep {@code True} If remote node should ignore deployment settings. * @param sesFullSup {@code True} if session attributes are disabled. * @param internal {@code True} if internal job. @@ -208,7 +192,7 @@ public GridJobExecuteRequest( IgniteUuid sesId, IgniteUuid jobId, String taskName, - String userVer, + GridDeploymentInfo depInfo, String taskClsName, ComputeJob job, long startTaskTime, @@ -219,10 +203,7 @@ public GridJobExecuteRequest( Map sesAttrs, Map jobAttrs, String cpSpi, - IgniteUuid clsLdrId, - DeploymentMode depMode, boolean dynamicSiblings, - Map ldrParticipants, boolean forceLocDep, boolean sesFullSup, boolean internal, @@ -238,14 +219,12 @@ public GridJobExecuteRequest( assert sesAttrs != null || !sesFullSup; assert jobAttrs != null; assert top != null || topPred != null; - assert clsLdrId != null; - assert userVer != null; - assert depMode != null; + assert depInfo != null; this.sesId = sesId; this.jobId = jobId; this.taskName = taskName; - this.userVer = userVer; + this.depInfo = new GridDeploymentInfoBean(depInfo); this.taskClsName = taskClsName; this.job = job; this.startTaskTime = startTaskTime; @@ -256,10 +235,7 @@ public GridJobExecuteRequest( this.siblings = dynamicSiblings ? null : siblings; this.sesAttrs = sesAttrs; this.jobAttrs = jobAttrs; - this.clsLdrId = clsLdrId; - this.depMode = depMode; this.dynamicSiblings = dynamicSiblings; - this.ldrParticipants = ldrParticipants; this.forceLocDep = forceLocDep; this.sesFullSup = sesFullSup; this.internal = internal; @@ -285,6 +261,11 @@ public IgniteUuid jobId() { return jobId; } + /** @return Deployment of the task classes. */ + public GridDeploymentInfo deploymentInfo() { + return depInfo; + } + /** * @return Task class name. */ @@ -299,13 +280,6 @@ public String taskName() { return taskName; } - /** - * @return Task version. - */ - public String userVersion() { - return userVer; - } - /** * @return Grid job. */ @@ -364,27 +338,6 @@ public String checkpointSpi() { return cpSpi; } - /** - * @return Task local class loader id. - */ - public IgniteUuid classLoaderId() { - return clsLdrId; - } - - /** - * @return Deployment mode. - */ - public DeploymentMode deploymentMode() { - return depMode; - } - - /** - * @return Node class loader participant map. - */ - public Map loaderParticipants() { - return ldrParticipants; - } - /** * @return Returns {@code true} if deployment should always be used. */ @@ -446,7 +399,6 @@ public AffinityTopologyVersion topologyVersion() { return topVer; } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridJobExecuteRequest.class, this); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java index 5b667572c586b..96d29c9de2bc8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java @@ -414,13 +414,7 @@ private GridDeployment checkDeployment(GridDeployment deployment, String store) */ public GridDeployment globalDeployment(GridDeploymentInfo depInfo, String clsName) throws IgniteDeploymentCheckedException { - GridDeployment dep = getGlobalDeployment(depInfo.deployMode(), - clsName, - clsName, - depInfo.userVersion(), - depInfo.classLoaderId().globalId(), - depInfo.classLoaderId(), - depInfo.participants()); + GridDeployment dep = globalDeployment(depInfo, clsName, clsName); if (dep == null) { throw new IgniteDeploymentCheckedException("Failed to obtain deployment for class (is peer class " + @@ -430,6 +424,26 @@ public GridDeployment globalDeployment(GridDeploymentInfo depInfo, String clsNam return dep; } + /** + * Resolves the deployment {@code depInfo} describes, as {@link #globalDeployment(GridDeploymentInfo, String)} + * does, but under {@code rsrcName} — a task may be deployed under a name of its own — and returns {@code null} + * instead of throwing, for callers that have somewhere else to look. + * + * @param depInfo Deployment of the classes, as it came with the message carrying them. + * @param rsrcName Name the classes are deployed under. + * @param clsName Name of a class the deployment must be able to load. + * @return The deployment, or {@code null} when there is none. + */ + @Nullable public GridDeployment globalDeployment(GridDeploymentInfo depInfo, String rsrcName, String clsName) { + return getGlobalDeployment(depInfo.deployMode(), + rsrcName, + clsName, + depInfo.userVersion(), + depInfo.classLoaderId().globalId(), + depInfo.classLoaderId(), + depInfo.participants()); + } + /** * @param depMode Deployment mode. * @param rsrcName Resource name (could be task name). diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java index 7058c751c0191..a51673caab20e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java @@ -1209,14 +1209,7 @@ public void processJobExecuteRequest(ClusterNode node, final GridJobExecuteReque GridDeployment tmpDep = req.forceLocalDeployment() ? ctx.deploy().getLocalDeployment(req.taskClassName()) : - ctx.deploy().getGlobalDeployment( - req.deploymentMode(), - req.taskName(), - req.taskClassName(), - req.userVersion(), - node.id(), - req.classLoaderId(), - req.loaderParticipants()); + ctx.deploy().globalDeployment(req.deploymentInfo(), req.taskName(), req.taskClassName()); if (tmpDep == null) { if (log.isDebugEnabled()) @@ -1224,7 +1217,7 @@ public void processJobExecuteRequest(ClusterNode node, final GridJobExecuteReque // Check local tasks. for (Map.Entry d : ctx.task().getUsedDeploymentMap().entrySet()) { - if (d.getValue().classLoaderId().equals(req.classLoaderId())) { + if (d.getValue().classLoaderId().equals(req.deploymentInfo().classLoaderId())) { assert d.getValue().local(); tmpDep = d.getValue(); @@ -1283,7 +1276,7 @@ public void processJobExecuteRequest(ClusterNode node, final GridJobExecuteReque catch (IgniteCheckedException e) { IgniteException ex = new IgniteException("Failed to deserialize task attributes " + "[taskName=" + req.taskName() + ", taskClsName=" + req.taskClassName() + - ", codeVer=" + req.userVersion() + ", taskClsLdr=" + dep.classLoader() + ']', e); + ", codeVer=" + req.deploymentInfo().userVersion() + ", taskClsLdr=" + dep.classLoader() + ']', e); U.error(log, ex.getMessage(), e); @@ -1375,9 +1368,7 @@ else if (jobAlwaysActivate) { // Deployment is null. IgniteException ex = new IgniteDeploymentException("Task was not deployed or was redeployed since " + "task execution [taskName=" + req.taskName() + ", taskClsName=" + req.taskClassName() + - ", codeVer=" + req.userVersion() + ", clsLdrId=" + req.classLoaderId() + - ", seqNum=" + req.classLoaderId().localId() + ", depMode=" + req.deploymentMode() + - ", dep=" + dep + ']'); + ", dep=" + req.deploymentInfo() + ", resolved=" + dep + ']'); U.error(log, ex.getMessage(), ex); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java index 0f262d928f5a9..5f62ee2b9f04e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java @@ -1385,7 +1385,7 @@ private void sendRequest(ComputeJobResult res) { ses.getId(), res.getJobContext().getJobId(), ses.getTaskName(), - ses.getUserVersion(), + dep, ses.getTaskClassName(), res.getJob(), ses.getStartTime(), @@ -1396,10 +1396,7 @@ private void sendRequest(ComputeJobResult res) { sesAttrs, jobAttrs, ses.getCheckpointSpi(), - dep.classLoaderId(), - dep.deployMode(), continuous, - dep.participants(), forceLocDep, ses.isFullSupport(), internal, From 2a2839e5673c1459bdf0345766d3585de58f6899 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 03:25:49 +0300 Subject: [PATCH 06/19] IGNITE-28528 Resolve the deployment class loader in generated marshallers A message that carries classes deployed on another node now says so with DeploymentAware, the same way CacheIdAware states the need for a cache context. The generated marshaller asks the deployment manager for the class loader, so a receiver no longer repeats that resolution by hand. Co-Authored-By: Claude Opus 5 --- .../internal/MessageMarshallerGenerator.java | 40 ++++++++++++++++++- .../ignite/internal/MessageProcessor.java | 6 +++ .../ignite/internal/DeploymentAware.java | 36 +++++++++++++++++ .../deployment/GridDeploymentManager.java | 16 ++++++++ .../eventstorage/GridEventStorageManager.java | 7 ++-- .../eventstorage/GridEventStorageRequest.java | 12 ++++-- .../continuous/StartRequestData.java | 38 ++++++++++-------- 7 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 34e6054141712..468f146a55654 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -46,6 +46,8 @@ import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.MessageProcessor.CACHE_OBJECT_CLS; +import static org.apache.ignite.internal.MessageProcessor.DEFERRED_UNMARSHAL_MESSAGE_INTERFACE; +import static org.apache.ignite.internal.MessageProcessor.DEPLOYMENT_AWARE_MESSAGE_INTERFACE; import static org.apache.ignite.internal.MessageProcessor.IGNITE_CHECKED_EXCEPTION_CLS; import static org.apache.ignite.internal.MessageProcessor.KEY_CACHE_OBJECT_CLS; import static org.apache.ignite.internal.MessageProcessor.MARSHALLABLE_MESSAGE_INTERFACE; @@ -96,6 +98,12 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { /** */ private final TypeMirror nonMarshallableType; + /** */ + private final TypeMirror deploymentAwareMsgType; + + /** */ + private final TypeMirror deferredUnmarshalMsgType; + /** */ private final TypeMirror selfMarshallingMsgType; @@ -140,6 +148,8 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { msgType = type(MESSAGE_INTERFACE); cacheObjType = type(CACHE_OBJECT_CLS); nonMarshallableType = type(NON_MARSHALLABLE_MESSAGE_INTERFACE); + deploymentAwareMsgType = type(DEPLOYMENT_AWARE_MESSAGE_INTERFACE); + deferredUnmarshalMsgType = type(DEFERRED_UNMARSHAL_MESSAGE_INTERFACE); selfMarshallingMsgType = type(SELF_MARSHALLING_MESSAGE_INTERFACE); cacheGrpIdMsgType = type(GRID_CACHE_GROUP_ID_MESSAGE_CLS); mapType = type(Map.class.getName()); @@ -306,6 +316,9 @@ private void generateUnmarshalMethod(String params, List fields if (needsCtx(fields) || !wireFieldSkip.isEmpty()) appendBlock(body, List.of(ctxResolutionLine())); + if (isDeploymentAware()) + appendBlock(body, List.of(deploymentResolutionLine())); + appendFields(body, fields, MarshalMode.UNMARSHAL, wireFieldSkip); if (marshallable) @@ -472,11 +485,15 @@ private void appendMarshalledFinish(List body) { indent++; code.add(indentedLine("%s = U.unmarshal(marshaller, %s, clsLdr);", objAcc, bytesAcc)); - code.add(EMPTY); // Drop the serialized cache once the object is restored: keeping both the deserialized value and its bytes // on every received message doubles retained memory (e.g. topology history nodes) and can exhaust the heap. - code.add(indentedLine("%s = null;", bytesAcc)); + // A message unmarshalled by its owner may be sent on (a discovery message travels the whole ring), so its + // bytes stay: re-marshalling the user object on the sending path is what they are there to avoid. + if (!isDeferredUnmarshal()) { + code.add(EMPTY); + code.add(indentedLine("%s = null;", bytesAcc)); + } indent--; @@ -1039,6 +1056,25 @@ else if (isCacheGroupIdMessage(type)) return indentedLine("CacheObjectContext ctx = cacheObjCtx;"); } + /** + * Returns the lines resolving the class loader of a {@code DeploymentAware} message: the caller may pass one, and + * when it does not, the deployment the message carries gives it. Mirrors {@link #ctxResolutionLine()}. + */ + private String deploymentResolutionLine() { + return indentedLine("if (clsLdr == null)") + NL + + indentedLine(" clsLdr = kctx.deploy().classLoader(msg);"); + } + + /** @return {@code true} if the message is unmarshalled by its owner instead of the receiving path. */ + private boolean isDeferredUnmarshal() { + return deferredUnmarshalMsgType != null && assignableFrom(type.asType(), deferredUnmarshalMsgType); + } + + /** @return {@code true} if the message carries the deployment of its classes. */ + private boolean isDeploymentAware() { + return deploymentAwareMsgType != null && assignableFrom(type.asType(), deploymentAwareMsgType); + } + /** Returns {@code true} if any field requires {@code ctx} in generated marshal/unmarshal code. */ private boolean needsCtx(List fields) { return fields.stream().anyMatch(f -> needsCtxType(f.asType())); diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java index 4886bb491e47b..414771f0b5160 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java @@ -87,6 +87,12 @@ public class MessageProcessor extends AbstractProcessor { /** Message that reshapes its own fields before they go on the wire. */ static final String SELF_MARSHALLING_MESSAGE_INTERFACE = "org.apache.ignite.internal.SelfMarshallingMessage"; + /** Message that carries the deployment of the classes inside it. */ + static final String DEPLOYMENT_AWARE_MESSAGE_INTERFACE = "org.apache.ignite.internal.DeploymentAware"; + + /** Message unmarshalled by its owner instead of the receiving path. */ + static final String DEFERRED_UNMARSHAL_MESSAGE_INTERFACE = "org.apache.ignite.internal.DeferredUnmarshalMessage"; + /** Marker of messages with no marshaller. */ static final String NON_MARSHALLABLE_MESSAGE_INTERFACE = "org.apache.ignite.plugin.extensions.communication.NonMarshallableMessage"; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java new file mode 100644 index 0000000000000..38117874a004c --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; +import org.apache.ignite.plugin.extensions.communication.Message; + +/** + * Implemented by messages that carry classes deployed from another node. The deployment lets the generated marshaller + * resolve the class loader those classes are read with, the same way {@code CacheIdAware} lets it resolve the cache + * object context. + * + * @see MarshallableMessage + */ +public interface DeploymentAware extends Message { + /** @return Deployment of the classes the message carries. */ + public GridDeploymentInfo deploymentInfo(); + + /** @return Name of a class the deployment must be able to load. */ + public String deployedClassName(); +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java index 96d29c9de2bc8..cf529a035a082 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java @@ -26,6 +26,7 @@ import org.apache.ignite.compute.ComputeTask; import org.apache.ignite.compute.ComputeTaskName; import org.apache.ignite.configuration.DeploymentMode; +import org.apache.ignite.internal.DeploymentAware; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteDeploymentCheckedException; import org.apache.ignite.internal.IgniteInternalFuture; @@ -412,6 +413,21 @@ private GridDeployment checkDeployment(GridDeployment deployment, String store) * @return The deployment the classes are loaded with. * @throws IgniteDeploymentCheckedException If the deployment is gone or peer class loading is off. */ + /** + * Resolves the class loader the classes of a message must be read with. + * + * @param msg Message carrying its own deployment. + * @return Class loader of the carried deployment, or the local one if the message carries none. + * @throws IgniteDeploymentCheckedException If the deployment cannot be obtained. + */ + public ClassLoader classLoader(DeploymentAware msg) throws IgniteDeploymentCheckedException { + if (msg.deploymentInfo() == null) + return U.resolveClassLoader(ctx.config()); + + return U.resolveClassLoader(globalDeployment(msg.deploymentInfo(), msg.deployedClassName()).classLoader(), + ctx.config()); + } + public GridDeployment globalDeployment(GridDeploymentInfo depInfo, String clsName) throws IgniteDeploymentCheckedException { GridDeployment dep = globalDeployment(depInfo, clsName, clsName); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java index 38c6be5aa6738..5c6d373f527c1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java @@ -1242,12 +1242,13 @@ private class RequestListener implements GridMessageListener { Collection evts; try { - GridDeployment dep = ctx.deploy().globalDeployment(req.deploymentInfo(), req.filterClassName()); - - MessageMarshalling.unmarshal(req, ctx, null, U.resolveClassLoader(dep.classLoader(), ctx.config())); + // No class loader: the message carries its own deployment, see GridEventStorageRequest. + MessageMarshalling.unmarshal(req, ctx, null, null); filter = (IgnitePredicate)req.filter(); + GridDeployment dep = ctx.deploy().globalDeployment(req.deploymentInfo(), req.filterClassName()); + // Resource injection. ctx.resource().inject(dep, dep.deployedClass(req.filterClassName()).get1(), filter); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java index 8c9eb0f698506..d5772172b591d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.managers.eventstorage; import org.apache.ignite.internal.DeferredUnmarshalMessage; +import org.apache.ignite.internal.DeploymentAware; import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.UseBinaryMarshaller; @@ -31,7 +32,7 @@ /** Remote event query. The filter is a user class, hence the deferred unmarshalling. */ @UseBinaryMarshaller -public class GridEventStorageRequest implements DeferredUnmarshalMessage { +public class GridEventStorageRequest implements DeploymentAware, DeferredUnmarshalMessage { /** */ @Order(0) IgniteUuid resTopicId; @@ -80,11 +81,16 @@ IgnitePredicate filter() { return filter; } - /** @return Deployment of the filter classes. */ - GridDeploymentInfo deploymentInfo() { + /** {@inheritDoc} */ + @Override public GridDeploymentInfo deploymentInfo() { return depInfo; } + /** {@inheritDoc} */ + @Override public String deployedClassName() { + return filterClsName; + } + /** @return Filter class name. */ String filterClassName() { return filterClsName; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index 9792739d13c55..cf695cac99841 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -20,9 +20,13 @@ import java.util.UUID; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.DeferredUnmarshalMessage; +import org.apache.ignite.internal.DeploymentAware; import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.managers.deployment.GridDeployment; +import org.apache.ignite.internal.managers.communication.MessageMarshalling; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryHandler; import org.apache.ignite.internal.util.typedef.internal.S; @@ -33,9 +37,10 @@ /** * Start request data. */ -public class StartRequestData implements Message { +public class StartRequestData implements DeploymentAware, DeferredUnmarshalMessage { /** Node filter. */ - private IgnitePredicate nodeFilter; + @Marshalled("nodeFilterBytes") + IgnitePredicate nodeFilter; /** Serialized node filter. */ @Order(0) @@ -169,6 +174,16 @@ public void autoUnsubscribe(boolean autoUnsubscribe) { return S.toString(StartRequestData.class, this); } + /** {@inheritDoc} */ + @Override public GridDeploymentInfo deploymentInfo() { + return depInfo; + } + + /** {@inheritDoc} */ + @Override public String deployedClassName() { + return clsName; + } + /** */ public void marshal(GridKernalContext ctx) throws IgniteCheckedException { if (hnd != null) { @@ -180,24 +195,13 @@ public void marshal(GridKernalContext ctx) throws IgniteCheckedException { hndBytes = U.marshal(ctx.marshaller(), hnd); } - if (nodeFilter != null) - nodeFilterBytes = U.marshal(ctx.marshaller(), nodeFilter); + MessageMarshalling.marshal(this, ctx, null); } /** */ public void unmarshal(GridKernalContext ctx, UUID sndId) throws IgniteCheckedException { - if (ctx.config().isPeerClassLoadingEnabled() && clsName != null) { - GridDeployment dep = ctx.deploy().globalDeployment(depInfo, clsName); - - nodeFilter = U.unmarshal(ctx.marshaller(), - nodeFilterBytes, - U.resolveClassLoader(dep.classLoader(), ctx.config())); - } - else { - nodeFilter = U.unmarshal(ctx.marshaller(), - nodeFilterBytes, - U.resolveClassLoader(ctx.config())); - } + // No class loader: the node filter is read with the deployment this message carries. + MessageMarshalling.unmarshal(this, ctx, null, null); if (hndBytes != null) { hnd = U.unmarshal(ctx.marshaller(), hndBytes, U.resolveClassLoader(ctx.config())); From 1c043959d4615fc5f751c81547ef6881c689fb72 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 03:48:52 +0300 Subject: [PATCH 07/19] IGNITE-28528 Self-review fixes Keeping the serialized copy is now a property of the field, @Marshalled keepBytes, instead of a property of the deferred-unmarshal marker: the marker covers GridCacheMessage, so tying it there dropped the memory saving for every cache message. Restores the diagnostics of two error paths, adds a codegen test for the resolved class loader and for keepBytes. Co-Authored-By: Claude Opus 5 --- .../apache/ignite/internal/Marshalled.java | 7 ++ .../internal/MessageMarshallerGenerator.java | 35 +++--- .../managers/communication/GridIoManager.java | 10 +- .../communication/GridIoUserMessage.java | 3 - .../deployment/GridDeploymentManager.java | 18 +-- .../eventstorage/GridEventStorageManager.java | 4 +- .../eventstorage/GridEventStorageRequest.java | 5 - .../continuous/StartRequestData.java | 6 +- .../datastreamer/DataStreamProcessor.java | 4 +- .../codegen/MessageProcessorTest.java | 18 +++ .../codegen/TestDeploymentAwareMessage.java | 53 +++++++++ .../TestDeploymentAwareMessageMarshaller.java | 77 ++++++++++++ .../TestDeploymentAwareMessageSerializer.java | 112 ++++++++++++++++++ 13 files changed, 309 insertions(+), 43 deletions(-) create mode 100644 modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java create mode 100644 modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java create mode 100644 modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java index 62526f4e03906..257f5474a29d8 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java @@ -46,4 +46,11 @@ /** Name of the map-values companion wire field; requires {@link #keys()}. */ String values() default ""; + + /** + * Keeps the serialized copy once the object is restored, instead of dropping it. Needed when the message is sent + * again after being read: a discovery message travels the whole ring, and marshalling a user object on that path + * is exactly what the copy is there to avoid. + */ + boolean keepBytes() default false; } diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 468f146a55654..5be3af565959c 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.BiFunction; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; @@ -46,7 +45,6 @@ import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.MessageProcessor.CACHE_OBJECT_CLS; -import static org.apache.ignite.internal.MessageProcessor.DEFERRED_UNMARSHAL_MESSAGE_INTERFACE; import static org.apache.ignite.internal.MessageProcessor.DEPLOYMENT_AWARE_MESSAGE_INTERFACE; import static org.apache.ignite.internal.MessageProcessor.IGNITE_CHECKED_EXCEPTION_CLS; import static org.apache.ignite.internal.MessageProcessor.KEY_CACHE_OBJECT_CLS; @@ -101,9 +99,6 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { /** */ private final TypeMirror deploymentAwareMsgType; - /** */ - private final TypeMirror deferredUnmarshalMsgType; - /** */ private final TypeMirror selfMarshallingMsgType; @@ -149,7 +144,6 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { cacheObjType = type(CACHE_OBJECT_CLS); nonMarshallableType = type(NON_MARSHALLABLE_MESSAGE_INTERFACE); deploymentAwareMsgType = type(DEPLOYMENT_AWARE_MESSAGE_INTERFACE); - deferredUnmarshalMsgType = type(DEFERRED_UNMARSHAL_MESSAGE_INTERFACE); selfMarshallingMsgType = type(SELF_MARSHALLING_MESSAGE_INTERFACE); cacheGrpIdMsgType = type(GRID_CACHE_GROUP_ID_MESSAGE_CLS); mapType = type(Map.class.getName()); @@ -460,7 +454,7 @@ private void appendMapPrepare(List body, VariableElement field) { /** Generates {@code U.marshal} calls for all {@code @Marshalled} fields in marshal. */ private void appendMarshalledPrepare(List body) { - forEachMarshalled((bytesAcc, objAcc) -> { + forEachMarshalled((bytesAcc, objAcc, ann) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null && %s == null)", objAcc, bytesAcc)); @@ -477,7 +471,7 @@ private void appendMarshalledPrepare(List body) { /** Generates {@code U.unmarshal} calls for all {@code @Marshalled} fields in the cache-aware unmarshal. */ private void appendMarshalledFinish(List body) { - forEachMarshalled((bytesAcc, objAcc) -> { + forEachMarshalled((bytesAcc, objAcc, ann) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null) {", bytesAcc)); @@ -488,9 +482,7 @@ private void appendMarshalledFinish(List body) { // Drop the serialized cache once the object is restored: keeping both the deserialized value and its bytes // on every received message doubles retained memory (e.g. topology history nodes) and can exhaust the heap. - // A message unmarshalled by its owner may be sent on (a discovery message travels the whole ring), so its - // bytes stay: re-marshalling the user object on the sending path is what they are there to avoid. - if (!isDeferredUnmarshal()) { + if (!ann.keepBytes()) { code.add(EMPTY); code.add(indentedLine("%s = null;", bytesAcc)); } @@ -1065,11 +1057,6 @@ private String deploymentResolutionLine() { + indentedLine(" clsLdr = kctx.deploy().classLoader(msg);"); } - /** @return {@code true} if the message is unmarshalled by its owner instead of the receiving path. */ - private boolean isDeferredUnmarshal() { - return deferredUnmarshalMsgType != null && assignableFrom(type.asType(), deferredUnmarshalMsgType); - } - /** @return {@code true} if the message carries the deployment of its classes. */ private boolean isDeploymentAware() { return deploymentAwareMsgType != null && assignableFrom(type.asType(), deploymentAwareMsgType); @@ -1217,17 +1204,29 @@ private VariableElement requireEnclosed(Map enclosed, S } /** Iterates all {@code @Marshalled} fields and applies {@code codeGen(bytesAccessor, objAccessor)} to each. */ - private void forEachMarshalled(BiFunction> codeGen, List body) { + private void forEachMarshalled(MarshalledCode codeGen, List body) { for (VariableElement field : enclosed.values()) { if (kinds.get(field) != MarshalledKind.BLOB) continue; Marshalled ann = field.getAnnotation(Marshalled.class); - appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName())); + appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName(), ann)); } } + /** Generates the code handling a single {@code @Marshalled} field. */ + @FunctionalInterface + private interface MarshalledCode { + /** + * @param bytesAcc Accessor of the companion field holding the serialized form. + * @param objAcc Accessor of the annotated field. + * @param ann Annotation of the field. + * @return Generated lines. + */ + public List apply(String bytesAcc, String objAcc, Marshalled ann); + } + /** Returns the element for {@code t}; for a type variable, uses its upper bound. */ private Element element(TypeMirror t) { return t.getKind() == TypeKind.DECLARED diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java index de740ed7da5cb..adedf983b76b6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java @@ -3630,7 +3630,15 @@ private class GridUserMessageListener implements GridMessageListener { if (dep == null && ctx.config().isPeerClassLoadingEnabled() && ioMsg.deploymentClassName() != null) { - dep = ctx.deploy().globalDeployment(ioMsg.deploymentInfo(), ioMsg.deploymentClassName()); + dep = ctx.deploy().globalDeployment(ioMsg.deploymentInfo(), ioMsg.deploymentClassName(), + ioMsg.deploymentClassName()); + + if (dep == null) { + throw new IgniteDeploymentCheckedException( + "Failed to obtain deployment information for user message. " + + "If you are using custom message or topic class, try implementing " + + "GridPeerDeployAware interface. [msg=" + ioMsg + ']'); + } ioMsg.deployment(dep); // Cache deployment. } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java index 0600af942e8ce..c58eb9802aa92 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoUserMessage.java @@ -93,8 +93,6 @@ public GridIoUserMessage() { return bodyBytes; } - - /** * @return Message body class name. */ @@ -102,7 +100,6 @@ public GridIoUserMessage() { return depClsName; } - /** * @return Serialized message topic. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java index cf529a035a082..3caaba1856d11 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java @@ -404,15 +404,6 @@ private GridDeployment checkDeployment(GridDeployment deployment, String store) return locStore.getDeployment(meta); } - /** - * Resolves the deployment {@code depInfo} describes, for the classes of {@code clsName}. The sender of those - * classes is the node that created the class loader, or a participant when the deployment has any. - * - * @param depInfo Deployment of the classes, as it came with the message carrying them. - * @param clsName Name of a class the deployment must be able to load. - * @return The deployment the classes are loaded with. - * @throws IgniteDeploymentCheckedException If the deployment is gone or peer class loading is off. - */ /** * Resolves the class loader the classes of a message must be read with. * @@ -428,6 +419,15 @@ public ClassLoader classLoader(DeploymentAware msg) throws IgniteDeploymentCheck ctx.config()); } + /** + * Resolves the deployment {@code depInfo} describes, for the classes of {@code clsName}. The sender of those + * classes is the node that created the class loader, or a participant when the deployment has any. + * + * @param depInfo Deployment of the classes, as it came with the message carrying them. + * @param clsName Name of a class the deployment must be able to load. + * @return The deployment the classes are loaded with. + * @throws IgniteDeploymentCheckedException If the deployment is gone or peer class loading is off. + */ public GridDeployment globalDeployment(GridDeploymentInfo depInfo, String clsName) throws IgniteDeploymentCheckedException { GridDeployment dep = globalDeployment(depInfo, clsName, clsName); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java index 5c6d373f527c1..0ead54b373af8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java @@ -1247,10 +1247,10 @@ private class RequestListener implements GridMessageListener { filter = (IgnitePredicate)req.filter(); - GridDeployment dep = ctx.deploy().globalDeployment(req.deploymentInfo(), req.filterClassName()); + GridDeployment dep = ctx.deploy().globalDeployment(req.deploymentInfo(), req.deployedClassName()); // Resource injection. - ctx.resource().inject(dep, dep.deployedClass(req.filterClassName()).get1(), filter); + ctx.resource().inject(dep, dep.deployedClass(req.deployedClassName()).get1(), filter); // Get local events. evts = localEvents(filter); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java index d5772172b591d..2b70456708fb6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageRequest.java @@ -91,11 +91,6 @@ IgnitePredicate filter() { return filterClsName; } - /** @return Filter class name. */ - String filterClassName() { - return filterClsName; - } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridEventStorageRequest.class, this); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index cf695cac99841..9f117af883318 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -20,7 +20,6 @@ import java.util.UUID; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cluster.ClusterNode; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.DeploymentAware; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.Marshalled; @@ -32,14 +31,13 @@ import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgnitePredicate; -import org.apache.ignite.plugin.extensions.communication.Message; /** * Start request data. */ -public class StartRequestData implements DeploymentAware, DeferredUnmarshalMessage { +public class StartRequestData implements DeploymentAware { /** Node filter. */ - @Marshalled("nodeFilterBytes") + @Marshalled(value = "nodeFilterBytes", keepBytes = true) IgnitePredicate nodeFilter; /** Serialized node filter. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java index a6872d8198a5b..5961fb1179a83 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamProcessor.java @@ -236,7 +236,9 @@ private void processRequest(final UUID nodeId, final DataStreamerRequest req) { } catch (IgniteDeploymentCheckedException e) { // The sender waits for an answer, so a missing deployment is reported back, not thrown. - sendResponse(nodeId, topic, req.requestId(), e); + sendResponse(nodeId, topic, req.requestId(), + new IgniteCheckedException("Failed to get deployment for request [sndId=" + nodeId + + ", req=" + req + ']', e)); return; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java index 43aa5b967eda1..c1ab5e7184bb1 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java @@ -503,6 +503,24 @@ public void testNioFieldNeedingCacheContextFails() { assertThat(compilation).hadErrorContaining("needs a cache object context to unmarshal"); } + /** A message carrying its own deployment resolves the class loader; a field may ask to keep its serialized copy. */ + @Test + public void testDeploymentAwareMessage() { + Compilation compilation = compile("TestDeploymentAwareMessage.java"); + + assertThat(compilation).succeeded(); + + assertEquals(2, compilation.generatedSourceFiles().size()); + + assertThat(compilation) + .generatedSourceFile("org.apache.ignite.internal.TestDeploymentAwareMessageSerializer") + .hasSourceEquivalentTo(javaFile("TestDeploymentAwareMessageSerializer.java")); + + assertThat(compilation) + .generatedSourceFile("org.apache.ignite.internal.TestDeploymentAwareMessageMarshaller") + .hasSourceEquivalentTo(javaFile("TestDeploymentAwareMessageMarshaller.java")); + } + /** Verifies that {@code @Marshalled} generates {@code U.unmarshal} with a blank line before the null-out. */ @Test public void testMarshalledMessage() { diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java new file mode 100644 index 0000000000000..035be77f595a9 --- /dev/null +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; + +public class TestDeploymentAwareMessage implements DeploymentAware { + @Marshalled("dataBytes") + Object data; + + @Order(0) + byte[] dataBytes; + + @Marshalled(value = "keptBytes", keepBytes = true) + Object kept; + + @Order(1) + byte[] keptBytes; + + @Order(2) + GridDeploymentInfoBean depInfo; + + @Order(3) + String clsName; + + @Override public GridDeploymentInfo deploymentInfo() { + return depInfo; + } + + @Override public String deployedClassName() { + return clsName; + } + + public short directType() { + return 0; + } +} diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java new file mode 100644 index 0000000000000..c4a9971cdbe7a --- /dev/null +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.TestDeploymentAwareMessage; +import org.apache.ignite.internal.managers.communication.MessageMarshalling; +import org.apache.ignite.internal.processors.cache.CacheObjectContext; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.marshaller.Marshaller; +import org.apache.ignite.plugin.extensions.communication.MessageMarshaller; + +/** + * This class is generated automatically. + * + * @see org.apache.ignite.internal.MessageProcessor + */ +public final class TestDeploymentAwareMessageMarshaller implements MessageMarshaller { + /** */ + private final Marshaller marshaller; + + /** */ + public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { + this.marshaller = marshaller; + } + + /** */ + @Override public void marshal(TestDeploymentAwareMessage msg, GridKernalContext kctx, CacheObjectContext cacheObjCtx) throws IgniteCheckedException { + CacheObjectContext ctx = cacheObjCtx; + + if (msg.data != null && msg.dataBytes == null) + msg.dataBytes = U.marshal(marshaller, msg.data); + + if (msg.kept != null && msg.keptBytes == null) + msg.keptBytes = U.marshal(marshaller, msg.kept); + + if (msg.depInfo != null) + MessageMarshalling.marshal(msg.depInfo, kctx, ctx); + } + + /** */ + @Override public void unmarshal(TestDeploymentAwareMessage msg, GridKernalContext kctx, CacheObjectContext cacheObjCtx, ClassLoader clsLdr) throws IgniteCheckedException { + CacheObjectContext ctx = cacheObjCtx; + + if (clsLdr == null) + clsLdr = kctx.deploy().classLoader(msg); + + if (msg.depInfo != null) + MessageMarshalling.unmarshal(msg.depInfo, kctx, ctx, clsLdr); + + if (msg.dataBytes != null) { + msg.data = U.unmarshal(marshaller, msg.dataBytes, clsLdr); + + msg.dataBytes = null; + } + + if (msg.keptBytes != null) { + msg.kept = U.unmarshal(marshaller, msg.keptBytes, clsLdr); + } + } +} \ No newline at end of file diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java new file mode 100644 index 0000000000000..c3758550a3b59 --- /dev/null +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +import org.apache.ignite.internal.TestDeploymentAwareMessage; +import org.apache.ignite.plugin.extensions.communication.MessageReader; +import org.apache.ignite.plugin.extensions.communication.MessageSerializer; +import org.apache.ignite.plugin.extensions.communication.MessageWriter; + +/** + * This class is generated automatically. + * + * @see org.apache.ignite.internal.MessageProcessor + */ +public final class TestDeploymentAwareMessageSerializer implements MessageSerializer { + /** */ + @Override public final boolean writeTo(TestDeploymentAwareMessage msg, MessageWriter writer) { + if (!writer.isHeaderWritten()) { + if (!writer.writeHeader(msg.directType())) + return false; + + writer.onHeaderWritten(); + } + + switch (writer.state()) { + case 0: + if (!writer.writeByteArray(msg.dataBytes)) + return false; + + writer.incrementState(); + + case 1: + if (!writer.writeByteArray(msg.keptBytes)) + return false; + + writer.incrementState(); + + case 2: + if (!writer.writeMessage(msg.depInfo)) + return false; + + writer.incrementState(); + + case 3: + if (!writer.writeString(msg.clsName)) + return false; + + writer.incrementState(); + } + + return true; + } + + /** */ + @Override public final boolean readFrom(TestDeploymentAwareMessage msg, MessageReader reader) { + switch (reader.state()) { + case 0: + msg.dataBytes = reader.readByteArray(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + + case 1: + msg.keptBytes = reader.readByteArray(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + + case 2: + msg.depInfo = reader.readMessage(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + + case 3: + msg.clsName = reader.readString(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + } + + return true; + } + + /** {@inheritDoc} */ + @Override public final TestDeploymentAwareMessage createMessage() { + return new TestDeploymentAwareMessage(); + } +} \ No newline at end of file From 9f69ae4c6b82d02e99c1174345a41337e5eb1eb2 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 04:04:07 +0300 Subject: [PATCH 08/19] IGNITE-28528 Keep the node filter off the socket-reading thread Marking StartRequestData deployment-aware put the deployment resolution into its generated unmarshal, and that unmarshal runs from the discovery socket reader, where obtaining a deployment may block on a network request. The message keeps its deployment as a plain field instead and asks the deployment manager for the loader where blocking is allowed. Co-Authored-By: Claude Opus 5 --- .../apache/ignite/internal/Marshalled.java | 7 ----- .../internal/MessageMarshallerGenerator.java | 27 +++++-------------- .../ignite/internal/DeploymentAware.java | 4 +++ .../deployment/GridDeploymentManager.java | 19 ++++++++++--- .../continuous/StartRequestData.java | 26 +++++------------- .../codegen/MessageProcessorTest.java | 2 +- .../codegen/TestDeploymentAwareMessage.java | 8 +----- .../TestDeploymentAwareMessageMarshaller.java | 7 ----- .../TestDeploymentAwareMessageSerializer.java | 18 ++----------- 9 files changed, 37 insertions(+), 81 deletions(-) diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java index 257f5474a29d8..62526f4e03906 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java @@ -46,11 +46,4 @@ /** Name of the map-values companion wire field; requires {@link #keys()}. */ String values() default ""; - - /** - * Keeps the serialized copy once the object is restored, instead of dropping it. Needed when the message is sent - * again after being read: a discovery message travels the whole ring, and marshalling a user object on that path - * is exactly what the copy is there to avoid. - */ - boolean keepBytes() default false; } diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 5be3af565959c..1c131f366a65d 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.function.BiFunction; import java.util.Map; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; @@ -454,7 +455,7 @@ private void appendMapPrepare(List body, VariableElement field) { /** Generates {@code U.marshal} calls for all {@code @Marshalled} fields in marshal. */ private void appendMarshalledPrepare(List body) { - forEachMarshalled((bytesAcc, objAcc, ann) -> { + forEachMarshalled((bytesAcc, objAcc) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null && %s == null)", objAcc, bytesAcc)); @@ -471,7 +472,7 @@ private void appendMarshalledPrepare(List body) { /** Generates {@code U.unmarshal} calls for all {@code @Marshalled} fields in the cache-aware unmarshal. */ private void appendMarshalledFinish(List body) { - forEachMarshalled((bytesAcc, objAcc, ann) -> { + forEachMarshalled((bytesAcc, objAcc) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null) {", bytesAcc)); @@ -479,13 +480,11 @@ private void appendMarshalledFinish(List body) { indent++; code.add(indentedLine("%s = U.unmarshal(marshaller, %s, clsLdr);", objAcc, bytesAcc)); + code.add(EMPTY); // Drop the serialized cache once the object is restored: keeping both the deserialized value and its bytes // on every received message doubles retained memory (e.g. topology history nodes) and can exhaust the heap. - if (!ann.keepBytes()) { - code.add(EMPTY); - code.add(indentedLine("%s = null;", bytesAcc)); - } + code.add(indentedLine("%s = null;", bytesAcc)); indent--; @@ -1204,29 +1203,17 @@ private VariableElement requireEnclosed(Map enclosed, S } /** Iterates all {@code @Marshalled} fields and applies {@code codeGen(bytesAccessor, objAccessor)} to each. */ - private void forEachMarshalled(MarshalledCode codeGen, List body) { + private void forEachMarshalled(BiFunction> codeGen, List body) { for (VariableElement field : enclosed.values()) { if (kinds.get(field) != MarshalledKind.BLOB) continue; Marshalled ann = field.getAnnotation(Marshalled.class); - appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName(), ann)); + appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName())); } } - /** Generates the code handling a single {@code @Marshalled} field. */ - @FunctionalInterface - private interface MarshalledCode { - /** - * @param bytesAcc Accessor of the companion field holding the serialized form. - * @param objAcc Accessor of the annotated field. - * @param ann Annotation of the field. - * @return Generated lines. - */ - public List apply(String bytesAcc, String objAcc, Marshalled ann); - } - /** Returns the element for {@code t}; for a type variable, uses its upper bound. */ private Element element(TypeMirror t) { return t.getKind() == TypeKind.DECLARED diff --git a/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java index 38117874a004c..bd8d14a5877d4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java @@ -24,6 +24,10 @@ * Implemented by messages that carry classes deployed from another node. The deployment lets the generated marshaller * resolve the class loader those classes are read with, the same way {@code CacheIdAware} lets it resolve the cache * object context. + *

+ * Resolving may have to request the deployment from its owner and block, so a message stating this must be unmarshalled + * where blocking is allowed: not from a socket-reading thread. A message that cannot promise that keeps its deployment + * as a plain field and asks {@code GridDeploymentManager} for the loader itself, as {@code StartRequestData} does. * * @see MarshallableMessage */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java index 3caaba1856d11..f23627df270c2 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java @@ -412,11 +412,24 @@ private GridDeployment checkDeployment(GridDeployment deployment, String store) * @throws IgniteDeploymentCheckedException If the deployment cannot be obtained. */ public ClassLoader classLoader(DeploymentAware msg) throws IgniteDeploymentCheckedException { - if (msg.deploymentInfo() == null) + return classLoader(msg.deploymentInfo(), msg.deployedClassName()); + } + + /** + * Resolves the class loader classes described by {@code depInfo} must be read with. Blocks when the deployment has + * to be requested from its owner, so it must not be called from a socket-reading thread. + * + * @param depInfo Deployment of the classes, or {@code null} when they carry none. + * @param clsName Name of a class the deployment must be able to load. + * @return Class loader of the deployment, or the local one when there is no deployment. + * @throws IgniteDeploymentCheckedException If the deployment cannot be obtained. + */ + public ClassLoader classLoader(@Nullable GridDeploymentInfo depInfo, String clsName) + throws IgniteDeploymentCheckedException { + if (depInfo == null) return U.resolveClassLoader(ctx.config()); - return U.resolveClassLoader(globalDeployment(msg.deploymentInfo(), msg.deployedClassName()).classLoader(), - ctx.config()); + return U.resolveClassLoader(globalDeployment(depInfo, clsName).classLoader(), ctx.config()); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index 9f117af883318..aae9f30d080e3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -20,25 +20,21 @@ import java.util.UUID; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cluster.ClusterNode; -import org.apache.ignite.internal.DeploymentAware; import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.managers.communication.MessageMarshalling; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryHandler; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgnitePredicate; +import org.apache.ignite.plugin.extensions.communication.Message; /** * Start request data. */ -public class StartRequestData implements DeploymentAware { +public class StartRequestData implements Message { /** Node filter. */ - @Marshalled(value = "nodeFilterBytes", keepBytes = true) - IgnitePredicate nodeFilter; + private IgnitePredicate nodeFilter; /** Serialized node filter. */ @Order(0) @@ -172,16 +168,6 @@ public void autoUnsubscribe(boolean autoUnsubscribe) { return S.toString(StartRequestData.class, this); } - /** {@inheritDoc} */ - @Override public GridDeploymentInfo deploymentInfo() { - return depInfo; - } - - /** {@inheritDoc} */ - @Override public String deployedClassName() { - return clsName; - } - /** */ public void marshal(GridKernalContext ctx) throws IgniteCheckedException { if (hnd != null) { @@ -193,13 +179,13 @@ public void marshal(GridKernalContext ctx) throws IgniteCheckedException { hndBytes = U.marshal(ctx.marshaller(), hnd); } - MessageMarshalling.marshal(this, ctx, null); + if (nodeFilter != null) + nodeFilterBytes = U.marshal(ctx.marshaller(), nodeFilter); } /** */ public void unmarshal(GridKernalContext ctx, UUID sndId) throws IgniteCheckedException { - // No class loader: the node filter is read with the deployment this message carries. - MessageMarshalling.unmarshal(this, ctx, null, null); + nodeFilter = U.unmarshal(ctx.marshaller(), nodeFilterBytes, ctx.deploy().classLoader(depInfo, clsName)); if (hndBytes != null) { hnd = U.unmarshal(ctx.marshaller(), hndBytes, U.resolveClassLoader(ctx.config())); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java index c1ab5e7184bb1..10ba07fcf4ec0 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java @@ -503,7 +503,7 @@ public void testNioFieldNeedingCacheContextFails() { assertThat(compilation).hadErrorContaining("needs a cache object context to unmarshal"); } - /** A message carrying its own deployment resolves the class loader; a field may ask to keep its serialized copy. */ + /** A message carrying its own deployment has the class loader resolved from it. */ @Test public void testDeploymentAwareMessage() { Compilation compilation = compile("TestDeploymentAwareMessage.java"); diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java index 035be77f595a9..d1aec991f1a8f 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java @@ -27,16 +27,10 @@ public class TestDeploymentAwareMessage implements DeploymentAware { @Order(0) byte[] dataBytes; - @Marshalled(value = "keptBytes", keepBytes = true) - Object kept; - @Order(1) - byte[] keptBytes; - - @Order(2) GridDeploymentInfoBean depInfo; - @Order(3) + @Order(2) String clsName; @Override public GridDeploymentInfo deploymentInfo() { diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java index c4a9971cdbe7a..48acd95965773 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java @@ -47,9 +47,6 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { if (msg.data != null && msg.dataBytes == null) msg.dataBytes = U.marshal(marshaller, msg.data); - if (msg.kept != null && msg.keptBytes == null) - msg.keptBytes = U.marshal(marshaller, msg.kept); - if (msg.depInfo != null) MessageMarshalling.marshal(msg.depInfo, kctx, ctx); } @@ -69,9 +66,5 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { msg.dataBytes = null; } - - if (msg.keptBytes != null) { - msg.kept = U.unmarshal(marshaller, msg.keptBytes, clsLdr); - } } } \ No newline at end of file diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java index c3758550a3b59..e3fbc50e2a92c 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java @@ -45,18 +45,12 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial writer.incrementState(); case 1: - if (!writer.writeByteArray(msg.keptBytes)) - return false; - - writer.incrementState(); - - case 2: if (!writer.writeMessage(msg.depInfo)) return false; writer.incrementState(); - case 3: + case 2: if (!writer.writeString(msg.clsName)) return false; @@ -78,14 +72,6 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial reader.incrementState(); case 1: - msg.keptBytes = reader.readByteArray(); - - if (!reader.isLastRead()) - return false; - - reader.incrementState(); - - case 2: msg.depInfo = reader.readMessage(); if (!reader.isLastRead()) @@ -93,7 +79,7 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial reader.incrementState(); - case 3: + case 2: msg.clsName = reader.readString(); if (!reader.isLastRead()) From 166f25bacc7b85a1da4d1dbf4ae24d6ff7ff1988 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 04:43:24 +0300 Subject: [PATCH 09/19] IGNITE-28528 Let the discovery layer defer unmarshalling The socket-reading threads of both discovery SPIs unmarshalled every message they read. A continuous-query start request cannot be read there: obtaining the deployment of its node filter may block on a network request, and a missing user class has to be tolerated so the acknowledgement is still sent. Both readers now honour DeferredUnmarshalMessage, as the communication manager already did, and the continuous processor reads the request where that work is allowed. StartRequestData is left as plain data: its marshal/unmarshal methods are gone, the fields are marshalled by the generated marshaller, and preparing the handler moved to the processor that builds and consumes the message. Co-Authored-By: Claude Opus 5 --- .../apache/ignite/internal/Marshalled.java | 7 +++ .../internal/MessageMarshallerGenerator.java | 27 +++++++--- .../continuous/GridContinuousProcessor.java | 40 ++++++++++++-- .../continuous/StartRequestData.java | 52 +++++-------------- .../StartRoutineDiscoveryMessage.java | 3 +- .../discovery/tcp/TcpDiscoveryIoSession.java | 6 ++- .../codegen/MessageProcessorTest.java | 2 +- .../codegen/TestDeploymentAwareMessage.java | 8 ++- .../TestDeploymentAwareMessageMarshaller.java | 7 +++ .../TestDeploymentAwareMessageSerializer.java | 18 ++++++- .../zk/internal/DiscoveryMessageParser.java | 5 ++ 11 files changed, 121 insertions(+), 54 deletions(-) diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java index 62526f4e03906..a5b62ba694ed7 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java @@ -46,4 +46,11 @@ /** Name of the map-values companion wire field; requires {@link #keys()}. */ String values() default ""; + + /** + * Keeps the serialized copy once the object is restored, instead of dropping it. Needed when the message is sent + * on after being read: a discovery message travels the whole ring, and marshalling a user object again on that + * path is exactly what the copy is there to avoid. + */ + boolean keepBytes() default false; } diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 1c131f366a65d..5be3af565959c 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -26,7 +26,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.function.BiFunction; import java.util.Map; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; @@ -455,7 +454,7 @@ private void appendMapPrepare(List body, VariableElement field) { /** Generates {@code U.marshal} calls for all {@code @Marshalled} fields in marshal. */ private void appendMarshalledPrepare(List body) { - forEachMarshalled((bytesAcc, objAcc) -> { + forEachMarshalled((bytesAcc, objAcc, ann) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null && %s == null)", objAcc, bytesAcc)); @@ -472,7 +471,7 @@ private void appendMarshalledPrepare(List body) { /** Generates {@code U.unmarshal} calls for all {@code @Marshalled} fields in the cache-aware unmarshal. */ private void appendMarshalledFinish(List body) { - forEachMarshalled((bytesAcc, objAcc) -> { + forEachMarshalled((bytesAcc, objAcc, ann) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null) {", bytesAcc)); @@ -480,11 +479,13 @@ private void appendMarshalledFinish(List body) { indent++; code.add(indentedLine("%s = U.unmarshal(marshaller, %s, clsLdr);", objAcc, bytesAcc)); - code.add(EMPTY); // Drop the serialized cache once the object is restored: keeping both the deserialized value and its bytes // on every received message doubles retained memory (e.g. topology history nodes) and can exhaust the heap. - code.add(indentedLine("%s = null;", bytesAcc)); + if (!ann.keepBytes()) { + code.add(EMPTY); + code.add(indentedLine("%s = null;", bytesAcc)); + } indent--; @@ -1203,17 +1204,29 @@ private VariableElement requireEnclosed(Map enclosed, S } /** Iterates all {@code @Marshalled} fields and applies {@code codeGen(bytesAccessor, objAccessor)} to each. */ - private void forEachMarshalled(BiFunction> codeGen, List body) { + private void forEachMarshalled(MarshalledCode codeGen, List body) { for (VariableElement field : enclosed.values()) { if (kinds.get(field) != MarshalledKind.BLOB) continue; Marshalled ann = field.getAnnotation(Marshalled.class); - appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName())); + appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName(), ann)); } } + /** Generates the code handling a single {@code @Marshalled} field. */ + @FunctionalInterface + private interface MarshalledCode { + /** + * @param bytesAcc Accessor of the companion field holding the serialized form. + * @param objAcc Accessor of the annotated field. + * @param ann Annotation of the field. + * @return Generated lines. + */ + public List apply(String bytesAcc, String objAcc, Marshalled ann); + } + /** Returns the element for {@code t}; for a type variable, uses its upper bound. */ private Element element(TypeMirror t) { return t.getKind() == TypeKind.DECLARED diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java index fecd065ff7551..d70e899d3541d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java @@ -59,6 +59,7 @@ import org.apache.ignite.internal.managers.communication.ErrorMessage; import org.apache.ignite.internal.managers.communication.GridMessageListener; import org.apache.ignite.internal.managers.deployment.GridDeployment; +import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.managers.discovery.CustomEventListener; import org.apache.ignite.internal.managers.discovery.DiscoCache; @@ -981,7 +982,14 @@ private AbstractContinuousMessage createStartMessage(UUID routineId, reqData.deploymentInfo(dep); } - reqData.marshal(ctx); + if (ctx.config().isPeerClassLoadingEnabled()) { + // Handle peer deployment for other handler-specific objects. + hnd.p2pMarshal(ctx); + } + + // Marshalled here rather than left to the discovery layer: doing it there would marshal the user classes on + // the thread that writes the ring. + MessageMarshalling.marshal(reqData, ctx, null); if (!immutableDiscoCustomMsg) { StartRoutineDiscoveryMessage msg = new StartRoutineDiscoveryMessage(routineId, reqData, Mode.MUTABLE); @@ -1338,6 +1346,32 @@ private void processStartAckRequest(AffinityTopologyVersion topVer, } } + /** + * Reads a start request the discovery layer left untouched. Obtaining the deployment of the node filter and + * restoring the handler both take work that must not run on the thread reading the ring. + * + * @param msg Message carrying the request. + * @param sndId Node that started the routine. + */ + private void unmarshalStartRequest(StartRoutineDiscoveryMessage msg, UUID sndId) throws IgniteCheckedException { + MessageMarshalling.unmarshal(msg, ctx); + + StartRequestData data = msg.startRequestData(); + + GridContinuousHandler hnd = data.handler(); + + if (hnd != null) { + if (ctx.config().isPeerClassLoadingEnabled()) + hnd.p2pUnmarshal(sndId, ctx); + + if (data.keepBinary) { + assert hnd instanceof CacheContinuousQueryHandler : hnd; + + ((CacheContinuousQueryHandler)hnd).keepBinary(true); + } + } + } + /** * @param node Sender. * @param req Start request. @@ -1353,7 +1387,7 @@ private void processStartRequestMutable(ClusterNode node, StartRoutineDiscoveryM IgniteCheckedException err = null; try { - data.unmarshal(ctx, node.id()); + unmarshalStartRequest(req, node.id()); } catch (IgniteCheckedException e) { U.error(log, "Failed to unmarshal start request data [nodeId=" + node.id() + @@ -1495,7 +1529,7 @@ private void processStartRequestImmutable(final AffinityTopologyVersion topVer, Exception err = null; try { - reqData.unmarshal(ctx, snd.id()); + unmarshalStartRequest(msg, snd.id()); } catch (IgniteCheckedException e) { err = e; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index aae9f30d080e3..88281f283a133 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -17,24 +17,22 @@ package org.apache.ignite.internal.processors.continuous; -import java.util.UUID; -import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cluster.ClusterNode; -import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.DeploymentAware; +import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; -import org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryHandler; import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgnitePredicate; -import org.apache.ignite.plugin.extensions.communication.Message; /** * Start request data. */ -public class StartRequestData implements Message { +public class StartRequestData implements DeploymentAware { /** Node filter. */ - private IgnitePredicate nodeFilter; + @Marshalled(value = "nodeFilterBytes", keepBytes = true) + IgnitePredicate nodeFilter; /** Serialized node filter. */ @Order(0) @@ -49,7 +47,8 @@ public class StartRequestData implements Message { GridDeploymentInfoBean depInfo; /** Handler. */ - private GridContinuousHandler hnd; + @Marshalled(value = "hndBytes", keepBytes = true) + GridContinuousHandler hnd; /** Serialized handler. */ @Order(3) @@ -168,36 +167,13 @@ public void autoUnsubscribe(boolean autoUnsubscribe) { return S.toString(StartRequestData.class, this); } - /** */ - public void marshal(GridKernalContext ctx) throws IgniteCheckedException { - if (hnd != null) { - if (ctx.config().isPeerClassLoadingEnabled()) { - // Handle peer deployment for other handler-specific objects. - hnd.p2pMarshal(ctx); - } - - hndBytes = U.marshal(ctx.marshaller(), hnd); - } - - if (nodeFilter != null) - nodeFilterBytes = U.marshal(ctx.marshaller(), nodeFilter); + /** {@inheritDoc} */ + @Override public GridDeploymentInfo deploymentInfo() { + return depInfo; } - /** */ - public void unmarshal(GridKernalContext ctx, UUID sndId) throws IgniteCheckedException { - nodeFilter = U.unmarshal(ctx.marshaller(), nodeFilterBytes, ctx.deploy().classLoader(depInfo, clsName)); - - if (hndBytes != null) { - hnd = U.unmarshal(ctx.marshaller(), hndBytes, U.resolveClassLoader(ctx.config())); - - if (ctx.config().isPeerClassLoadingEnabled()) - hnd.p2pUnmarshal(sndId, ctx); - - if (keepBinary) { - assert hnd instanceof CacheContinuousQueryHandler : hnd; - - ((CacheContinuousQueryHandler)hnd).keepBinary(true); - } - } + /** {@inheritDoc} */ + @Override public String deployedClassName() { + return clsName; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java index ddf9e63f6c424..7cf7945674d1a 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.UUID; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.managers.communication.ErrorMessage; import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; @@ -31,7 +32,7 @@ /** * Discovery message used for Continuous Query registration. */ -public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage { +public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage implements DeferredUnmarshalMessage { /** Discovery message mode. */ enum Mode { /** Mutable discovery mode. */ diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index e237db2cf3c7a..77cacb35bde90 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -35,6 +35,7 @@ import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.util.CommonUtils; @@ -197,7 +198,10 @@ T readMessage() throws IgniteCheckedException, IOException { } while (!finished); - MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); + // A deferred-unmarshal message is left as it arrived: reading it here would run user-class loading, and + // whatever else its owner does, on the thread that reads the ring. + if (!(msg instanceof DeferredUnmarshalMessage)) + MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); return (T)msg; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java index 10ba07fcf4ec0..c1ab5e7184bb1 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java @@ -503,7 +503,7 @@ public void testNioFieldNeedingCacheContextFails() { assertThat(compilation).hadErrorContaining("needs a cache object context to unmarshal"); } - /** A message carrying its own deployment has the class loader resolved from it. */ + /** A message carrying its own deployment resolves the class loader; a field may ask to keep its serialized copy. */ @Test public void testDeploymentAwareMessage() { Compilation compilation = compile("TestDeploymentAwareMessage.java"); diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java index d1aec991f1a8f..035be77f595a9 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java @@ -27,10 +27,16 @@ public class TestDeploymentAwareMessage implements DeploymentAware { @Order(0) byte[] dataBytes; + @Marshalled(value = "keptBytes", keepBytes = true) + Object kept; + @Order(1) - GridDeploymentInfoBean depInfo; + byte[] keptBytes; @Order(2) + GridDeploymentInfoBean depInfo; + + @Order(3) String clsName; @Override public GridDeploymentInfo deploymentInfo() { diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java index 48acd95965773..c4a9971cdbe7a 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java @@ -47,6 +47,9 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { if (msg.data != null && msg.dataBytes == null) msg.dataBytes = U.marshal(marshaller, msg.data); + if (msg.kept != null && msg.keptBytes == null) + msg.keptBytes = U.marshal(marshaller, msg.kept); + if (msg.depInfo != null) MessageMarshalling.marshal(msg.depInfo, kctx, ctx); } @@ -66,5 +69,9 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { msg.dataBytes = null; } + + if (msg.keptBytes != null) { + msg.kept = U.unmarshal(marshaller, msg.keptBytes, clsLdr); + } } } \ No newline at end of file diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java index e3fbc50e2a92c..c3758550a3b59 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java @@ -45,12 +45,18 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial writer.incrementState(); case 1: - if (!writer.writeMessage(msg.depInfo)) + if (!writer.writeByteArray(msg.keptBytes)) return false; writer.incrementState(); case 2: + if (!writer.writeMessage(msg.depInfo)) + return false; + + writer.incrementState(); + + case 3: if (!writer.writeString(msg.clsName)) return false; @@ -72,7 +78,7 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial reader.incrementState(); case 1: - msg.depInfo = reader.readMessage(); + msg.keptBytes = reader.readByteArray(); if (!reader.isLastRead()) return false; @@ -80,6 +86,14 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial reader.incrementState(); case 2: + msg.depInfo = reader.readMessage(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + + case 3: msg.clsName = reader.readString(); if (!reader.isLastRead()) diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java index bcc10b0bbb018..a3603c1755016 100644 --- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java +++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java @@ -29,6 +29,7 @@ import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.util.CommonUtils; import org.apache.ignite.internal.util.nio.MessageSerialization; @@ -140,6 +141,10 @@ private T deserializeMessage(InputStream in) throws IOExcept } while (!finished); + // A deferred-unmarshal message is left as it arrived, for its owner to read where the work it takes is allowed. + if (msg instanceof DeferredUnmarshalMessage) + return (T)msg; + try { MessageMarshalling.unmarshal(msg, kctx); } From ed22879a9842c551f0fb12c6125f211a3b4a4c08 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 04:59:57 +0300 Subject: [PATCH 10/19] IGNITE-28528 Keep the start request in the form it arrived The generated marshaller drops the serialized copy of a field once the object is restored, which a message that travels the discovery ring cannot afford: every node would pass on a copy of its own making. For the handler that is not even equivalent - p2pUnmarshal unpacks its user objects and nothing packs them back, so they would go on the wire outside peer class loading. The request stays plain data then: the fields keep their serialized form, and the continuous processor restores the objects, asking the deployment manager for the class loader of the node filter. Co-Authored-By: Claude Opus 5 --- .../apache/ignite/internal/Marshalled.java | 7 ----- .../internal/MessageMarshallerGenerator.java | 27 +++++------------- .../continuous/GridContinuousProcessor.java | 28 ++++++++++--------- .../continuous/StartRequestData.java | 22 +++------------ .../StartRoutineDiscoveryMessage.java | 3 +- .../discovery/tcp/TcpDiscoveryIoSession.java | 6 +--- .../codegen/MessageProcessorTest.java | 2 +- .../codegen/TestDeploymentAwareMessage.java | 8 +----- .../TestDeploymentAwareMessageMarshaller.java | 7 ----- .../TestDeploymentAwareMessageSerializer.java | 18 ++---------- .../zk/internal/DiscoveryMessageParser.java | 5 ---- 11 files changed, 32 insertions(+), 101 deletions(-) diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java index a5b62ba694ed7..62526f4e03906 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/Marshalled.java @@ -46,11 +46,4 @@ /** Name of the map-values companion wire field; requires {@link #keys()}. */ String values() default ""; - - /** - * Keeps the serialized copy once the object is restored, instead of dropping it. Needed when the message is sent - * on after being read: a discovery message travels the whole ring, and marshalling a user object again on that - * path is exactly what the copy is there to avoid. - */ - boolean keepBytes() default false; } diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 5be3af565959c..15a56bfb0185a 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -27,6 +27,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.BiFunction; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; @@ -454,7 +455,7 @@ private void appendMapPrepare(List body, VariableElement field) { /** Generates {@code U.marshal} calls for all {@code @Marshalled} fields in marshal. */ private void appendMarshalledPrepare(List body) { - forEachMarshalled((bytesAcc, objAcc, ann) -> { + forEachMarshalled((bytesAcc, objAcc) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null && %s == null)", objAcc, bytesAcc)); @@ -471,7 +472,7 @@ private void appendMarshalledPrepare(List body) { /** Generates {@code U.unmarshal} calls for all {@code @Marshalled} fields in the cache-aware unmarshal. */ private void appendMarshalledFinish(List body) { - forEachMarshalled((bytesAcc, objAcc, ann) -> { + forEachMarshalled((bytesAcc, objAcc) -> { List code = new ArrayList<>(); code.add(indentedLine("if (%s != null) {", bytesAcc)); @@ -479,13 +480,11 @@ private void appendMarshalledFinish(List body) { indent++; code.add(indentedLine("%s = U.unmarshal(marshaller, %s, clsLdr);", objAcc, bytesAcc)); + code.add(EMPTY); // Drop the serialized cache once the object is restored: keeping both the deserialized value and its bytes // on every received message doubles retained memory (e.g. topology history nodes) and can exhaust the heap. - if (!ann.keepBytes()) { - code.add(EMPTY); - code.add(indentedLine("%s = null;", bytesAcc)); - } + code.add(indentedLine("%s = null;", bytesAcc)); indent--; @@ -1204,29 +1203,17 @@ private VariableElement requireEnclosed(Map enclosed, S } /** Iterates all {@code @Marshalled} fields and applies {@code codeGen(bytesAccessor, objAccessor)} to each. */ - private void forEachMarshalled(MarshalledCode codeGen, List body) { + private void forEachMarshalled(BiFunction> codeGen, List body) { for (VariableElement field : enclosed.values()) { if (kinds.get(field) != MarshalledKind.BLOB) continue; Marshalled ann = field.getAnnotation(Marshalled.class); - appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName(), ann)); + appendBlock(body, codeGen.apply("msg." + ann.value(), "msg." + field.getSimpleName())); } } - /** Generates the code handling a single {@code @Marshalled} field. */ - @FunctionalInterface - private interface MarshalledCode { - /** - * @param bytesAcc Accessor of the companion field holding the serialized form. - * @param objAcc Accessor of the annotated field. - * @param ann Annotation of the field. - * @return Generated lines. - */ - public List apply(String bytesAcc, String objAcc, Marshalled ann); - } - /** Returns the element for {@code t}; for a type variable, uses its upper bound. */ private Element element(TypeMirror t) { return t.getKind() == TypeKind.DECLARED diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java index d70e899d3541d..0915e7b15378f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java @@ -59,7 +59,6 @@ import org.apache.ignite.internal.managers.communication.ErrorMessage; import org.apache.ignite.internal.managers.communication.GridMessageListener; import org.apache.ignite.internal.managers.deployment.GridDeployment; -import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.managers.discovery.CustomEventListener; import org.apache.ignite.internal.managers.discovery.DiscoCache; @@ -987,9 +986,10 @@ private AbstractContinuousMessage createStartMessage(UUID routineId, hnd.p2pMarshal(ctx); } - // Marshalled here rather than left to the discovery layer: doing it there would marshal the user classes on - // the thread that writes the ring. - MessageMarshalling.marshal(reqData, ctx, null); + reqData.hndBytes = U.marshal(marsh, hnd); + + if (nodeFilter != null) + reqData.nodeFilterBytes = U.marshal(marsh, nodeFilter); if (!immutableDiscoCustomMsg) { StartRoutineDiscoveryMessage msg = new StartRoutineDiscoveryMessage(routineId, reqData, Mode.MUTABLE); @@ -1347,27 +1347,29 @@ private void processStartAckRequest(AffinityTopologyVersion topVer, } /** - * Reads a start request the discovery layer left untouched. Obtaining the deployment of the node filter and - * restoring the handler both take work that must not run on the thread reading the ring. + * Restores the objects a start request carries. The serialized form stays on the message: it travels the whole + * ring, and a node must pass on what it received rather than a copy of its own making — the handler unpacks its + * user objects here and never packs them back. * * @param msg Message carrying the request. * @param sndId Node that started the routine. */ private void unmarshalStartRequest(StartRoutineDiscoveryMessage msg, UUID sndId) throws IgniteCheckedException { - MessageMarshalling.unmarshal(msg, ctx); - StartRequestData data = msg.startRequestData(); - GridContinuousHandler hnd = data.handler(); + data.nodeFilter = U.unmarshal(marsh, data.nodeFilterBytes, + ctx.deploy().classLoader(data.depInfo, data.clsName)); + + if (data.hndBytes != null) { + data.hnd = U.unmarshal(marsh, data.hndBytes, U.resolveClassLoader(ctx.config())); - if (hnd != null) { if (ctx.config().isPeerClassLoadingEnabled()) - hnd.p2pUnmarshal(sndId, ctx); + data.hnd.p2pUnmarshal(sndId, ctx); if (data.keepBinary) { - assert hnd instanceof CacheContinuousQueryHandler : hnd; + assert data.hnd instanceof CacheContinuousQueryHandler : data.hnd; - ((CacheContinuousQueryHandler)hnd).keepBinary(true); + ((CacheContinuousQueryHandler)data.hnd).keepBinary(true); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index 88281f283a133..dbdce6963bf45 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -18,20 +18,17 @@ package org.apache.ignite.internal.processors.continuous; import org.apache.ignite.cluster.ClusterNode; -import org.apache.ignite.internal.DeploymentAware; -import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgnitePredicate; +import org.apache.ignite.plugin.extensions.communication.Message; /** * Start request data. */ -public class StartRequestData implements DeploymentAware { - /** Node filter. */ - @Marshalled(value = "nodeFilterBytes", keepBytes = true) +public class StartRequestData implements Message { + /** Node filter, restored from {@link #nodeFilterBytes} by the processor reading this request. */ IgnitePredicate nodeFilter; /** Serialized node filter. */ @@ -46,8 +43,7 @@ public class StartRequestData implements DeploymentAware { @Order(2) GridDeploymentInfoBean depInfo; - /** Handler. */ - @Marshalled(value = "hndBytes", keepBytes = true) + /** Handler, restored from {@link #hndBytes} by the processor reading this request. */ GridContinuousHandler hnd; /** Serialized handler. */ @@ -166,14 +162,4 @@ public void autoUnsubscribe(boolean autoUnsubscribe) { @Override public String toString() { return S.toString(StartRequestData.class, this); } - - /** {@inheritDoc} */ - @Override public GridDeploymentInfo deploymentInfo() { - return depInfo; - } - - /** {@inheritDoc} */ - @Override public String deployedClassName() { - return clsName; - } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java index 7cf7945674d1a..ddf9e63f6c424 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.UUID; import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.managers.communication.ErrorMessage; import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; @@ -32,7 +31,7 @@ /** * Discovery message used for Continuous Query registration. */ -public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage implements DeferredUnmarshalMessage { +public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage { /** Discovery message mode. */ enum Mode { /** Mutable discovery mode. */ diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 77cacb35bde90..e237db2cf3c7a 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -35,7 +35,6 @@ import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.util.CommonUtils; @@ -198,10 +197,7 @@ T readMessage() throws IgniteCheckedException, IOException { } while (!finished); - // A deferred-unmarshal message is left as it arrived: reading it here would run user-class loading, and - // whatever else its owner does, on the thread that reads the ring. - if (!(msg instanceof DeferredUnmarshalMessage)) - MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); + MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); return (T)msg; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java index c1ab5e7184bb1..10ba07fcf4ec0 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java @@ -503,7 +503,7 @@ public void testNioFieldNeedingCacheContextFails() { assertThat(compilation).hadErrorContaining("needs a cache object context to unmarshal"); } - /** A message carrying its own deployment resolves the class loader; a field may ask to keep its serialized copy. */ + /** A message carrying its own deployment has the class loader resolved from it. */ @Test public void testDeploymentAwareMessage() { Compilation compilation = compile("TestDeploymentAwareMessage.java"); diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java index 035be77f595a9..d1aec991f1a8f 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java @@ -27,16 +27,10 @@ public class TestDeploymentAwareMessage implements DeploymentAware { @Order(0) byte[] dataBytes; - @Marshalled(value = "keptBytes", keepBytes = true) - Object kept; - @Order(1) - byte[] keptBytes; - - @Order(2) GridDeploymentInfoBean depInfo; - @Order(3) + @Order(2) String clsName; @Override public GridDeploymentInfo deploymentInfo() { diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java index c4a9971cdbe7a..48acd95965773 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java @@ -47,9 +47,6 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { if (msg.data != null && msg.dataBytes == null) msg.dataBytes = U.marshal(marshaller, msg.data); - if (msg.kept != null && msg.keptBytes == null) - msg.keptBytes = U.marshal(marshaller, msg.kept); - if (msg.depInfo != null) MessageMarshalling.marshal(msg.depInfo, kctx, ctx); } @@ -69,9 +66,5 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { msg.dataBytes = null; } - - if (msg.keptBytes != null) { - msg.kept = U.unmarshal(marshaller, msg.keptBytes, clsLdr); - } } } \ No newline at end of file diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java index c3758550a3b59..e3fbc50e2a92c 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java @@ -45,18 +45,12 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial writer.incrementState(); case 1: - if (!writer.writeByteArray(msg.keptBytes)) - return false; - - writer.incrementState(); - - case 2: if (!writer.writeMessage(msg.depInfo)) return false; writer.incrementState(); - case 3: + case 2: if (!writer.writeString(msg.clsName)) return false; @@ -78,14 +72,6 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial reader.incrementState(); case 1: - msg.keptBytes = reader.readByteArray(); - - if (!reader.isLastRead()) - return false; - - reader.incrementState(); - - case 2: msg.depInfo = reader.readMessage(); if (!reader.isLastRead()) @@ -93,7 +79,7 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial reader.incrementState(); - case 3: + case 2: msg.clsName = reader.readString(); if (!reader.isLastRead()) diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java index a3603c1755016..bcc10b0bbb018 100644 --- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java +++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java @@ -29,7 +29,6 @@ import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.util.CommonUtils; import org.apache.ignite.internal.util.nio.MessageSerialization; @@ -141,10 +140,6 @@ private T deserializeMessage(InputStream in) throws IOExcept } while (!finished); - // A deferred-unmarshal message is left as it arrived, for its owner to read where the work it takes is allowed. - if (msg instanceof DeferredUnmarshalMessage) - return (T)msg; - try { MessageMarshalling.unmarshal(msg, kctx); } From 66c9d947066bf5a043a8faa1facc6df99b3ba0c3 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 05:24:16 +0300 Subject: [PATCH 11/19] IGNITE-28528 State that the start request is passed on Whether the serialized copy of a field may be dropped once the object is restored is a property of the message, not of the field: a message read and then sent on must reach every node in the form it left the sender. ForwardedMessage states that, so the start request goes back on the generated marshaller, and its handler keeps the bytes it arrived with - p2pUnmarshal unpacks the user objects inside it and nothing packs them back. Reading it is deferred, as before: both discovery readers honour the marker, and the continuous processor reads the message where obtaining a deployment is allowed to block. Co-Authored-By: Claude Opus 5 --- .../internal/MessageMarshallerGenerator.java | 17 ++++- .../ignite/internal/MessageProcessor.java | 3 + .../ignite/internal/ForwardedMessage.java | 31 ++++++++ .../continuous/GridContinuousProcessor.java | 28 ++++---- .../continuous/StartRequestData.java | 23 ++++-- .../StartRoutineDiscoveryMessage.java | 3 +- .../discovery/tcp/TcpDiscoveryIoSession.java | 6 +- .../codegen/MessageProcessorTest.java | 18 +++++ .../codegen/TestForwardedMessage.java | 30 ++++++++ .../TestForwardedMessageMarshaller.java | 54 ++++++++++++++ .../TestForwardedMessageSerializer.java | 70 +++++++++++++++++++ .../zk/internal/DiscoveryMessageParser.java | 5 ++ 12 files changed, 265 insertions(+), 23 deletions(-) create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java create mode 100644 modules/core/src/test/resources/codegen/TestForwardedMessage.java create mode 100644 modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java create mode 100644 modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 15a56bfb0185a..8e75adcc88bdb 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -47,6 +47,7 @@ import static org.apache.ignite.internal.MessageProcessor.CACHE_OBJECT_CLS; import static org.apache.ignite.internal.MessageProcessor.DEPLOYMENT_AWARE_MESSAGE_INTERFACE; +import static org.apache.ignite.internal.MessageProcessor.FORWARDED_MESSAGE_INTERFACE; import static org.apache.ignite.internal.MessageProcessor.IGNITE_CHECKED_EXCEPTION_CLS; import static org.apache.ignite.internal.MessageProcessor.KEY_CACHE_OBJECT_CLS; import static org.apache.ignite.internal.MessageProcessor.MARSHALLABLE_MESSAGE_INTERFACE; @@ -100,6 +101,9 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { /** */ private final TypeMirror deploymentAwareMsgType; + /** */ + private final TypeMirror forwardedMsgType; + /** */ private final TypeMirror selfMarshallingMsgType; @@ -145,6 +149,7 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { cacheObjType = type(CACHE_OBJECT_CLS); nonMarshallableType = type(NON_MARSHALLABLE_MESSAGE_INTERFACE); deploymentAwareMsgType = type(DEPLOYMENT_AWARE_MESSAGE_INTERFACE); + forwardedMsgType = type(FORWARDED_MESSAGE_INTERFACE); selfMarshallingMsgType = type(SELF_MARSHALLING_MESSAGE_INTERFACE); cacheGrpIdMsgType = type(GRID_CACHE_GROUP_ID_MESSAGE_CLS); mapType = type(Map.class.getName()); @@ -480,11 +485,14 @@ private void appendMarshalledFinish(List body) { indent++; code.add(indentedLine("%s = U.unmarshal(marshaller, %s, clsLdr);", objAcc, bytesAcc)); - code.add(EMPTY); // Drop the serialized cache once the object is restored: keeping both the deserialized value and its bytes // on every received message doubles retained memory (e.g. topology history nodes) and can exhaust the heap. - code.add(indentedLine("%s = null;", bytesAcc)); + // A forwarded message keeps it: what it passes on has to be what it received. + if (!isForwarded()) { + code.add(EMPTY); + code.add(indentedLine("%s = null;", bytesAcc)); + } indent--; @@ -1056,6 +1064,11 @@ private String deploymentResolutionLine() { + indentedLine(" clsLdr = kctx.deploy().classLoader(msg);"); } + /** @return {@code true} if the message is sent on after being read. */ + private boolean isForwarded() { + return forwardedMsgType != null && assignableFrom(type.asType(), forwardedMsgType); + } + /** @return {@code true} if the message carries the deployment of its classes. */ private boolean isDeploymentAware() { return deploymentAwareMsgType != null && assignableFrom(type.asType(), deploymentAwareMsgType); diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java index 414771f0b5160..a4f1e9a59a83c 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java @@ -87,6 +87,9 @@ public class MessageProcessor extends AbstractProcessor { /** Message that reshapes its own fields before they go on the wire. */ static final String SELF_MARSHALLING_MESSAGE_INTERFACE = "org.apache.ignite.internal.SelfMarshallingMessage"; + /** Message sent on after being read, so its serialized form must stay as it arrived. */ + static final String FORWARDED_MESSAGE_INTERFACE = "org.apache.ignite.internal.ForwardedMessage"; + /** Message that carries the deployment of the classes inside it. */ static final String DEPLOYMENT_AWARE_MESSAGE_INTERFACE = "org.apache.ignite.internal.DeploymentAware"; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java new file mode 100644 index 0000000000000..a3e0f27fc7947 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +import org.apache.ignite.plugin.extensions.communication.Message; + +/** + * Implemented by messages that are sent on after being read, a discovery message travelling the ring being the case. + * Such a message must reach every node in the form it left the sender, so the generated marshaller keeps the + * serialized form of its fields instead of dropping it once the objects are restored. + * + * @see Marshalled + */ +public interface ForwardedMessage extends Message { + // No-op. +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java index 0915e7b15378f..d70e899d3541d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java @@ -59,6 +59,7 @@ import org.apache.ignite.internal.managers.communication.ErrorMessage; import org.apache.ignite.internal.managers.communication.GridMessageListener; import org.apache.ignite.internal.managers.deployment.GridDeployment; +import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.managers.discovery.CustomEventListener; import org.apache.ignite.internal.managers.discovery.DiscoCache; @@ -986,10 +987,9 @@ private AbstractContinuousMessage createStartMessage(UUID routineId, hnd.p2pMarshal(ctx); } - reqData.hndBytes = U.marshal(marsh, hnd); - - if (nodeFilter != null) - reqData.nodeFilterBytes = U.marshal(marsh, nodeFilter); + // Marshalled here rather than left to the discovery layer: doing it there would marshal the user classes on + // the thread that writes the ring. + MessageMarshalling.marshal(reqData, ctx, null); if (!immutableDiscoCustomMsg) { StartRoutineDiscoveryMessage msg = new StartRoutineDiscoveryMessage(routineId, reqData, Mode.MUTABLE); @@ -1347,29 +1347,27 @@ private void processStartAckRequest(AffinityTopologyVersion topVer, } /** - * Restores the objects a start request carries. The serialized form stays on the message: it travels the whole - * ring, and a node must pass on what it received rather than a copy of its own making — the handler unpacks its - * user objects here and never packs them back. + * Reads a start request the discovery layer left untouched. Obtaining the deployment of the node filter and + * restoring the handler both take work that must not run on the thread reading the ring. * * @param msg Message carrying the request. * @param sndId Node that started the routine. */ private void unmarshalStartRequest(StartRoutineDiscoveryMessage msg, UUID sndId) throws IgniteCheckedException { - StartRequestData data = msg.startRequestData(); + MessageMarshalling.unmarshal(msg, ctx); - data.nodeFilter = U.unmarshal(marsh, data.nodeFilterBytes, - ctx.deploy().classLoader(data.depInfo, data.clsName)); + StartRequestData data = msg.startRequestData(); - if (data.hndBytes != null) { - data.hnd = U.unmarshal(marsh, data.hndBytes, U.resolveClassLoader(ctx.config())); + GridContinuousHandler hnd = data.handler(); + if (hnd != null) { if (ctx.config().isPeerClassLoadingEnabled()) - data.hnd.p2pUnmarshal(sndId, ctx); + hnd.p2pUnmarshal(sndId, ctx); if (data.keepBinary) { - assert data.hnd instanceof CacheContinuousQueryHandler : data.hnd; + assert hnd instanceof CacheContinuousQueryHandler : hnd; - ((CacheContinuousQueryHandler)data.hnd).keepBinary(true); + ((CacheContinuousQueryHandler)hnd).keepBinary(true); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index dbdce6963bf45..bf181e1545f8b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -18,17 +18,21 @@ package org.apache.ignite.internal.processors.continuous; import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.DeploymentAware; +import org.apache.ignite.internal.ForwardedMessage; +import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgnitePredicate; -import org.apache.ignite.plugin.extensions.communication.Message; /** * Start request data. */ -public class StartRequestData implements Message { - /** Node filter, restored from {@link #nodeFilterBytes} by the processor reading this request. */ +public class StartRequestData implements DeploymentAware, ForwardedMessage { + /** Node filter. */ + @Marshalled("nodeFilterBytes") IgnitePredicate nodeFilter; /** Serialized node filter. */ @@ -43,7 +47,8 @@ public class StartRequestData implements Message { @Order(2) GridDeploymentInfoBean depInfo; - /** Handler, restored from {@link #hndBytes} by the processor reading this request. */ + /** Handler. */ + @Marshalled("hndBytes") GridContinuousHandler hnd; /** Serialized handler. */ @@ -162,4 +167,14 @@ public void autoUnsubscribe(boolean autoUnsubscribe) { @Override public String toString() { return S.toString(StartRequestData.class, this); } + + /** {@inheritDoc} */ + @Override public GridDeploymentInfo deploymentInfo() { + return depInfo; + } + + /** {@inheritDoc} */ + @Override public String deployedClassName() { + return clsName; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java index ddf9e63f6c424..7cf7945674d1a 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.UUID; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.managers.communication.ErrorMessage; import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; @@ -31,7 +32,7 @@ /** * Discovery message used for Continuous Query registration. */ -public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage { +public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage implements DeferredUnmarshalMessage { /** Discovery message mode. */ enum Mode { /** Mutable discovery mode. */ diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index e237db2cf3c7a..77cacb35bde90 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -35,6 +35,7 @@ import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.util.CommonUtils; @@ -197,7 +198,10 @@ T readMessage() throws IgniteCheckedException, IOException { } while (!finished); - MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); + // A deferred-unmarshal message is left as it arrived: reading it here would run user-class loading, and + // whatever else its owner does, on the thread that reads the ring. + if (!(msg instanceof DeferredUnmarshalMessage)) + MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); return (T)msg; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java index 10ba07fcf4ec0..5de529a158235 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java @@ -503,6 +503,24 @@ public void testNioFieldNeedingCacheContextFails() { assertThat(compilation).hadErrorContaining("needs a cache object context to unmarshal"); } + /** A message that is sent on after being read keeps the serialized form of its fields. */ + @Test + public void testForwardedMessage() { + Compilation compilation = compile("TestForwardedMessage.java"); + + assertThat(compilation).succeeded(); + + assertEquals(2, compilation.generatedSourceFiles().size()); + + assertThat(compilation) + .generatedSourceFile("org.apache.ignite.internal.TestForwardedMessageSerializer") + .hasSourceEquivalentTo(javaFile("TestForwardedMessageSerializer.java")); + + assertThat(compilation) + .generatedSourceFile("org.apache.ignite.internal.TestForwardedMessageMarshaller") + .hasSourceEquivalentTo(javaFile("TestForwardedMessageMarshaller.java")); + } + /** A message carrying its own deployment has the class loader resolved from it. */ @Test public void testDeploymentAwareMessage() { diff --git a/modules/core/src/test/resources/codegen/TestForwardedMessage.java b/modules/core/src/test/resources/codegen/TestForwardedMessage.java new file mode 100644 index 0000000000000..c5b373dcbe802 --- /dev/null +++ b/modules/core/src/test/resources/codegen/TestForwardedMessage.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +public class TestForwardedMessage implements ForwardedMessage { + @Marshalled("dataBytes") + Object data; + + @Order(0) + byte[] dataBytes; + + public short directType() { + return 0; + } +} diff --git a/modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java new file mode 100644 index 0000000000000..2070ad1713ac8 --- /dev/null +++ b/modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.TestForwardedMessage; +import org.apache.ignite.internal.processors.cache.CacheObjectContext; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.marshaller.Marshaller; +import org.apache.ignite.plugin.extensions.communication.MessageMarshaller; + +/** + * This class is generated automatically. + * + * @see org.apache.ignite.internal.MessageProcessor + */ +public final class TestForwardedMessageMarshaller implements MessageMarshaller { + /** */ + private final Marshaller marshaller; + + /** */ + public TestForwardedMessageMarshaller(Marshaller marshaller) { + this.marshaller = marshaller; + } + + /** */ + @Override public void marshal(TestForwardedMessage msg, GridKernalContext kctx, CacheObjectContext cacheObjCtx) throws IgniteCheckedException { + if (msg.data != null && msg.dataBytes == null) + msg.dataBytes = U.marshal(marshaller, msg.data); + } + + /** */ + @Override public void unmarshal(TestForwardedMessage msg, GridKernalContext kctx, CacheObjectContext cacheObjCtx, ClassLoader clsLdr) throws IgniteCheckedException { + if (msg.dataBytes != null) { + msg.data = U.unmarshal(marshaller, msg.dataBytes, clsLdr); + } + } +} \ No newline at end of file diff --git a/modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java b/modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java new file mode 100644 index 0000000000000..92137b53d39fa --- /dev/null +++ b/modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal; + +import org.apache.ignite.internal.TestForwardedMessage; +import org.apache.ignite.plugin.extensions.communication.MessageReader; +import org.apache.ignite.plugin.extensions.communication.MessageSerializer; +import org.apache.ignite.plugin.extensions.communication.MessageWriter; + +/** + * This class is generated automatically. + * + * @see org.apache.ignite.internal.MessageProcessor + */ +public final class TestForwardedMessageSerializer implements MessageSerializer { + /** */ + @Override public final boolean writeTo(TestForwardedMessage msg, MessageWriter writer) { + if (!writer.isHeaderWritten()) { + if (!writer.writeHeader(msg.directType())) + return false; + + writer.onHeaderWritten(); + } + + switch (writer.state()) { + case 0: + if (!writer.writeByteArray(msg.dataBytes)) + return false; + + writer.incrementState(); + } + + return true; + } + + /** */ + @Override public final boolean readFrom(TestForwardedMessage msg, MessageReader reader) { + switch (reader.state()) { + case 0: + msg.dataBytes = reader.readByteArray(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + } + + return true; + } + + /** {@inheritDoc} */ + @Override public final TestForwardedMessage createMessage() { + return new TestForwardedMessage(); + } +} \ No newline at end of file diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java index bcc10b0bbb018..a3603c1755016 100644 --- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java +++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java @@ -29,6 +29,7 @@ import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.util.CommonUtils; import org.apache.ignite.internal.util.nio.MessageSerialization; @@ -140,6 +141,10 @@ private T deserializeMessage(InputStream in) throws IOExcept } while (!finished); + // A deferred-unmarshal message is left as it arrived, for its owner to read where the work it takes is allowed. + if (msg instanceof DeferredUnmarshalMessage) + return (T)msg; + try { MessageMarshalling.unmarshal(msg, kctx); } From 23aca185be082a4565acfc8f3d285019d1e5715f Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Thu, 6 Aug 2026 05:56:00 +0300 Subject: [PATCH 12/19] IGNITE-28528 Drop the forwarded-message marker The handler survives being read and sent on: its writeExternal writes the deployable wrapper whenever there is one, so what p2pUnmarshal restores never reaches the wire. The node filter is simply marshalled again, as it is on every other discovery message with a marshalled field. Keeping the serialized copy was guarding against nothing. Co-Authored-By: Claude Opus 5 --- .../internal/MessageMarshallerGenerator.java | 16 +---- .../ignite/internal/MessageProcessor.java | 3 - .../ignite/internal/ForwardedMessage.java | 31 -------- .../continuous/StartRequestData.java | 3 +- .../codegen/MessageProcessorTest.java | 18 ----- .../codegen/TestForwardedMessage.java | 30 -------- .../TestForwardedMessageMarshaller.java | 54 -------------- .../TestForwardedMessageSerializer.java | 70 ------------------- 8 files changed, 3 insertions(+), 222 deletions(-) delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java delete mode 100644 modules/core/src/test/resources/codegen/TestForwardedMessage.java delete mode 100644 modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java delete mode 100644 modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 8e75adcc88bdb..770796a9c1085 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -47,7 +47,6 @@ import static org.apache.ignite.internal.MessageProcessor.CACHE_OBJECT_CLS; import static org.apache.ignite.internal.MessageProcessor.DEPLOYMENT_AWARE_MESSAGE_INTERFACE; -import static org.apache.ignite.internal.MessageProcessor.FORWARDED_MESSAGE_INTERFACE; import static org.apache.ignite.internal.MessageProcessor.IGNITE_CHECKED_EXCEPTION_CLS; import static org.apache.ignite.internal.MessageProcessor.KEY_CACHE_OBJECT_CLS; import static org.apache.ignite.internal.MessageProcessor.MARSHALLABLE_MESSAGE_INTERFACE; @@ -101,8 +100,6 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { /** */ private final TypeMirror deploymentAwareMsgType; - /** */ - private final TypeMirror forwardedMsgType; /** */ private final TypeMirror selfMarshallingMsgType; @@ -149,7 +146,6 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { cacheObjType = type(CACHE_OBJECT_CLS); nonMarshallableType = type(NON_MARSHALLABLE_MESSAGE_INTERFACE); deploymentAwareMsgType = type(DEPLOYMENT_AWARE_MESSAGE_INTERFACE); - forwardedMsgType = type(FORWARDED_MESSAGE_INTERFACE); selfMarshallingMsgType = type(SELF_MARSHALLING_MESSAGE_INTERFACE); cacheGrpIdMsgType = type(GRID_CACHE_GROUP_ID_MESSAGE_CLS); mapType = type(Map.class.getName()); @@ -485,14 +481,11 @@ private void appendMarshalledFinish(List body) { indent++; code.add(indentedLine("%s = U.unmarshal(marshaller, %s, clsLdr);", objAcc, bytesAcc)); + code.add(EMPTY); // Drop the serialized cache once the object is restored: keeping both the deserialized value and its bytes // on every received message doubles retained memory (e.g. topology history nodes) and can exhaust the heap. - // A forwarded message keeps it: what it passes on has to be what it received. - if (!isForwarded()) { - code.add(EMPTY); - code.add(indentedLine("%s = null;", bytesAcc)); - } + code.add(indentedLine("%s = null;", bytesAcc)); indent--; @@ -1064,11 +1057,6 @@ private String deploymentResolutionLine() { + indentedLine(" clsLdr = kctx.deploy().classLoader(msg);"); } - /** @return {@code true} if the message is sent on after being read. */ - private boolean isForwarded() { - return forwardedMsgType != null && assignableFrom(type.asType(), forwardedMsgType); - } - /** @return {@code true} if the message carries the deployment of its classes. */ private boolean isDeploymentAware() { return deploymentAwareMsgType != null && assignableFrom(type.asType(), deploymentAwareMsgType); diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java index a4f1e9a59a83c..414771f0b5160 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java @@ -87,9 +87,6 @@ public class MessageProcessor extends AbstractProcessor { /** Message that reshapes its own fields before they go on the wire. */ static final String SELF_MARSHALLING_MESSAGE_INTERFACE = "org.apache.ignite.internal.SelfMarshallingMessage"; - /** Message sent on after being read, so its serialized form must stay as it arrived. */ - static final String FORWARDED_MESSAGE_INTERFACE = "org.apache.ignite.internal.ForwardedMessage"; - /** Message that carries the deployment of the classes inside it. */ static final String DEPLOYMENT_AWARE_MESSAGE_INTERFACE = "org.apache.ignite.internal.DeploymentAware"; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java deleted file mode 100644 index a3e0f27fc7947..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/ForwardedMessage.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.ignite.internal; - -import org.apache.ignite.plugin.extensions.communication.Message; - -/** - * Implemented by messages that are sent on after being read, a discovery message travelling the ring being the case. - * Such a message must reach every node in the form it left the sender, so the generated marshaller keeps the - * serialized form of its fields instead of dropping it once the objects are restored. - * - * @see Marshalled - */ -public interface ForwardedMessage extends Message { - // No-op. -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index bf181e1545f8b..362979caa2daf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -19,7 +19,6 @@ import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.DeploymentAware; -import org.apache.ignite.internal.ForwardedMessage; import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; @@ -30,7 +29,7 @@ /** * Start request data. */ -public class StartRequestData implements DeploymentAware, ForwardedMessage { +public class StartRequestData implements DeploymentAware { /** Node filter. */ @Marshalled("nodeFilterBytes") IgnitePredicate nodeFilter; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java index 5de529a158235..10ba07fcf4ec0 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java @@ -503,24 +503,6 @@ public void testNioFieldNeedingCacheContextFails() { assertThat(compilation).hadErrorContaining("needs a cache object context to unmarshal"); } - /** A message that is sent on after being read keeps the serialized form of its fields. */ - @Test - public void testForwardedMessage() { - Compilation compilation = compile("TestForwardedMessage.java"); - - assertThat(compilation).succeeded(); - - assertEquals(2, compilation.generatedSourceFiles().size()); - - assertThat(compilation) - .generatedSourceFile("org.apache.ignite.internal.TestForwardedMessageSerializer") - .hasSourceEquivalentTo(javaFile("TestForwardedMessageSerializer.java")); - - assertThat(compilation) - .generatedSourceFile("org.apache.ignite.internal.TestForwardedMessageMarshaller") - .hasSourceEquivalentTo(javaFile("TestForwardedMessageMarshaller.java")); - } - /** A message carrying its own deployment has the class loader resolved from it. */ @Test public void testDeploymentAwareMessage() { diff --git a/modules/core/src/test/resources/codegen/TestForwardedMessage.java b/modules/core/src/test/resources/codegen/TestForwardedMessage.java deleted file mode 100644 index c5b373dcbe802..0000000000000 --- a/modules/core/src/test/resources/codegen/TestForwardedMessage.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.ignite.internal; - -public class TestForwardedMessage implements ForwardedMessage { - @Marshalled("dataBytes") - Object data; - - @Order(0) - byte[] dataBytes; - - public short directType() { - return 0; - } -} diff --git a/modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java deleted file mode 100644 index 2070ad1713ac8..0000000000000 --- a/modules/core/src/test/resources/codegen/TestForwardedMessageMarshaller.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.ignite.internal; - -import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.internal.TestForwardedMessage; -import org.apache.ignite.internal.processors.cache.CacheObjectContext; -import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.marshaller.Marshaller; -import org.apache.ignite.plugin.extensions.communication.MessageMarshaller; - -/** - * This class is generated automatically. - * - * @see org.apache.ignite.internal.MessageProcessor - */ -public final class TestForwardedMessageMarshaller implements MessageMarshaller { - /** */ - private final Marshaller marshaller; - - /** */ - public TestForwardedMessageMarshaller(Marshaller marshaller) { - this.marshaller = marshaller; - } - - /** */ - @Override public void marshal(TestForwardedMessage msg, GridKernalContext kctx, CacheObjectContext cacheObjCtx) throws IgniteCheckedException { - if (msg.data != null && msg.dataBytes == null) - msg.dataBytes = U.marshal(marshaller, msg.data); - } - - /** */ - @Override public void unmarshal(TestForwardedMessage msg, GridKernalContext kctx, CacheObjectContext cacheObjCtx, ClassLoader clsLdr) throws IgniteCheckedException { - if (msg.dataBytes != null) { - msg.data = U.unmarshal(marshaller, msg.dataBytes, clsLdr); - } - } -} \ No newline at end of file diff --git a/modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java b/modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java deleted file mode 100644 index 92137b53d39fa..0000000000000 --- a/modules/core/src/test/resources/codegen/TestForwardedMessageSerializer.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.ignite.internal; - -import org.apache.ignite.internal.TestForwardedMessage; -import org.apache.ignite.plugin.extensions.communication.MessageReader; -import org.apache.ignite.plugin.extensions.communication.MessageSerializer; -import org.apache.ignite.plugin.extensions.communication.MessageWriter; - -/** - * This class is generated automatically. - * - * @see org.apache.ignite.internal.MessageProcessor - */ -public final class TestForwardedMessageSerializer implements MessageSerializer { - /** */ - @Override public final boolean writeTo(TestForwardedMessage msg, MessageWriter writer) { - if (!writer.isHeaderWritten()) { - if (!writer.writeHeader(msg.directType())) - return false; - - writer.onHeaderWritten(); - } - - switch (writer.state()) { - case 0: - if (!writer.writeByteArray(msg.dataBytes)) - return false; - - writer.incrementState(); - } - - return true; - } - - /** */ - @Override public final boolean readFrom(TestForwardedMessage msg, MessageReader reader) { - switch (reader.state()) { - case 0: - msg.dataBytes = reader.readByteArray(); - - if (!reader.isLastRead()) - return false; - - reader.incrementState(); - } - - return true; - } - - /** {@inheritDoc} */ - @Override public final TestForwardedMessage createMessage() { - return new TestForwardedMessage(); - } -} \ No newline at end of file From 58a3d33b10ea45c8f6ad6e90c3ac96ca18debfbf Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Sat, 8 Aug 2026 23:52:22 +0300 Subject: [PATCH 13/19] IGNITE-28528 Self-review fixes Fixes the import order the strict checkstyle profile rejects, and drops what earlier iterations left behind: an unused constant, an unused overload, and a javadoc line describing a design that is no longer there. Co-Authored-By: Claude Opus 5 --- .../internal/MessageMarshallerGenerator.java | 3 +-- .../ignite/internal/MessageProcessor.java | 3 --- .../ignite/internal/DeploymentAware.java | 4 ++-- .../deployment/GridDeploymentManager.java | 24 +++++-------------- .../continuous/GridContinuousProcessor.java | 2 +- .../discovery/tcp/TcpDiscoveryIoSession.java | 2 +- .../zk/internal/DiscoveryMessageParser.java | 2 +- 7 files changed, 12 insertions(+), 28 deletions(-) diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 770796a9c1085..0503261a77299 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -27,8 +27,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import java.util.Set; +import java.util.function.BiFunction; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; @@ -100,7 +100,6 @@ public class MessageMarshallerGenerator extends MessageCompanionGenerator { /** */ private final TypeMirror deploymentAwareMsgType; - /** */ private final TypeMirror selfMarshallingMsgType; diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java index 0dfec55e1a021..34f5dd7021249 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java @@ -90,9 +90,6 @@ public class MessageProcessor extends AbstractProcessor { /** Message that carries the deployment of the classes inside it. */ static final String DEPLOYMENT_AWARE_MESSAGE_INTERFACE = "org.apache.ignite.internal.DeploymentAware"; - /** Message unmarshalled by its owner instead of the receiving path. */ - static final String DEFERRED_UNMARSHAL_MESSAGE_INTERFACE = "org.apache.ignite.internal.DeferredUnmarshalMessage"; - /** Marker of messages with no marshaller. */ static final String NON_MARSHALLABLE_MESSAGE_INTERFACE = "org.apache.ignite.plugin.extensions.communication.NonMarshallableMessage"; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java index bd8d14a5877d4..f50c60a7ea9d2 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java @@ -26,8 +26,8 @@ * object context. *

* Resolving may have to request the deployment from its owner and block, so a message stating this must be unmarshalled - * where blocking is allowed: not from a socket-reading thread. A message that cannot promise that keeps its deployment - * as a plain field and asks {@code GridDeploymentManager} for the loader itself, as {@code StartRequestData} does. + * where blocking is allowed, never from a socket-reading thread. A message read on such a thread states + * {@code DeferredUnmarshalMessage} as well, leaving the read to its owner. * * @see MarshallableMessage */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java index f23627df270c2..009ac8af6490f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java @@ -405,31 +405,19 @@ private GridDeployment checkDeployment(GridDeployment deployment, String store) } /** - * Resolves the class loader the classes of a message must be read with. + * Resolves the class loader the classes of a message must be read with. Blocks when the deployment has to be + * requested from its owner, so it must not be called from a socket-reading thread. * * @param msg Message carrying its own deployment. * @return Class loader of the carried deployment, or the local one if the message carries none. * @throws IgniteDeploymentCheckedException If the deployment cannot be obtained. */ public ClassLoader classLoader(DeploymentAware msg) throws IgniteDeploymentCheckedException { - return classLoader(msg.deploymentInfo(), msg.deployedClassName()); - } - - /** - * Resolves the class loader classes described by {@code depInfo} must be read with. Blocks when the deployment has - * to be requested from its owner, so it must not be called from a socket-reading thread. - * - * @param depInfo Deployment of the classes, or {@code null} when they carry none. - * @param clsName Name of a class the deployment must be able to load. - * @return Class loader of the deployment, or the local one when there is no deployment. - * @throws IgniteDeploymentCheckedException If the deployment cannot be obtained. - */ - public ClassLoader classLoader(@Nullable GridDeploymentInfo depInfo, String clsName) - throws IgniteDeploymentCheckedException { - if (depInfo == null) + if (msg.deploymentInfo() == null) return U.resolveClassLoader(ctx.config()); - return U.resolveClassLoader(globalDeployment(depInfo, clsName).classLoader(), ctx.config()); + return U.resolveClassLoader(globalDeployment(msg.deploymentInfo(), msg.deployedClassName()).classLoader(), + ctx.config()); } /** @@ -455,7 +443,7 @@ public GridDeployment globalDeployment(GridDeploymentInfo depInfo, String clsNam /** * Resolves the deployment {@code depInfo} describes, as {@link #globalDeployment(GridDeploymentInfo, String)} - * does, but under {@code rsrcName} — a task may be deployed under a name of its own — and returns {@code null} + * does, but under {@code rsrcName} (a task may be deployed under a name of its own) and returns {@code null} * instead of throwing, for callers that have somewhere else to look. * * @param depInfo Deployment of the classes, as it came with the message carrying them. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java index 8c4aa773b9865..dd2dced416abc 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java @@ -57,8 +57,8 @@ import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; import org.apache.ignite.internal.managers.communication.GridMessageListener; -import org.apache.ignite.internal.managers.deployment.GridDeployment; import org.apache.ignite.internal.managers.communication.MessageMarshalling; +import org.apache.ignite.internal.managers.deployment.GridDeployment; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; import org.apache.ignite.internal.managers.discovery.CustomEventListener; import org.apache.ignite.internal.managers.discovery.DiscoCache; diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 77cacb35bde90..6664cd78eaef3 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -32,10 +32,10 @@ import javax.net.ssl.SSLSocket; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.util.CommonUtils; diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java index a3603c1755016..441e9042e9ba5 100644 --- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java +++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java @@ -26,10 +26,10 @@ import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.util.CommonUtils; import org.apache.ignite.internal.util.nio.MessageSerialization; From 4b2985be4457e6e73ca22f21c47cc4e570bd11cc Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Sun, 9 Aug 2026 00:39:49 +0300 Subject: [PATCH 14/19] IGNITE-28528 Indent the generated loader resolution through the generator Co-Authored-By: Claude Opus 5 --- .../internal/MessageMarshallerGenerator.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index 0503261a77299..e9f2f7d89b6d8 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -312,7 +312,7 @@ private void generateUnmarshalMethod(String params, List fields appendBlock(body, List.of(ctxResolutionLine())); if (isDeploymentAware()) - appendBlock(body, List.of(deploymentResolutionLine())); + appendBlock(body, deploymentResolutionLines()); appendFields(body, fields, MarshalMode.UNMARSHAL, wireFieldSkip); @@ -1051,9 +1051,18 @@ else if (isCacheGroupIdMessage(type)) * Returns the lines resolving the class loader of a {@code DeploymentAware} message: the caller may pass one, and * when it does not, the deployment the message carries gives it. Mirrors {@link #ctxResolutionLine()}. */ - private String deploymentResolutionLine() { - return indentedLine("if (clsLdr == null)") + NL - + indentedLine(" clsLdr = kctx.deploy().classLoader(msg);"); + private List deploymentResolutionLines() { + List code = new ArrayList<>(); + + code.add(indentedLine("if (clsLdr == null)")); + + indent++; + + code.add(indentedLine("clsLdr = kctx.deploy().classLoader(msg);")); + + indent--; + + return code; } /** @return {@code true} if the message carries the deployment of its classes. */ From 528b04f5444a81c22298628200a17fb435f17210 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Sun, 9 Aug 2026 00:57:05 +0300 Subject: [PATCH 15/19] IGNITE-28528 Rename GridDeploymentInfoBean to GridDeploymentInfoMessage The internal code uses the Bean suffix nowhere else: what is left is Spring, public API and JMX. This class is a Message carrying a deployment descriptor inside other messages, like BinaryMetadataVersionInfo or NodeEncryptionKeys, and its name now says so. It cannot simply drop the suffix, since it implements the GridDeploymentInfo interface. Co-Authored-By: Claude Opus 5 --- .../ignite/internal/CoreMessagesProvider.java | 4 ++-- .../internal/GridEventConsumeHandler.java | 4 ++-- .../ignite/internal/GridJobExecuteRequest.java | 6 +++--- .../internal/GridMessageListenHandler.java | 8 ++++---- .../communication/GridIoUserMessage.java | 6 +++--- ...ean.java => GridDeploymentInfoMessage.java} | 16 ++++++++-------- .../eventstorage/GridEventStorageRequest.java | 6 +++--- .../cache/GridCacheDeploymentManager.java | 18 +++++++++--------- .../processors/cache/GridCacheMessage.java | 10 +++++----- .../CacheContinuousQueryDeployableObject.java | 4 ++-- .../continuous/GridContinuousProcessor.java | 6 +++--- .../continuous/StartRequestData.java | 6 +++--- .../datastreamer/DataStreamerRequest.java | 6 +++--- .../resources/META-INF/classnames.properties | 2 +- .../p2p/ClassLoadingProblemExceptionTest.java | 4 ++-- .../codegen/TestDeploymentAwareMessage.java | 4 ++-- 16 files changed, 55 insertions(+), 55 deletions(-) rename modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/{GridDeploymentInfoBean.java => GridDeploymentInfoMessage.java} (86%) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java index a460ed789d24b..3b066879eea24 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java @@ -30,7 +30,7 @@ import org.apache.ignite.internal.managers.communication.IgniteIoTestMessage; import org.apache.ignite.internal.managers.communication.IgniteMessageFactory; import org.apache.ignite.internal.managers.communication.SessionChannelMessage; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.managers.deployment.GridDeploymentRequest; import org.apache.ignite.internal.managers.deployment.GridDeploymentResponse; import org.apache.ignite.internal.managers.encryption.ChangeCacheEncryptionRequest; @@ -686,7 +686,7 @@ public CoreMessagesProvider(Marshaller dfltMarsh, Marshaller schemaAwareMarsh) { // [12200 - 12300]: Binary, classloading and marshalling messages. msgIdx = 12200; - register(GridDeploymentInfoBean.class); + register(GridDeploymentInfoMessage.class); register(GridDeploymentRequest.class); register(GridDeploymentResponse.class); register(MissingMappingRequestMessage.class); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java index 300e93a43771c..ce23c7f083180 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridEventConsumeHandler.java @@ -34,7 +34,7 @@ import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; import org.apache.ignite.internal.managers.deployment.GridDeployment; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.managers.deployment.P2PClassLoadingIssues; import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; @@ -403,7 +403,7 @@ private boolean filterDropsEvent(Event evt) { if (dep == null) throw new IgniteDeploymentCheckedException("Failed to deploy event filter: " + filter); - depInfo = new GridDeploymentInfoBean(dep); + depInfo = new GridDeploymentInfoMessage(dep); filterBytes = U.marshal(ctx.marshaller(), filter); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java index 5ddd444fe030d..c431610e1ea44 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java @@ -25,7 +25,7 @@ import org.apache.ignite.compute.ComputeJob; import org.apache.ignite.compute.ComputeJobSibling; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.util.tostring.GridToStringExclude; import org.apache.ignite.internal.util.typedef.internal.S; @@ -72,7 +72,7 @@ public class GridJobExecuteRequest implements ExecutorAwareMessage, DeferredUnma /** Deployment of the task classes. */ @Order(6) - GridDeploymentInfoBean depInfo; + GridDeploymentInfoMessage depInfo; /** */ @Order(7) @@ -224,7 +224,7 @@ public GridJobExecuteRequest( this.sesId = sesId; this.jobId = jobId; this.taskName = taskName; - this.depInfo = new GridDeploymentInfoBean(depInfo); + this.depInfo = new GridDeploymentInfoMessage(depInfo); this.taskClsName = taskClsName; this.job = job; this.startTaskTime = startTaskTime; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java index 0226e982d51fb..d177374920dc0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridMessageListenHandler.java @@ -28,7 +28,7 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.internal.managers.deployment.GridDeployment; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.continuous.GridContinuousBatch; import org.apache.ignite.internal.processors.continuous.GridContinuousBatchAdapter; @@ -64,7 +64,7 @@ public class GridMessageListenHandler implements GridContinuousHandler { private String clsName; /** */ - private GridDeploymentInfoBean depInfo; + private GridDeploymentInfoMessage depInfo; /** */ private boolean depEnabled; @@ -166,7 +166,7 @@ public GridMessageListenHandler(@Nullable Object topic, IgniteBiPredicate filter, GridDeploymentInfo depInfo) { this.resTopicId = resTopicId; this.filter = filter; - this.depInfo = new GridDeploymentInfoBean(depInfo); + this.depInfo = new GridDeploymentInfoMessage(depInfo); filterClsName = filter.getClass().getName(); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java index 992523b9faf09..46a7c046ea721 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheDeploymentManager.java @@ -30,7 +30,7 @@ import org.apache.ignite.events.DiscoveryEvent; import org.apache.ignite.events.Event; import org.apache.ignite.internal.managers.deployment.GridDeployment; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener; import org.apache.ignite.internal.util.lang.GridPeerDeployAware; import org.apache.ignite.internal.util.tostring.GridToStringInclude; @@ -395,14 +395,14 @@ public void prepare(GridCacheDeployable deployable) throws IgnitePeerToPeerClass // Only set deployment info if it was not set automatically. if (deployable.deployInfo() == null) { - GridDeploymentInfoBean dep = globalDeploymentInfo(); + GridDeploymentInfoMessage dep = globalDeploymentInfo(); if (dep == null) { GridDeployment locDep0 = locDep.get(); if (locDep0 != null) { // Will copy sequence number to bean. - dep = new GridDeploymentInfoBean(locDep0); + dep = new GridDeploymentInfoMessage(locDep0); checkDeploymentIsCorrect(dep, deployable, false); } @@ -426,7 +426,7 @@ public void prepare(GridCacheDeployable deployable) throws IgnitePeerToPeerClass * @param failIfNotCorrect Flag determining whether to throw exception or just warn. * @throws IgnitePeerToPeerClassLoadingException If deployment is incorrect. */ - private void checkDeploymentIsCorrect(GridDeploymentInfoBean deployment, GridCacheDeployable deployable, + private void checkDeploymentIsCorrect(GridDeploymentInfoMessage deployment, GridCacheDeployable deployable, boolean failIfNotCorrect) throws IgnitePeerToPeerClassLoadingException { if (deployment.participants() == null @@ -445,7 +445,7 @@ private void checkDeploymentIsCorrect(GridDeploymentInfoBean deployment, GridCac /** * @return First global deployment. */ - @Nullable public GridDeploymentInfoBean globalDeploymentInfo() { + @Nullable public GridDeploymentInfoMessage globalDeploymentInfo() { assert depEnabled; // Do not return info if mode is CONTINUOUS. @@ -456,14 +456,14 @@ private void checkDeploymentIsCorrect(GridDeploymentInfoBean deployment, GridCac IgniteUuid locLdrId0 = localLdrId.get(); if (locLdrId0 != null) { - GridDeploymentInfoBean deploymentInfoBean = getDepBean(deps.get(localLdrId.get())); + GridDeploymentInfoMessage deploymentInfoBean = getDepBean(deps.get(localLdrId.get())); if (deploymentInfoBean != null) return deploymentInfoBean; } for (CachedDeploymentInfo d : deps.values()) { - GridDeploymentInfoBean deploymentInfoBean = getDepBean(d); + GridDeploymentInfoMessage deploymentInfoBean = getDepBean(d); if (deploymentInfoBean != null) return deploymentInfoBean; } @@ -472,7 +472,7 @@ private void checkDeploymentIsCorrect(GridDeploymentInfoBean deployment, GridCac } /** */ - @Nullable private GridDeploymentInfoBean getDepBean(CachedDeploymentInfo d) { + @Nullable private GridDeploymentInfoMessage getDepBean(CachedDeploymentInfo d) { if (d == null || cctx.discovery().node(d.senderId()) == null) // Sender has left. return null; @@ -484,7 +484,7 @@ private void checkDeploymentIsCorrect(GridDeploymentInfoBean deployment, GridCac for (UUID id : participants.keySet()) { if (cctx.discovery().node(id) != null) { // At least 1 participant is still in the grid. - return new GridDeploymentInfoBean( + return new GridDeploymentInfoMessage( d.loaderId(), d.userVersion(), d.mode(), diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMessage.java index 2f09c39eb9fc9..8cdceed58e393 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMessage.java @@ -29,7 +29,7 @@ import org.apache.ignite.internal.StripedMessage; import org.apache.ignite.internal.managers.deployment.GridDeployment; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry; import org.apache.ignite.internal.util.tostring.GridToStringInclude; @@ -65,7 +65,7 @@ public abstract class GridCacheMessage implements DeferredUnmarshalMessage, Stri /** */ @GridToStringInclude @Order(1) - public GridDeploymentInfoBean depInfo; + public GridDeploymentInfoMessage depInfo; /** */ @GridToStringInclude @@ -257,8 +257,8 @@ public final void deploy(GridDeploymentInfo depInfo) { if (((GridDeployment)depInfo).local()) return; - this.depInfo = depInfo instanceof GridDeploymentInfoBean ? - (GridDeploymentInfoBean)depInfo : new GridDeploymentInfoBean(depInfo); + this.depInfo = depInfo instanceof GridDeploymentInfoMessage ? + (GridDeploymentInfoMessage)depInfo : new GridDeploymentInfoMessage(depInfo); } } @@ -266,7 +266,7 @@ public final void deploy(GridDeploymentInfo depInfo) { * @return Preset deployment info. * @see GridCacheDeployable#deployInfo() */ - public GridDeploymentInfoBean deployInfo() { + public GridDeploymentInfoMessage deployInfo() { return depInfo; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java index 647098ee50e7b..9453d84464a36 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryDeployableObject.java @@ -27,7 +27,7 @@ import org.apache.ignite.internal.IgniteDeploymentCheckedException; import org.apache.ignite.internal.managers.deployment.GridDeployment; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.util.tostring.GridToStringExclude; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; @@ -74,7 +74,7 @@ protected CacheContinuousQueryDeployableObject(Object obj, GridKernalContext ctx if (dep == null) throw new IgniteDeploymentCheckedException("Failed to deploy object: " + obj); - depInfo = new GridDeploymentInfoBean(dep); + depInfo = new GridDeploymentInfoMessage(dep); bytes = U.marshal(ctx, obj); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java index dd2dced416abc..18fd7e6ba8cf3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java @@ -59,7 +59,7 @@ import org.apache.ignite.internal.managers.communication.GridMessageListener; import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.deployment.GridDeployment; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.managers.discovery.CustomEventListener; import org.apache.ignite.internal.managers.discovery.DiscoCache; import org.apache.ignite.internal.managers.discovery.DiscoveryMessageResultsCollector; @@ -952,7 +952,7 @@ private AbstractContinuousMessage createStartMessage(UUID routineId, hnd = hnd.clone(); String clsName = null; - GridDeploymentInfoBean dep = null; + GridDeploymentInfoMessage dep = null; if (ctx.config().isPeerClassLoadingEnabled()) { // Handle peer deployment for projection predicate. @@ -966,7 +966,7 @@ private AbstractContinuousMessage createStartMessage(UUID routineId, if (dep0 == null) throw new IgniteDeploymentCheckedException("Failed to deploy projection predicate: " + nodeFilter); - dep = new GridDeploymentInfoBean(dep0); + dep = new GridDeploymentInfoMessage(dep0); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index 362979caa2daf..42994fc155c13 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -22,7 +22,7 @@ import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgnitePredicate; @@ -44,7 +44,7 @@ public class StartRequestData implements DeploymentAware { /** Deployment info. */ @Order(2) - GridDeploymentInfoBean depInfo; + GridDeploymentInfoMessage depInfo; /** Handler. */ @Marshalled("hndBytes") @@ -116,7 +116,7 @@ public void className(String clsName) { /** * @param depInfo New deployment info. */ - public void deploymentInfo(GridDeploymentInfoBean depInfo) { + public void deploymentInfo(GridDeploymentInfoMessage depInfo) { this.depInfo = depInfo; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java index ea709734d1f0c..6d941ce969a2f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastreamer/DataStreamerRequest.java @@ -22,7 +22,7 @@ import org.apache.ignite.internal.Order; import org.apache.ignite.internal.StripedMessage; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.GridCacheUtils; import org.apache.ignite.internal.util.typedef.internal.S; @@ -70,7 +70,7 @@ public class DataStreamerRequest implements DeferredUnmarshalMessage, CacheIdAwa /** Deployment of the streamed classes. */ @Order(8) - GridDeploymentInfoBean depInfo; + GridDeploymentInfoMessage depInfo; /** */ @Order(9) @@ -133,7 +133,7 @@ public DataStreamerRequest( this.ignoreDepOwnership = ignoreDepOwnership; this.skipStore = skipStore; this.keepBinary = keepBinary; - this.depInfo = depInfo != null ? new GridDeploymentInfoBean(depInfo) : null; + this.depInfo = depInfo != null ? new GridDeploymentInfoMessage(depInfo) : null; this.sampleClsName = sampleClsName; this.forceLocDep = forceLocDep; this.topVer = topVer; diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties index fc1966fef4b4e..b9e5b18ebcaef 100644 --- a/modules/core/src/main/resources/META-INF/classnames.properties +++ b/modules/core/src/main/resources/META-INF/classnames.properties @@ -711,7 +711,7 @@ org.apache.ignite.internal.managers.communication.SessionChannelMessage org.apache.ignite.internal.managers.communication.TransmissionCancelledException org.apache.ignite.internal.managers.communication.TransmissionMeta org.apache.ignite.internal.managers.communication.TransmissionPolicy -org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean +org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage org.apache.ignite.internal.managers.deployment.GridDeploymentPerVersionStore$2 org.apache.ignite.internal.managers.deployment.GridDeploymentRequest org.apache.ignite.internal.managers.deployment.GridDeploymentResponse diff --git a/modules/core/src/test/java/org/apache/ignite/p2p/ClassLoadingProblemExceptionTest.java b/modules/core/src/test/java/org/apache/ignite/p2p/ClassLoadingProblemExceptionTest.java index 1af74a63c3340..cfab17bfa5668 100644 --- a/modules/core/src/test/java/org/apache/ignite/p2p/ClassLoadingProblemExceptionTest.java +++ b/modules/core/src/test/java/org/apache/ignite/p2p/ClassLoadingProblemExceptionTest.java @@ -38,7 +38,7 @@ import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.managers.communication.GridIoMessage; import org.apache.ignite.internal.managers.deployment.GridDeployment; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.managers.deployment.GridDeploymentManager; import org.apache.ignite.internal.managers.deployment.GridDeploymentMetadata; import org.apache.ignite.internal.managers.deployment.GridDeploymentStore; @@ -197,7 +197,7 @@ private class TestCommunicationSpi extends TcpCommunicationSpi { GridCacheQueryRequest qryReq = (GridCacheQueryRequest)m; if (qryReq.deployInfo() != null) { - qryReq.deploy(new GridDeploymentInfoBean( + qryReq.deploy(new GridDeploymentInfoMessage( IgniteUuid.fromUuid(UUID.randomUUID()), qryReq.deployInfo().userVersion(), qryReq.deployInfo().deployMode(), diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java index d1aec991f1a8f..08288583e5b13 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessage.java @@ -18,7 +18,7 @@ package org.apache.ignite.internal; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoBean; +import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; public class TestDeploymentAwareMessage implements DeploymentAware { @Marshalled("dataBytes") @@ -28,7 +28,7 @@ public class TestDeploymentAwareMessage implements DeploymentAware { byte[] dataBytes; @Order(1) - GridDeploymentInfoBean depInfo; + GridDeploymentInfoMessage depInfo; @Order(2) String clsName; From 556ca2d4c8b726d6a615177271aefc868088b520 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Sun, 9 Aug 2026 01:15:15 +0300 Subject: [PATCH 16/19] IGNITE-28528 Resolve the deployment loader regardless of what the caller passed The generated resolution ran only when the caller passed no class loader, but the convenience overloads pass the local one, so a caller using them would read peer-deployed classes without peer class loading. A message that carries its own deployment knows the loader better than its caller. Co-Authored-By: Claude Opus 5 --- .../internal/MessageMarshallerGenerator.java | 21 ++++++------------- .../eventstorage/GridEventStorageManager.java | 3 +-- .../TestDeploymentAwareMessageMarshaller.java | 3 +-- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java index e9f2f7d89b6d8..7fa4d3e7a6969 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageMarshallerGenerator.java @@ -312,7 +312,7 @@ private void generateUnmarshalMethod(String params, List fields appendBlock(body, List.of(ctxResolutionLine())); if (isDeploymentAware()) - appendBlock(body, deploymentResolutionLines()); + appendBlock(body, List.of(deploymentResolutionLine())); appendFields(body, fields, MarshalMode.UNMARSHAL, wireFieldSkip); @@ -1048,21 +1048,12 @@ else if (isCacheGroupIdMessage(type)) } /** - * Returns the lines resolving the class loader of a {@code DeploymentAware} message: the caller may pass one, and - * when it does not, the deployment the message carries gives it. Mirrors {@link #ctxResolutionLine()}. + * Returns the line resolving the class loader of a {@code DeploymentAware} message. The deployment the message + * carries wins over whatever the caller passed: the caller cannot know the loader of classes deployed elsewhere, + * and the overloads defaulting to the local one would silently read them without peer class loading. */ - private List deploymentResolutionLines() { - List code = new ArrayList<>(); - - code.add(indentedLine("if (clsLdr == null)")); - - indent++; - - code.add(indentedLine("clsLdr = kctx.deploy().classLoader(msg);")); - - indent--; - - return code; + private String deploymentResolutionLine() { + return indentedLine("clsLdr = kctx.deploy().classLoader(msg);"); } /** @return {@code true} if the message carries the deployment of its classes. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java index 0ead54b373af8..44d188ea3f6df 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/eventstorage/GridEventStorageManager.java @@ -1242,8 +1242,7 @@ private class RequestListener implements GridMessageListener { Collection evts; try { - // No class loader: the message carries its own deployment, see GridEventStorageRequest. - MessageMarshalling.unmarshal(req, ctx, null, null); + MessageMarshalling.unmarshal(req, ctx); filter = (IgnitePredicate)req.filter(); diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java index 48acd95965773..0b7d9a843de08 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java @@ -55,8 +55,7 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { @Override public void unmarshal(TestDeploymentAwareMessage msg, GridKernalContext kctx, CacheObjectContext cacheObjCtx, ClassLoader clsLdr) throws IgniteCheckedException { CacheObjectContext ctx = cacheObjCtx; - if (clsLdr == null) - clsLdr = kctx.deploy().classLoader(msg); + clsLdr = kctx.deploy().classLoader(msg); if (msg.depInfo != null) MessageMarshalling.unmarshal(msg.depInfo, kctx, ctx, clsLdr); From 7454525e3e06eae1e50850ed4a180639d810487a Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Sun, 9 Aug 2026 01:46:11 +0300 Subject: [PATCH 17/19] Checkstyle autofix by Ignite PR Checker (requested via PR command) --- .../resources/codegen/TestDeploymentAwareMessageMarshaller.java | 2 +- .../resources/codegen/TestDeploymentAwareMessageSerializer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java index 0b7d9a843de08..b6067da72c7ce 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageMarshaller.java @@ -66,4 +66,4 @@ public TestDeploymentAwareMessageMarshaller(Marshaller marshaller) { msg.dataBytes = null; } } -} \ No newline at end of file +} diff --git a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java index e3fbc50e2a92c..2e589572a5260 100644 --- a/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestDeploymentAwareMessageSerializer.java @@ -95,4 +95,4 @@ public final class TestDeploymentAwareMessageSerializer implements MessageSerial @Override public final TestDeploymentAwareMessage createMessage() { return new TestDeploymentAwareMessage(); } -} \ No newline at end of file +} From e77db5cc81c28a356b8d80a8f9613e61743297a2 Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Sun, 9 Aug 2026 05:16:55 +0300 Subject: [PATCH 18/19] IGNITE-28528 Keep the start request off the generated marshaller A discovery custom message is not carried as bytes: TcpDiscoveryCustomEventMessage holds it as a nested message field, so the generated marshaller of the envelope reads the whole tree on the socket-reading thread. There a missing user class is swallowed with a warning, and the routine start never reports it back to the node that started it. The node filter and the handler therefore stay serialized on the message, and the continuous processor restores them where the failure can be reported. The deployment resolution is still a single call, which is what the ticket asks for. Co-Authored-By: Claude Opus 5 --- .../deployment/GridDeploymentManager.java | 19 ++++++++++--- .../continuous/GridContinuousProcessor.java | 27 ++++++++++--------- .../continuous/StartRequestData.java | 22 +++------------ .../StartRoutineDiscoveryMessage.java | 3 +-- .../discovery/tcp/TcpDiscoveryIoSession.java | 6 +---- .../zk/internal/DiscoveryMessageParser.java | 5 ---- 6 files changed, 36 insertions(+), 46 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java index 009ac8af6490f..543e969f9c9f7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/deployment/GridDeploymentManager.java @@ -413,11 +413,24 @@ private GridDeployment checkDeployment(GridDeployment deployment, String store) * @throws IgniteDeploymentCheckedException If the deployment cannot be obtained. */ public ClassLoader classLoader(DeploymentAware msg) throws IgniteDeploymentCheckedException { - if (msg.deploymentInfo() == null) + return classLoader(msg.deploymentInfo(), msg.deployedClassName()); + } + + /** + * Resolves the class loader classes described by {@code depInfo} must be read with. Blocks when the deployment has + * to be requested from its owner, so it must not be called from a socket-reading thread. + * + * @param depInfo Deployment of the classes, or {@code null} when they carry none. + * @param clsName Name of a class the deployment must be able to load. + * @return Class loader of the deployment, or the local one when there is no deployment. + * @throws IgniteDeploymentCheckedException If the deployment cannot be obtained. + */ + public ClassLoader classLoader(@Nullable GridDeploymentInfo depInfo, String clsName) + throws IgniteDeploymentCheckedException { + if (depInfo == null) return U.resolveClassLoader(ctx.config()); - return U.resolveClassLoader(globalDeployment(msg.deploymentInfo(), msg.deployedClassName()).classLoader(), - ctx.config()); + return U.resolveClassLoader(globalDeployment(depInfo, clsName).classLoader(), ctx.config()); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java index 18fd7e6ba8cf3..579fe098919bd 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/GridContinuousProcessor.java @@ -57,7 +57,6 @@ import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; import org.apache.ignite.internal.managers.communication.GridMessageListener; -import org.apache.ignite.internal.managers.communication.MessageMarshalling; import org.apache.ignite.internal.managers.deployment.GridDeployment; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.managers.discovery.CustomEventListener; @@ -987,9 +986,10 @@ private AbstractContinuousMessage createStartMessage(UUID routineId, hnd.p2pMarshal(ctx); } - // Marshalled here rather than left to the discovery layer: doing it there would marshal the user classes on - // the thread that writes the ring. - MessageMarshalling.marshal(reqData, ctx, null); + reqData.hndBytes = U.marshal(marsh, hnd); + + if (nodeFilter != null) + reqData.nodeFilterBytes = U.marshal(marsh, nodeFilter); if (!immutableDiscoCustomMsg) { StartRoutineDiscoveryMessage msg = new StartRoutineDiscoveryMessage(routineId, reqData, Mode.MUTABLE); @@ -1347,27 +1347,28 @@ private void processStartAckRequest(AffinityTopologyVersion topVer, } /** - * Reads a start request the discovery layer left untouched. Obtaining the deployment of the node filter and - * restoring the handler both take work that must not run on the thread reading the ring. + * Restores the objects a start request carries. The discovery layer reads the message on the thread that reads + * the ring, where obtaining a deployment must not happen, so the request keeps them serialized until here. * * @param msg Message carrying the request. * @param sndId Node that started the routine. */ private void unmarshalStartRequest(StartRoutineDiscoveryMessage msg, UUID sndId) throws IgniteCheckedException { - MessageMarshalling.unmarshal(msg, ctx); - StartRequestData data = msg.startRequestData(); - GridContinuousHandler hnd = data.handler(); + data.nodeFilter = U.unmarshal(marsh, data.nodeFilterBytes, + ctx.deploy().classLoader(data.depInfo, data.clsName)); + + if (data.hndBytes != null) { + data.hnd = U.unmarshal(marsh, data.hndBytes, U.resolveClassLoader(ctx.config())); - if (hnd != null) { if (ctx.config().isPeerClassLoadingEnabled()) - hnd.p2pUnmarshal(sndId, ctx); + data.hnd.p2pUnmarshal(sndId, ctx); if (data.keepBinary) { - assert hnd instanceof CacheContinuousQueryHandler : hnd; + assert data.hnd instanceof CacheContinuousQueryHandler : data.hnd; - ((CacheContinuousQueryHandler)hnd).keepBinary(true); + ((CacheContinuousQueryHandler)data.hnd).keepBinary(true); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java index 42994fc155c13..595aaa56c5865 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRequestData.java @@ -18,20 +18,17 @@ package org.apache.ignite.internal.processors.continuous; import org.apache.ignite.cluster.ClusterNode; -import org.apache.ignite.internal.DeploymentAware; -import org.apache.ignite.internal.Marshalled; import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.managers.deployment.GridDeploymentInfo; import org.apache.ignite.internal.managers.deployment.GridDeploymentInfoMessage; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgnitePredicate; +import org.apache.ignite.plugin.extensions.communication.Message; /** * Start request data. */ -public class StartRequestData implements DeploymentAware { - /** Node filter. */ - @Marshalled("nodeFilterBytes") +public class StartRequestData implements Message { + /** Node filter, restored by the processor reading this request. */ IgnitePredicate nodeFilter; /** Serialized node filter. */ @@ -46,8 +43,7 @@ public class StartRequestData implements DeploymentAware { @Order(2) GridDeploymentInfoMessage depInfo; - /** Handler. */ - @Marshalled("hndBytes") + /** Handler, restored by the processor reading this request. */ GridContinuousHandler hnd; /** Serialized handler. */ @@ -166,14 +162,4 @@ public void autoUnsubscribe(boolean autoUnsubscribe) { @Override public String toString() { return S.toString(StartRequestData.class, this); } - - /** {@inheritDoc} */ - @Override public GridDeploymentInfo deploymentInfo() { - return depInfo; - } - - /** {@inheritDoc} */ - @Override public String deployedClassName() { - return clsName; - } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java index a96feebb02721..1b22be66776c6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/continuous/StartRoutineDiscoveryMessage.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.UUID; import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; import org.apache.ignite.internal.util.ErrorMessage; @@ -32,7 +31,7 @@ /** * Discovery message used for Continuous Query registration. */ -public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage implements DeferredUnmarshalMessage { +public class StartRoutineDiscoveryMessage extends AbstractContinuousMessage { /** Discovery message mode. */ enum Mode { /** Mutable discovery mode. */ diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 6664cd78eaef3..e237db2cf3c7a 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -32,7 +32,6 @@ import javax.net.ssl.SSLSocket; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; @@ -198,10 +197,7 @@ T readMessage() throws IgniteCheckedException, IOException { } while (!finished); - // A deferred-unmarshal message is left as it arrived: reading it here would run user-class loading, and - // whatever else its owner does, on the thread that reads the ring. - if (!(msg instanceof DeferredUnmarshalMessage)) - MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); + MessageMarshalling.unmarshal(msg, ((IgniteEx)spi.ignite()).context()); return (T)msg; } diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java index 441e9042e9ba5..bcc10b0bbb018 100644 --- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java +++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java @@ -26,7 +26,6 @@ import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.internal.DeferredUnmarshalMessage; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; @@ -141,10 +140,6 @@ private T deserializeMessage(InputStream in) throws IOExcept } while (!finished); - // A deferred-unmarshal message is left as it arrived, for its owner to read where the work it takes is allowed. - if (msg instanceof DeferredUnmarshalMessage) - return (T)msg; - try { MessageMarshalling.unmarshal(msg, kctx); } From 4f87ac390c0cbf52ae139521fcc4c142f69543ea Mon Sep 17 00:00:00 2001 From: Anton Vinogradov Date: Sun, 9 Aug 2026 05:30:41 +0300 Subject: [PATCH 19/19] IGNITE-28528 Fix the DeploymentAware javadoc Co-Authored-By: Claude Opus 5 --- .../java/org/apache/ignite/internal/DeploymentAware.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java index f50c60a7ea9d2..5e1b6fb92afa4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/DeploymentAware.java @@ -25,9 +25,12 @@ * resolve the class loader those classes are read with, the same way {@code CacheIdAware} lets it resolve the cache * object context. *

- * Resolving may have to request the deployment from its owner and block, so a message stating this must be unmarshalled - * where blocking is allowed, never from a socket-reading thread. A message read on such a thread states - * {@code DeferredUnmarshalMessage} as well, leaving the read to its owner. + * Resolving may have to request the deployment from its owner and block, and it fails when the classes are gone, so a + * message stating this must be unmarshalled where blocking is allowed and where the failure reaches whoever waits for + * it. A message read on a socket-reading thread promises neither: a discovery custom message, for one, is a nested + * field of its envelope, so the envelope's marshaller reads the whole tree there, and a missing class is swallowed + * with a warning. Such a message keeps its deployment as a plain field and asks {@code GridDeploymentManager} for the + * loader where it is read, as {@code StartRequestData} does. * * @see MarshallableMessage */