This is an automated email from the ASF dual-hosted git repository.
petrov-mg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 84eaa722f38 IGNITE-29006 Refactored TcpDiscoveryMessageSerializer to
handle serialization only (#13514)
84eaa722f38 is described below
commit 84eaa722f38dd61fe4516b2d318a235d87d7b361
Author: Mikhail Petrov <[email protected]>
AuthorDate: Tue Sep 22 14:39:14 2026 +0300
IGNITE-29006 Refactored TcpDiscoveryMessageSerializer to handle
serialization only (#13514)
---
.../ignite/spi/discovery/tcp/ServerImpl.java | 134 +++++++++------------
.../spi/discovery/tcp/TcpDiscoveryIoSession.java | 70 ++++-------
.../tcp/TcpDiscoveryMessageSerializer.java | 68 -----------
.../tcp/internal/ClientMessageHolder.java | 59 +++++++++
.../internal/TcpDiscoveryMessageSerializer.java | 96 +++++++++++++++
5 files changed, 239 insertions(+), 188 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
index 5974e617807..1356c3d04d2 100644
---
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
+++
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
@@ -99,7 +99,6 @@ import
org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.typedef.C1;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.P1;
-import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.LT;
import org.apache.ignite.internal.util.typedef.internal.S;
@@ -122,8 +121,10 @@ import
org.apache.ignite.spi.discovery.DiscoveryNotification;
import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage;
import org.apache.ignite.spi.discovery.DiscoverySpiListener;
import org.apache.ignite.spi.discovery.IgniteDiscoveryThread;
+import org.apache.ignite.spi.discovery.tcp.internal.ClientMessageHolder;
import org.apache.ignite.spi.discovery.tcp.internal.DiscoveryDataPacket;
import org.apache.ignite.spi.discovery.tcp.internal.FutureTask;
+import
org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryMessageSerializer;
import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNodesRing;
import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoverySpiState;
@@ -2851,15 +2852,11 @@ class ServerImpl extends TcpDiscoveryImpl {
/** Force pending messages send. */
private boolean forceSndPending;
- // This serializer is used exclusively for serializing messages sent
to clients,
- // as it represents a special case within the RingMessageWorker
workflow.
- // Generally, both serialization and deserialization of messages
should be handled by TcpDiscoveryIoSession.
- // However, there are scenarios where the session is not available,
yet messages still need to be sent to clients.
- // A typical example is a single server with one or more connected
clients.
- // To address this, we use TcpDiscoveryMessageSerializer, which
includes some code copied from TcpDiscoveryIoSession
- // and can be instantiated independently of any active session.
- /** */
- private final TcpDiscoveryMessageSerializer clientMsgSer = new
TcpDiscoveryMessageSerializer(ctx);
+ /**
+ * This serializer is used exclusively for pre-serializing messages
sent to clients. Pre-serialization is performed
+ * once for each message, after which the serialized message is reused
for sending to all connected clients.
+ */
+ private final TcpDiscoveryMessageSerializer cliMsgSer = new
TcpDiscoveryMessageSerializer(ctx);
/** IO session. */
private TcpDiscoveryIoSession ses;
@@ -3229,50 +3226,52 @@ class ServerImpl extends TcpDiscoveryImpl {
* @param msg Message.
*/
private void sendMessageToClients(TcpDiscoveryAbstractMessage msg) {
- if (redirectToClients(msg)) {
- if (spi.ensured(msg))
- msgHist.add(msg);
+ if (!redirectToClients(msg))
+ return;
- if (clientMsgWorkers.isEmpty())
- return;
+ if (spi.ensured(msg))
+ msgHist.add(msg);
+
+ if (clientMsgWorkers.isEmpty())
+ return;
+
+ ClientMessageHolder sharedMsgHolder = new ClientMessageHolder(msg);
+
+ for (ClientMessageWorker worker : clientMsgWorkers.values()) {
+ TcpDiscoveryAbstractMessage rebuiltMsg = rebuildForClient(msg,
worker.clientNodeId);
- byte[] msgBytes;
+ ClientMessageHolder msgToSend = rebuiltMsg == msg
+ ? sharedMsgHolder
+ : new ClientMessageHolder(rebuiltMsg);
try {
- msgBytes = clientMsgSer.serializeMessage(msg);
+ msgToSend.serialize(cliMsgSer);
}
- catch (IgniteCheckedException | IOException e) {
- U.error(log, "Failed to serialize message: " + msg, e);
+ catch (IgniteCheckedException e) {
+ U.error(log, "Failed to serialize message: " + msgToSend,
e);
return;
}
- for (ClientMessageWorker clientMsgWorker :
clientMsgWorkers.values()) {
- TcpDiscoveryAbstractMessage msg0 = msg;
- byte[] msgBytes0 = msgBytes;
+ worker.addMessage(msgToSend);
+ }
+ }
- if (msg instanceof TcpDiscoveryNodeAddedMessage) {
- TcpDiscoveryNodeAddedMessage nodeAddedMsg =
(TcpDiscoveryNodeAddedMessage)msg;
+ /** */
+ private TcpDiscoveryAbstractMessage
rebuildForClient(TcpDiscoveryAbstractMessage msg, UUID clientNodeId) {
+ if (!(msg instanceof TcpDiscoveryNodeAddedMessage))
+ return msg;
- if
(clientMsgWorker.clientNodeId.equals(nodeAddedMsg.node().id())) {
- msg0 = new
TcpDiscoveryNodeAddedMessage(nodeAddedMsg);
+ TcpDiscoveryNodeAddedMessage nodeAddedMsg =
(TcpDiscoveryNodeAddedMessage)msg;
- prepareNodeAddedMessage(msg0,
clientMsgWorker.clientNodeId, null);
+ if (!clientNodeId.equals(nodeAddedMsg.node().id()))
+ return msg;
- try {
- msgBytes0 =
clientMsgSer.serializeMessage(msg0);
- }
- catch (IgniteCheckedException | IOException e) {
- U.error(log, "Failed to serialize message: " +
msg0, e);
+ TcpDiscoveryNodeAddedMessage res = new
TcpDiscoveryNodeAddedMessage(nodeAddedMsg);
- return;
- }
- }
- }
+ prepareNodeAddedMessage(res, clientNodeId, null);
- clientMsgWorker.addMessage(msg0, msgBytes0);
- }
- }
+ return res;
}
/**
@@ -7469,21 +7468,10 @@ class ServerImpl extends TcpDiscoveryImpl {
}
/** */
- private class ClientMessageWorker extends
MessageWorker<T2<TcpDiscoveryAbstractMessage, byte[]>> {
+ private class ClientMessageWorker extends
MessageWorker<ClientMessageHolder> {
/** Node ID. */
private final UUID clientNodeId;
- // The code responsible for sending and receiving messages to and from
client nodes represents a special case in ServerImpl,
- // as it is split into two separate components.
- // One part, ClientMessageWorker, handles only message sending to
clients and does not process responses.
- // The other part, which reads messages from clients, is implemented
in SocketReader.
- // Due to this separation, we don't require a full
TcpDiscoveryIoSession here
- // and can instead extract just the message-writing functionality.
- // At the same time, we aim to keep both reading and writing logic
encapsulated within TcpDiscoveryIoSession.
- // As a result, we need to copy some code from TcpDiscoveryIoSession
into the new class, TcpDiscoveryMessageSerializer.
- /** */
- private final TcpDiscoveryMessageSerializer clientMsgSer;
-
/** Session shared with the socket reader serving the same client
connection. */
private final TcpDiscoveryIoSession ses;
@@ -7518,8 +7506,6 @@ class ServerImpl extends TcpDiscoveryImpl {
this.ses = ses;
this.clientNodeId = clientNodeId;
- clientMsgSer = new TcpDiscoveryMessageSerializer(ctx);
-
lastMetricsUpdateMsgTimeNanos = System.nanoTime();
}
@@ -7546,24 +7532,19 @@ class ServerImpl extends TcpDiscoveryImpl {
this.metrics = metrics;
}
- /**
- * @param msg Message.
- */
+ /** @param msg Discovery Message. */
void addMessage(TcpDiscoveryAbstractMessage msg) {
- addMessage(msg, null);
+ addMessage(new ClientMessageHolder(msg));
}
- /**
- * @param msg Message.
- * @param msgBytes Optional message bytes.
- */
- void addMessage(TcpDiscoveryAbstractMessage msg, @Nullable byte[]
msgBytes) {
- T2<TcpDiscoveryAbstractMessage, byte[]> t = new T2<>(msg,
msgBytes);
+ /** @param msgHolder Holder of a Discovery Message to send to the
client. */
+ void addMessage(ClientMessageHolder msgHolder) {
+ TcpDiscoveryAbstractMessage msg = msgHolder.message();
if (msg.highPriority())
- queue.addFirst(t);
+ queue.addFirst(msgHolder);
else
- queue.add(t);
+ queue.add(msgHolder);
DebugLogger log = messageLogger(msg);
@@ -7572,10 +7553,10 @@ class ServerImpl extends TcpDiscoveryImpl {
}
/** {@inheritDoc} */
- @Override protected void
processMessage(T2<TcpDiscoveryAbstractMessage, byte[]> msgT) {
+ @Override protected void processMessage(ClientMessageHolder msgHolder)
{
boolean success = false;
- TcpDiscoveryAbstractMessage msg = msgT.get1();
+ TcpDiscoveryAbstractMessage msg = msgHolder.message();
try {
assert msg.verified() : msg;
@@ -7601,8 +7582,9 @@ class ServerImpl extends TcpDiscoveryImpl {
+ getLocalNodeId() + ", rmtNodeId=" +
clientNodeId + ", msg=" + msg + ']');
}
- writeToSocket(msgT,
spi.failureDetectionTimeoutEnabled() ? spi.clientFailureDetectionTimeout() :
- spi.getSocketTimeout());
+ long timeout = spi.failureDetectionTimeoutEnabled() ?
spi.clientFailureDetectionTimeout() : spi.getSocketTimeout();
+
+ writeMessage(msgHolder, timeout);
}
}
else {
@@ -7613,7 +7595,7 @@ class ServerImpl extends TcpDiscoveryImpl {
assert topologyInitialized(msg) : msg;
- writeToSocket(msgT, spi.getEffectiveSocketTimeout(false));
+ writeMessage(msgHolder,
spi.getEffectiveSocketTimeout(false));
}
boolean clientFailed = msg instanceof
TcpDiscoveryNodeFailedMessage &&
@@ -7643,14 +7625,16 @@ class ServerImpl extends TcpDiscoveryImpl {
}
/**
- * @param msgT Message tuple.
+ * @param msgHolder Message holder.
* @param timeout Timeout.
*/
- private void writeToSocket(T2<TcpDiscoveryAbstractMessage, byte[]>
msgT, long timeout)
- throws IgniteCheckedException, IOException {
- byte[] msgBytes = msgT.get2() == null ?
clientMsgSer.serializeMessage(msgT.get1()) : msgT.get2();
+ private void writeMessage(ClientMessageHolder msgHolder, long timeout)
throws IgniteCheckedException, IOException {
+ byte[] msgBytes = msgHolder.messageBytes();
- spi.write(ses, msgBytes, timeout);
+ if (msgBytes != null)
+ spi.write(ses, msgBytes, timeout);
+ else
+ spi.writeMessage(ses, msgHolder.message(), timeout);
}
/**
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 0251712fc95..4b81a151fd7 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
@@ -36,7 +36,6 @@ import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
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.managers.communication.DiscoveryMarshalling;
import
org.apache.ignite.internal.managers.communication.UnknownMessageException;
import org.apache.ignite.internal.util.CommonUtils;
@@ -47,6 +46,7 @@ import org.apache.ignite.marshaller.jdk.JdkMarshaller;
import org.apache.ignite.plugin.extensions.communication.Message;
import org.apache.ignite.plugin.extensions.communication.MessageFactory;
import org.apache.ignite.plugin.extensions.communication.MessageSerializer;
+import
org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryMessageSerializer;
import
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -60,14 +60,21 @@ import org.jetbrains.annotations.Nullable;
* <li>Using {@link MessageSerializer} for messages implementing the
{@link Message} interface.</li>
* <li>Deprecated: Using {@link JdkMarshaller} for messages that have not
yet been refactored.</li>
* </ul>
- * A leading byte is used to distinguish between the modes. The byte will be
removed in future.
+ * A leading byte is used to distinguish between the modes. The byte will be
removed in the future.
+ * <p>
+ * <b>NOTE:</b> This class is designed with the following access rules in
mind. Socket read operations must be performed
+ * by a single thread at a time, while socket write operations may be
performed concurrently by multiple threads. Because
+ * {@link #writeMessage(TcpDiscoveryAbstractMessage)} writes messages in
batches, all session write methods must be
+ * blocking. Currently, {@link TcpDiscoveryIoSession} may be accessed
concurrently for writing by the
+ * ServerImpl.ClientMessageWorker and ServerImpl.SocketReader threads.
+ * </p>
*/
public class TcpDiscoveryIoSession implements AutoCloseable {
/** Default size of buffer used for buffering socket in/out. */
private static final int DFLT_SOCK_BUFFER_SIZE = 8192;
- /** Size for an intermediate buffer for serializing discovery messages. */
- private static final int MSG_BUFFER_SIZE = 100;
+ /** Size of the intermediate buffer a message is deserialized through. */
+ private static final int READ_BUFFER_SIZE = 100;
/** */
private final GridKernalContext ctx;
@@ -82,23 +89,20 @@ public class TcpDiscoveryIoSession implements AutoCloseable
{
private final Socket sock;
/** */
- private final DirectMessageWriter msgWriter;
+ private final TcpDiscoveryMessageSerializer msgSer;
/** */
private final DirectMessageReader msgReader;
+ /** */
+ private final ByteBuffer readBuf;
+
/** Buffered socket output stream. */
private final OutputStream out;
/** Buffered socket input stream. */
private final CompositeInputStream in;
- /** */
- private final ByteBuffer readBuf;
-
- /** */
- private final ByteBuffer writeBuf;
-
/**
* Creates a new discovery I/O session bound to the given socket.
*
@@ -112,12 +116,11 @@ public class TcpDiscoveryIoSession implements
AutoCloseable {
this.msgFactory = ctx.messageFactory();
this.log = ctx.log(getClass());
- readBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE);
- writeBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE);
-
- msgWriter = new DirectMessageWriter(msgFactory);
+ readBuf = ByteBuffer.allocate(READ_BUFFER_SIZE);
msgReader = new DirectMessageReader(msgFactory, null);
+ msgSer = new TcpDiscoveryMessageSerializer(ctx);
+
try {
int sendBufSize = sock.getSendBufferSize() > 0 ?
sock.getSendBufferSize() : DFLT_SOCK_BUFFER_SIZE;
int rcvBufSize = sock.getReceiveBufferSize() > 0 ?
sock.getReceiveBufferSize() : DFLT_SOCK_BUFFER_SIZE;
@@ -132,13 +135,14 @@ public class TcpDiscoveryIoSession implements
AutoCloseable {
/**
* Writes a discovery message to the underlying socket output stream.
+ * Refer to the class description for the rationale behind synchronized
access.
*
* @param msg Message to send to the remote node.
* @throws IgniteCheckedException If serialization fails.
*/
- void writeMessage(TcpDiscoveryAbstractMessage msg) throws
IgniteCheckedException, IOException {
+ synchronized void writeMessage(TcpDiscoveryAbstractMessage msg) throws
IgniteCheckedException, IOException {
try {
- serializeMessage((Message)msg, out);
+ msgSer.writeTo(msg, out);
out.flush();
}
@@ -262,39 +266,14 @@ public class TcpDiscoveryIoSession implements
AutoCloseable {
return sock;
}
- /**
- * Serializes a discovery message into given output stream.
- *
- * @param m Discovery message to serialize.
- * @param out Output stream to write serialized message.
- * @throws IOException If serialization fails.
- */
- void serializeMessage(Message m, OutputStream out) throws IOException,
IgniteCheckedException {
- DiscoveryMarshalling.marshal(m, ctx, null);
-
- msgWriter.reset();
- msgWriter.setBuffer(writeBuf);
-
- boolean finished;
-
- do {
- // Should be cleared before first operation.
- writeBuf.clear();
-
- finished = MessageSerialization.writeTo(msgFactory, m, msgWriter);
-
- out.write(writeBuf.array(), 0, writeBuf.position());
- }
- while (!finished);
- }
-
/**
* Writes raw data to the underlying socket output stream.
+ * Refer to the class description for the rationale behind synchronized
access.
*
* @param data Raw data to write.
* @throws IOException If failed.
*/
- void write(byte[] data) throws IOException {
+ synchronized void write(byte[] data) throws IOException {
out.write(data);
out.flush();
@@ -302,11 +281,12 @@ public class TcpDiscoveryIoSession implements
AutoCloseable {
/**
* Writes a single byte response to the underlying socket output stream.
+ * Refer to the class description for the rationale behind synchronized
access.
*
* @param b Integer response.
* @throws IOException If failed.
*/
- void write(int b) throws IOException {
+ synchronized void write(int b) throws IOException {
out.write(b);
out.flush();
diff --git
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java
deleted file mode 100644
index ec7cdc569f0..00000000000
---
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java
+++ /dev/null
@@ -1,68 +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.spi.discovery.tcp;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.internal.GridKernalContext;
-import org.apache.ignite.plugin.extensions.communication.Message;
-import org.apache.ignite.plugin.extensions.communication.MessageSerializer;
-import
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage;
-
-/**
- * Class is responsible for serializing discovery messages using RU-ready
{@link MessageSerializer} mechanism.
- * <p>
- * It is used in a special case: when server wants to send discovery messages
to clients, it may not have a {@link TcpDiscoveryIoSession}
- * to serialize the messages.
- * This class enables server to serialize discovery messages anyway,
duplicating serialization code from {@link TcpDiscoveryIoSession}.
- */
-class TcpDiscoveryMessageSerializer extends TcpDiscoveryIoSession {
- /**
- * @param ctx Kernal context.
- */
- public TcpDiscoveryMessageSerializer(GridKernalContext ctx) {
- super(ctx, new Socket() {
- @Override public OutputStream getOutputStream() throws IOException
{
- return null;
- }
-
- @Override public InputStream getInputStream() throws IOException {
- return null;
- }
- });
- }
-
- /**
- * Serializes a discovery message into a byte array.
- *
- * @param msg Discovery message to serialize.
- * @return Serialized byte array containing the message data.
- * @throws IgniteCheckedException If serialization fails.
- * @throws IOException If serialization fails.
- */
- byte[] serializeMessage(TcpDiscoveryAbstractMessage msg) throws
IgniteCheckedException, IOException {
- try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
- serializeMessage((Message)msg, out);
-
- return out.toByteArray();
- }
- }
-}
diff --git
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java
new file mode 100644
index 00000000000..1652824dd15
--- /dev/null
+++
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java
@@ -0,0 +1,59 @@
+/*
+ * 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.spi.discovery.tcp.internal;
+
+import org.apache.ignite.IgniteCheckedException;
+import
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage;
+import org.jetbrains.annotations.Nullable;
+
+/** */
+public class ClientMessageHolder {
+ /** */
+ private final TcpDiscoveryAbstractMessage msg;
+
+ /** */
+ private volatile byte[] msgBytes;
+
+ /** */
+ public ClientMessageHolder(TcpDiscoveryAbstractMessage msg) {
+ assert msg != null;
+
+ this.msg = msg;
+ }
+
+ /** */
+ public TcpDiscoveryAbstractMessage message() {
+ return msg;
+ }
+
+ /** */
+ public byte @Nullable [] messageBytes() {
+ return msgBytes;
+ }
+
+ /** */
+ public void serialize(TcpDiscoveryMessageSerializer ser) throws
IgniteCheckedException {
+ if (msgBytes == null)
+ msgBytes = ser.serialize(msg);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return msg.toString();
+ }
+}
diff --git
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java
new file mode 100644
index 00000000000..45aa1d9b767
--- /dev/null
+++
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java
@@ -0,0 +1,96 @@
+/*
+ * 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.spi.discovery.tcp.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.direct.DirectMessageWriter;
+import org.apache.ignite.internal.managers.communication.DiscoveryMarshalling;
+import org.apache.ignite.internal.util.io.GridByteArrayOutputStream;
+import org.apache.ignite.internal.util.nio.MessageSerialization;
+import
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage;
+
+/** */
+public class TcpDiscoveryMessageSerializer {
+ /** Size of the intermediate buffer a message is serialized through. */
+ private static final int BUFFER_SIZE = 100;
+
+ /** */
+ private final GridKernalContext ctx;
+
+ /** */
+ private final DirectMessageWriter writer;
+
+ /** */
+ private final ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
+
+ /** @param ctx Kernal context. */
+ public TcpDiscoveryMessageSerializer(GridKernalContext ctx) {
+ this.ctx = ctx;
+
+ writer = new DirectMessageWriter(ctx.messageFactory());
+ }
+
+ /**
+ * Serializes a discovery message into given output stream.
+ *
+ * @param msg Discovery message to serialize.
+ * @param out Output stream to write serialized message.
+ * @throws IgniteCheckedException If serialization fails.
+ * @throws IOException If serialization fails.
+ */
+ public void writeTo(TcpDiscoveryAbstractMessage msg, OutputStream out)
throws IgniteCheckedException, IOException {
+ DiscoveryMarshalling.marshal(msg, ctx, null);
+
+ writer.reset();
+ writer.setBuffer(buf);
+
+ boolean finished;
+
+ do {
+ // Should be cleared before first operation.
+ buf.clear();
+
+ finished = MessageSerialization.writeTo(ctx.messageFactory(), msg,
writer);
+
+ out.write(buf.array(), 0, buf.position());
+ }
+ while (!finished);
+ }
+
+ /**
+ * Serializes a discovery message into a byte array.
+ *
+ * @param msg Discovery message to serialize.
+ * @return Serialized byte array containing the message data.
+ * @throws IgniteCheckedException If serialization fails.
+ */
+ public byte[] serialize(TcpDiscoveryAbstractMessage msg) throws
IgniteCheckedException {
+ try (GridByteArrayOutputStream out = new GridByteArrayOutputStream()) {
+ writeTo(msg, out);
+
+ return out.toByteArray();
+ }
+ catch (IOException e) {
+ throw new IgniteCheckedException("Failed to serialize a discovery
message: " + msg, e);
+ }
+ }
+}