You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ignite.apache.org by GitBox <gi...@apache.org> on 2021/04/22 10:02:31 UTC

[GitHub] [ignite-3] SammyVimes opened a new pull request #102: IGNITE-14088 ScaleCube transport API over Netty

SammyVimes opened a new pull request #102:
URL: https://github.com/apache/ignite-3/pull/102


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623852267



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+    }
+
+    /**
+     * Create an unresolved {@link InetSocketAddress} with "localhost" as a host.
+     *
+     * @param port Port.
+     * @return Address.
+     */
+    private InetSocketAddress address(int port) {
+        return InetSocketAddress.createUnresolved("localhost", port);

Review comment:
       is it a problem for the localhost?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630790411



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -87,6 +92,35 @@ public void test(long seed) throws Exception {
         assertEquals(msg, output);
     }
 
+    /**
+     * Tests that an {@link InboundDecoder} doesn't hang if it encounters a byte buffer with only partially written
+     * header.
+     *
+     * @throws InterruptedException If failed.
+     */
+    @Test
+    public void testPartialHeader() throws InterruptedException {
+        var registry = new MessageSerializationRegistry();
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        ByteBuf buffer = allocator.buffer();
+
+        buffer.writeByte(1);
+
+        var latch = new CountDownLatch(1);
+
+        new Thread(() -> {

Review comment:
       Can be written shorter:
   ```
   CompletableFuture
       .runAsync(() -> {
           channel.writeInbound(buffer);
   
           channel.readInbound();
       })
       .get(3, TimeUnit.SECONDS);
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630787386



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -82,12 +85,34 @@ public NettyClient(
         if (clientFuture != null)
             throw new IgniteInternalException("Attempted to start an already started NettyClient");
 
-        clientFuture = NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
-            .thenApply(ch -> {
-                clientCloseFuture = NettyUtils.toCompletableFuture(ch.closeFuture());
-                channel = ch;
-
-                return new NettySender(channel, serializationRegistry);
+        clientFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
+            .whenComplete((channel, throwable) -> {
+                synchronized (this) {
+                    if (throwable == null) {
+                        CompletableFuture<Void> closeFuture = NettyUtils.toCompletableFuture(channel.closeFuture());
+
+                        if (stopped) {
+                            // Close channel in case if client has been stopped prior to this moment.
+                            channel.close();
+
+                            // Wait for channel to close and then cancel the client future.
+                            closeFuture.whenComplete((unused, ignored) -> {
+                                clientFuture.cancel(true);

Review comment:
       Out of curiosity I read documentation of "cancel" method. Do we really need it? Especially with "true" parameter? What's the point if we're the ones that execute the task? So many questions.

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -153,35 +161,37 @@ public NettyServer(
              */
             .childOption(ChannelOption.SO_KEEPALIVE, true);
 
-        serverCloseFuture = CompletableFuture.allOf(
-            NettyUtils.toCompletableFuture(bossGroup.terminationFuture()),
-            NettyUtils.toCompletableFuture(workerGroup.terminationFuture())
-        );
-
         serverStartFuture = new CompletableFuture<>();
 
         NettyUtils.toChannelCompletableFuture(bootstrap.bind(port))
-            .thenAccept(ch -> {
-                CompletableFuture<Void> channelCloseFuture = NettyUtils.toCompletableFuture(ch.closeFuture())
-                    // Shutdown event loops on server stop.
-                    .whenComplete((v, err) -> shutdownEventLoopGroups());
+            .whenComplete((channel, err) -> {
+                synchronized (this) {

Review comment:
       Why "synchronized"?

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -90,14 +101,37 @@ public void testServerFailedToStart() throws Exception {
     public void testServerChannelClosedAbruptly() throws Exception {
         var channel = new EmbeddedServerChannel();
 
-        NettyServer server = getServer(channel, true);
+        server = getServer(channel.newSucceededFuture(), true);
 
         channel.close();
 
         assertTrue(server.getBossGroup().isShuttingDown());
         assertTrue(server.getWorkerGroup().isShuttingDown());
     }
 
+    /**
+     * Tests a scenario where a server is stopped before a server socket is successfully bound.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerStoppedBeforeStarted() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        var future = channel.newPromise();

Review comment:
       Please use explicit type

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -82,12 +85,34 @@ public NettyClient(
         if (clientFuture != null)
             throw new IgniteInternalException("Attempted to start an already started NettyClient");
 
-        clientFuture = NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
-            .thenApply(ch -> {
-                clientCloseFuture = NettyUtils.toCompletableFuture(ch.closeFuture());
-                channel = ch;
-
-                return new NettySender(channel, serializationRegistry);
+        clientFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
+            .whenComplete((channel, throwable) -> {
+                synchronized (this) {

Review comment:
       Why "synchronized"?

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyClientTest.java
##########
@@ -23,26 +23,38 @@
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 /**
  * Tests for {@link NettyClient}.
  */
 public class NettyClientTest {
+    /** Client. */
+    private NettyClient client;
+
     /** */
     private final SocketAddress address = InetSocketAddress.createUnresolved("", 0);
 
+    /** */
+    @AfterEach
+    void tearDown() {

Review comment:
       Why not public?

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -35,6 +37,15 @@
  * Tests for {@link NettyServer}.
  */
 public class NettyServerTest {
+    /** Server. */
+    private NettyServer server;
+
+    /** */
+    @AfterEach
+    final void tearDown() {

Review comment:
       Why not public?

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyClientTest.java
##########
@@ -111,6 +124,36 @@ public void testConnectionClose() throws Exception {
         assertFalse(tuple.client.failedToConnect());
     }
 
+    /**
+     * Tests a scenario where a connection is established successfully after a client has been stopped.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testStoppedBeforeStarted() throws Exception {
+        var channel = new EmbeddedChannel();
+
+        var future = channel.newPromise();
+
+        ClientAndSender tuple = createClientAndSenderFromChannelFuture(future);
+
+        tuple.client.stop();
+
+        future.setSuccess(null);
+
+        client = tuple.client;
+
+        try {
+            tuple.sender.get(3, TimeUnit.SECONDS);
+            fail();
+        }
+        catch (CancellationException ignored) {

Review comment:
       assertThrows?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623137337



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,119 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = connect.channel();
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    public static Bootstrap setupBootstrap(
+        EventLoopGroup eventLoopGroup,
+        MessageSerializationRegistry serializationRegistry,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener
+    ) {
+        Bootstrap clientBootstrap = new Bootstrap();
+
+        clientBootstrap.group(eventLoopGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */

Review comment:
       Because it's for documentation, not commenting, just looks weird




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630837783



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,254 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.SocketAddress;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link NioServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap;
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<SocketAddress, NetworkMessage> messageListener;
+
+    /** Server start future. */
+    private CompletableFuture<Void> serverStartFuture;
+
+    /** Server socket channel. */
+    private volatile ServerChannel channel;
+
+    /** Server close future. */
+    private CompletableFuture<Void> serverCloseFuture = CompletableFuture.allOf(
+        NettyUtils.toCompletableFuture(bossGroup.terminationFuture()),
+        NettyUtils.toCompletableFuture(workerGroup.terminationFuture())
+    );
+
+    /** New connections listener. */
+    private final Consumer<NettySender> newConnectionListener;
+
+    /** Flag indicating if {@link #stop()} has been called. */
+    private boolean stopped = false;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this(new ServerBootstrap(), port, newConnectionListener, messageListener, serializationRegistry);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param bootstrap Server bootstrap.
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        ServerBootstrap bootstrap,
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.bootstrap = bootstrap;
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return Future that resolves when the server is successfully started.
+     */
+    public CompletableFuture<Void> start() {
+        if (serverStartFuture != null)
+            throw new IgniteInternalException("Attempted to start an already started server");
+
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch) {
+                    ch.pipeline().addLast(
+                        /*
+                         * Decoder that uses org.apache.ignite.network.internal.MessageReader
+                         * to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        // Handles decoded NetworkMessages.
+                        new MessageHandler(messageListener),
+                        /*
+                         * Encoder that uses org.apache.ignite.network.internal.MessageWriter
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(new NettySender(ch, serializationRegistry));
+                }
+            })
+            /*
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /*
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        serverStartFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.bind(port))
+            .whenComplete((channel, err) -> {
+                synchronized (this) {
+                    // Shutdown event loops if the server has failed to start of has been stopped.
+                    if (err != null || stopped) {
+                        this.channel = null;
+
+                        shutdownEventLoopGroups();
+
+                        serverCloseFuture.whenComplete((unused, throwable) -> {
+                            Throwable stopErr = err != null ? err : new CancellationException();

Review comment:
       1. I think CancellationException is suitable here, as we cancelled server's startup.
   2. Ok




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619073987



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {
+            consumer.accept(from, message);
+        });
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+        // TODO: there might be outgoing connection already
+        channels.put(remoteAddress, new NettySender(channel, serializationRegistry));
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(InetSocketAddress address) {
+        NettyClient client = new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, (src, message) -> {
+            this.onMessage(src, message);
+        });
+
+        client.start().whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stop server and all clients.
+     */
+    public void stop() {
+        // TODO: maybe add some flag that prohibts opening new connections from this moment?
+        this.server.stop();
+        HashMap<InetSocketAddress, NettyClient> map = new HashMap<>(clients);

Review comment:
       That's actually why here is a TODO comment, for now we are only closing some connections, but we need to close all. I suppose we'll have to synchronize adding new clients and stop method :(




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623140406



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();

Review comment:
       Can this break membership protocol then?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619078048



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();

Review comment:
       true




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628201400



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessageSerializationFactory.java
##########
@@ -0,0 +1,116 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageMappingException;
+import org.apache.ignite.network.message.MessageSerializationFactory;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Serialization factory for {@link ScaleCubeMessage}.
+ * TODO: IGNITE-14649 This class should be generated.
+ */
+public class ScaleCubeMessageSerializationFactory implements MessageSerializationFactory<ScaleCubeMessage> {
+    /** {@inheritDoc} */
+    @Override public MessageDeserializer<ScaleCubeMessage> createDeserializer() {
+        return new MessageDeserializer<>() {
+            /** */
+            ScaleCubeMessage obj;

Review comment:
       Weeeeell, I don't think we should spend much time adding modifiers to code that would be generated anyway...




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628280908



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(SocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(NettySender channel) {
+        SocketAddress remoteAddress = channel.remoteAddress();
+        channels.put(remoteAddress, channel);
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(SocketAddress address) {
+        NettyClient client = new NettyClient(
+            address,
+            serializationRegistry
+        );
+
+        client.start(clientBootstrap).whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<SocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stops the server and all clients.
+     */
+    public void stop() {
+         var stream = Stream.concat(

Review comment:
       ok!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628104351



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/TestMessageSerializationFactory.java
##########
@@ -84,7 +84,7 @@ public TestMessage getMessage() {
     @Override public MessageSerializer<TestMessage> createSerializer() {
         return (message, writer) -> {
             if (!writer.isHeaderWritten()) {
-                if (!writer.writeHeader(message.directType(), (byte) 1))
+                if (!writer.writeHeader(message.directType(), (byte) 2))

Review comment:
       Can this be a constant somewhere?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(SocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(NettySender channel) {
+        SocketAddress remoteAddress = channel.remoteAddress();
+        channels.put(remoteAddress, channel);
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(SocketAddress address) {
+        NettyClient client = new NettyClient(
+            address,
+            serializationRegistry
+        );
+
+        client.start(clientBootstrap).whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<SocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stops the server and all clients.
+     */
+    public void stop() {
+         var stream = Stream.concat(

Review comment:
       Please use explicit type

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(SocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(NettySender channel) {
+        SocketAddress remoteAddress = channel.remoteAddress();
+        channels.put(remoteAddress, channel);
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(SocketAddress address) {
+        NettyClient client = new NettyClient(
+            address,
+            serializationRegistry
+        );
+
+        client.start(clientBootstrap).whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<SocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stops the server and all clients.
+     */
+    public void stop() {
+         var stream = Stream.concat(
+            clients.values().stream().map(NettyClient::stop),
+            Stream.of(server.stop())
+        );
+
+         var stopFut = CompletableFuture.allOf(stream.toArray(CompletableFuture<?>[]::new));

Review comment:
       Please use explicit type here as well

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeClusterServiceFactory.java
##########
@@ -39,7 +41,10 @@
     @Override public ClusterService createClusterService(ClusterLocalConfiguration context) {
         var topologyService = new ScaleCubeTopologyService();
         var messagingService = new ScaleCubeMessagingService(topologyService);
-        var transportFactory = new DelegatingTransportFactory(messagingService);
+        MessageSerializationRegistry registry = context.getSerializationRegistry();
+        ConnectionManager connectionManager = new ConnectionManager(context.getPort(), registry);

Review comment:
       Can you add some empty lines?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessageSerializationFactory.java
##########
@@ -0,0 +1,116 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageMappingException;
+import org.apache.ignite.network.message.MessageSerializationFactory;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Serialization factory for {@link ScaleCubeMessage}.
+ * TODO: IGNITE-14649 This class should be generated.
+ */
+public class ScaleCubeMessageSerializationFactory implements MessageSerializationFactory<ScaleCubeMessage> {
+    /** {@inheritDoc} */
+    @Override public MessageDeserializer<ScaleCubeMessage> createDeserializer() {
+        return new MessageDeserializer<>() {
+            /** */
+            ScaleCubeMessage obj;

Review comment:
       Not private. Why?

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {

Review comment:
       a) please make it final.
   b) you can use "ConnectionManager::stop" in methods implementation.

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessageSerializationFactory.java
##########
@@ -0,0 +1,116 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageMappingException;
+import org.apache.ignite.network.message.MessageSerializationFactory;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Serialization factory for {@link ScaleCubeMessage}.
+ * TODO: IGNITE-14649 This class should be generated.
+ */
+public class ScaleCubeMessageSerializationFactory implements MessageSerializationFactory<ScaleCubeMessage> {
+    /** {@inheritDoc} */
+    @Override public MessageDeserializer<ScaleCubeMessage> createDeserializer() {
+        return new MessageDeserializer<>() {
+            /** */
+            ScaleCubeMessage obj;
+
+            /** */
+            byte[] array;
+
+            /** */
+            Map<String, String> headers;
+
+            /** {@inheritDoc} */
+            @Override public boolean readMessage(MessageReader reader) throws MessageMappingException {
+                if (!reader.beforeMessageRead())
+                    return false;
+
+                switch (reader.state()) {
+                    case 0:
+                        array = reader.readByteArray("array");
+
+                        if (!reader.isLastRead())
+                            return false;
+
+                        reader.incrementState();
+
+                    //noinspection fallthrough
+                    case 1:
+                        headers = reader.readMap("headers", MessageCollectionItemType.STRING, MessageCollectionItemType.STRING, false);
+
+                        if (!reader.isLastRead())
+                            return false;
+
+                        reader.incrementState();
+
+                }
+
+                obj = new ScaleCubeMessage(array, headers);
+
+                return reader.afterMessageRead(ScaleCubeMessage.class);
+            }
+
+            /** {@inheritDoc} */
+            @Override public Class<ScaleCubeMessage> klass() {
+                return ScaleCubeMessage.class;
+            }
+
+            /** {@inheritDoc} */
+            @Override public ScaleCubeMessage getMessage() {
+                return obj;
+            }
+        };
+    }
+
+    /** {@inheritDoc} */
+    @Override public MessageSerializer<ScaleCubeMessage> createSerializer() {
+        return (message, writer) -> {
+            if (!writer.isHeaderWritten()) {
+                if (!writer.writeHeader(message.directType(), (byte) 2))

Review comment:
       Again, why do we have to hardcode this constant?

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       Yeah, looks weird. Why "final var" instead of explicit "NettySender"?

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/AllTypesMessage.java
##########
@@ -0,0 +1,154 @@
+/*
+ * 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.network.internal;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Message with all types supported by Direct Marshalling.
+ */
+public class AllTypesMessage implements NetworkMessage {
+    /** */
+    @TestFieldType(MessageCollectionItemType.BYTE)
+    byte a;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.SHORT)
+    short b;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.INT)
+    int c;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.LONG)
+    long d;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.FLOAT)
+    float e;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.DOUBLE)
+    double f;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.CHAR)
+    char g;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BOOLEAN)
+    boolean h;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BYTE_ARR)
+    byte[] i;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.SHORT_ARR)
+    short[] j;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.INT_ARR)
+    int[] k;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.LONG_ARR)
+    long[] l;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.FLOAT_ARR)
+    float[] m;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.DOUBLE_ARR)
+    double[] n;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.CHAR_ARR)
+    char[] o;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BOOLEAN_ARR)
+    boolean[] p;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.STRING)
+    String q;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BIT_SET)
+    BitSet r;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.UUID)
+    UUID s;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.IGNITE_UUID)
+    IgniteUuid t;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.MSG)
+    NetworkMessage u;
+
+    /** */
+    Object[] v;
+
+    /** */
+    Collection<?> w;
+
+    /** */
+    Map<?, ?> x;
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 5555;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o1) {
+        if (this == o1) return true;
+        if (o1 == null || getClass() != o1.getClass()) return false;
+        AllTypesMessage message = (AllTypesMessage) o1;
+        return a == message.a && b == message.b && c == message.c && d == message.d && Float.compare(message.e, e) == 0 && Double.compare(message.f, f) == 0 && g == message.g && h == message.h && Arrays.equals(i, message.i) && Arrays.equals(j, message.j) && Arrays.equals(k, message.k) && Arrays.equals(l, message.l) && Arrays.equals(m, message.m) && Arrays.equals(n, message.n) && Arrays.equals(o, message.o) && Arrays.equals(p, message.p) && Objects.equals(q, message.q) && Objects.equals(r, message.r) && Objects.equals(s, message.s) && Objects.equals(t, message.t) && Objects.equals(u, message.u) && Arrays.equals(v, message.v) && Objects.equals(w, message.w) && Objects.equals(x, message.x);

Review comment:
       Oh gods...

##########
File path: modules/network/src/test/java/org/apache/ignite/network/message/MessageSerializationRegistryTest.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.message;
+
+import org.apache.ignite.network.NetworkConfigurationException;
+import org.apache.ignite.network.internal.MessageReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * {@link MessageSerializationRegistry} tests.
+ */
+class MessageSerializationRegistryTest {
+    /**
+     * Tests that a serialization factory can be registered.
+     */
+    @Test
+    public void testRegisterFactory() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+    }
+
+    /**
+     * Tests that a serialization factory can't be registered if there is an already registered serialization factory
+     * with the same direct type.
+     */
+    @Test
+    public void testRegisterFactoryWithSameType() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+
+        assertThrows(NetworkConfigurationException.class, () -> {
+            registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+        });
+    }
+
+    /**
+     * Tests that a {@link MessageSerializer} and a {@link MessageDeserializer} can be created if a
+     * {@link MessageSerializationFactory} was registered.
+     */
+    @Test
+    public void testCreateSerializers() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+
+        assertNotNull(registry.createSerializer(Msg.TYPE));
+        assertNotNull(registry.createDeserializer(Msg.TYPE));
+    }
+
+    /**
+     * Tests that creation of a {@link MessageSerializer} or a {@link MessageDeserializer} fails if a
+     * {@link MessageSerializationFactory} was not registered.
+     */
+    @Test
+    public void testCreateSerializersIfNotRegistered() {
+        var registry = new MessageSerializationRegistry();
+
+        assertThrows(AssertionError.class, () -> registry.createSerializer(Msg.TYPE));
+        assertThrows(AssertionError.class, () -> registry.createDeserializer(Msg.TYPE));
+    }
+
+    /** */
+    static class Msg implements NetworkMessage {
+        /** */
+        static final byte TYPE = 0;
+
+        /** {@inheritDoc} */
+        @Override public short directType() {
+            return TYPE;
+        }
+    }
+
+    /** */
+    static class MsgSerializationFactory implements MessageSerializationFactory<Msg> {
+        /** {@inheritDoc} */
+        @Override public MessageDeserializer<Msg> createDeserializer() {
+            return new MessageDeserializer<Msg>() {
+                /** {@inheritDoc} */
+                @Override public boolean readMessage(MessageReader reader) throws MessageMappingException {
+                    return false;
+                }
+
+                /** {@inheritDoc} */
+                @Override public Class<Msg> klass() {
+                    return null;
+                }
+
+                /** {@inheritDoc} */
+                @Override public Msg getMessage() {
+                    return null;
+                }
+            };
+        }
+
+        /** {@inheritDoc} */
+        @Override public MessageSerializer<Msg> createSerializer() {
+            return (message, writer) -> false;
+        }
+    }
+}

Review comment:
       Please add empty line

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyClientTest.java
##########
@@ -0,0 +1,173 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyClient}.
+ */
+class NettyClientTest {

Review comment:
       Why not public?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622142491



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for a Netty {@link Channel}, that uses {@link ChunkedInput} and {@link DirectMessageWriter} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));
+    }
+
+    /**
+     * Close channel.
+     */
+    public void close() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * Chunked input for network message.
+     */
+    private static class NetworkMessageChunkedInput implements ChunkedInput<ByteBuf> {
+        /** Network message. */
+        private final NetworkMessage msg;
+
+        /** Message serializer. */
+        private final MessageSerializer<NetworkMessage> serializer;
+
+        /** Message writer. */
+        private final DirectMessageWriter writer = new DirectMessageWriter(ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        /** Whether the message was fully written. */
+        boolean finished = false;
+
+        /**
+         * Constructor.
+         *
+         * @param msg Network message.
+         * @param serializer Serializer.
+         */
+        private NetworkMessageChunkedInput(NetworkMessage msg, MessageSerializer<NetworkMessage> serializer) {
+            this.msg = msg;
+            this.serializer = serializer;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isEndOfInput() throws Exception {
+            return finished;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() throws Exception {
+
+        }
+
+        /** {@inheritDoc} */
+        @Deprecated
+        @Override public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
+            return readChunk(ctx.alloc());
+        }
+
+        /** {@inheritDoc} */
+        @Override public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception {
+            ByteBuf buffer = allocator.buffer(4096);

Review comment:
       I believe so, yes, but for now we don't even have a good ByteBuffer abstraction, so we use internal ByteBuffer of the netty's ByteBuf :)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628166337



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       True




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623910512



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+    }
+
+    /**
+     * Create an unresolved {@link InetSocketAddress} with "localhost" as a host.
+     *
+     * @param port Port.
+     * @return Address.
+     */
+    private InetSocketAddress address(int port) {
+        return InetSocketAddress.createUnresolved("localhost", port);

Review comment:
       at least it would be shorter - you won't need a custom method and will simply use a library one




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619704923



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessageSerializationFactory.java
##########
@@ -0,0 +1,110 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageMappingException;
+import org.apache.ignite.network.message.MessageSerializationFactory;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+public class ScaleCubeMessageSerializationFactory implements MessageSerializationFactory<ScaleCubeMessage> {
+    /** {@inheritDoc} */
+    @Override public MessageDeserializer<ScaleCubeMessage> createDeserializer() {
+        return new MessageDeserializer<ScaleCubeMessage>() {
+
+            ScaleCubeMessage obj;
+
+            byte[] array;
+
+            Map<String, String> headers;
+
+            /** {@inheritDoc} */
+            @Override public boolean readMessage(MessageReader reader) throws MessageMappingException {
+                if (!reader.beforeMessageRead())
+                    return false;
+
+                switch (reader.state()) {
+                    case 0:
+                        array = reader.readByteArray("array");
+
+                        if (!reader.isLastRead())
+                            return false;
+
+                        reader.incrementState();
+
+                    //noinspection fallthrough
+                    case 1:
+                        headers = reader.readMap("headers", MessageCollectionItemType.STRING, MessageCollectionItemType.STRING, false);

Review comment:
       No, why? The order is lexicographic




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619191736



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();

Review comment:
       Sorry, I don't understand why is it would be impossible to configure since you will still be able to create a new handler per client (with the custom pipeline)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622044389



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITScaleCubeNetworkMessagingTest.java
##########
@@ -179,11 +183,14 @@ private static void stopForcefully(ClusterService cluster) throws Exception {
      * Wrapper for cluster.
      */
     private static final class Cluster {
-        /** */
-        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry();
         /** */
         private static final ClusterServiceFactory NETWORK_FACTORY = new ScaleCubeClusterServiceFactory();
 
+        /** */
+        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry()

Review comment:
       Please don't make these things static even in tests

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,217 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettyChannel> channels = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    private Lock lock = new ReentrantLock();
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.setupBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettyChannel channel = channels.computeIfAbsent(address, this::connect);
+
+        return channel.channel();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+
+        lock.lock();
+
+        try {
+            NettyChannel existingChannel = channels.get(remoteAddress);
+
+            if (existingChannel != null && existingChannel.isOpen())
+                channel.close();
+            else {
+                NettyChannel serverChannel = NettyChannel.fromServer(new NettySender(channel, serializationRegistry));
+                channels.put(remoteAddress, serverChannel);
+
+                if (existingChannel != null)
+                    existingChannel.close();
+            }
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyChannel connect(InetSocketAddress address) {
+        CompletableFuture<NettySender> fut = new CompletableFuture<>();
+
+        connect0(fut, address, 3);
+
+        return NettyChannel.fromFuture(fut);
+    }
+
+    private void connect0(CompletableFuture<NettySender> fut, InetSocketAddress address, int retryCount) {
+        NettyClient client = new NettyClient(
+            address.getHostName(),
+            address.getPort(),
+            serializationRegistry
+        );
+
+        client.start(clientBootstrap).whenComplete((sender, throwable) -> {
+            lock.lock();
+
+            try {
+                if (throwable != null) {
+                    NettyChannel existingChannel = channels.get(address);
+
+                    if (existingChannel != null && existingChannel.isOpen()) {
+                        try {
+                            NettySender sender1 = existingChannel.channel().get();
+                            fut.complete(sender1);
+                        }
+                        catch (Exception e) {
+                            e.printStackTrace();

Review comment:
       Please fix this place

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for a Netty {@link Channel}, that uses {@link ChunkedInput} and {@link DirectMessageWriter} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));

Review comment:
       hm, should we flush every message? What other options do we have?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())

Review comment:
       Looks like we can update message format, do we?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null) {
+            reader = new DirectMessageReader(serializationRegistry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+            readerAttr.set(reader);
+        }
+
+        Attribute<MessageDeserializer<NetworkMessage>> messageAttr = ctx.attr(DESERIALIZER_KEY);
+
+        while (buffer.hasRemaining()) {
+            MessageDeserializer<NetworkMessage> msg = messageAttr.get();
+
+            try {
+                // Read message type.
+                if (msg == null && buffer.remaining() >= NetworkMessage.DIRECT_TYPE_SIZE) {
+                    byte b0 = buffer.get();

Review comment:
       Short type can be compressed for first 128 values, which is enough for most cases. Anyway, **short type = SomeUtil.readType(buffer)**  would look better.

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,159 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return Future that resolves when the server is successfully started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch) {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.

Review comment:
       formatting

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for a Netty {@link Channel}, that uses {@link ChunkedInput} and {@link DirectMessageWriter} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));
+    }
+
+    /**
+     * Close channel.
+     */
+    public void close() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * Chunked input for network message.
+     */
+    private static class NetworkMessageChunkedInput implements ChunkedInput<ByteBuf> {
+        /** Network message. */
+        private final NetworkMessage msg;
+
+        /** Message serializer. */
+        private final MessageSerializer<NetworkMessage> serializer;
+
+        /** Message writer. */
+        private final DirectMessageWriter writer = new DirectMessageWriter(ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        /** Whether the message was fully written. */
+        boolean finished = false;
+
+        /**
+         * Constructor.
+         *
+         * @param msg Network message.
+         * @param serializer Serializer.
+         */
+        private NetworkMessageChunkedInput(NetworkMessage msg, MessageSerializer<NetworkMessage> serializer) {
+            this.msg = msg;
+            this.serializer = serializer;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isEndOfInput() throws Exception {
+            return finished;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() throws Exception {
+
+        }
+
+        /** {@inheritDoc} */
+        @Deprecated
+        @Override public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
+            return readChunk(ctx.alloc());
+        }
+
+        /** {@inheritDoc} */
+        @Override public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception {
+            ByteBuf buffer = allocator.buffer(4096);

Review comment:
       This should be configurable

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null) {
+            reader = new DirectMessageReader(serializationRegistry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+            readerAttr.set(reader);
+        }
+
+        Attribute<MessageDeserializer<NetworkMessage>> messageAttr = ctx.attr(DESERIALIZER_KEY);
+
+        while (buffer.hasRemaining()) {
+            MessageDeserializer<NetworkMessage> msg = messageAttr.get();
+
+            try {
+                // Read message type.
+                if (msg == null && buffer.remaining() >= NetworkMessage.DIRECT_TYPE_SIZE) {
+                    byte b0 = buffer.get();
+                    byte b1 = buffer.get();
+
+                    msg = serializationRegistry.createDeserializer(makeMessageType(b0, b1));

Review comment:
       We will implement "module direct type" later, right?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,119 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = connect.channel();
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    public static Bootstrap setupBootstrap(
+        EventLoopGroup eventLoopGroup,
+        MessageSerializationRegistry serializationRegistry,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener
+    ) {
+        Bootstrap clientBootstrap = new Bootstrap();
+
+        clientBootstrap.group(eventLoopGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */

Review comment:
       Wait a minute, what's the point of making /** */ instead of /* */? IDE highlighting for the link?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessage.java
##########
@@ -0,0 +1,62 @@
+/*
+ * 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.network.scalecube.message;
+
+import io.scalecube.cluster.transport.api.Message;
+import java.util.Map;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for ScaleCube's {@link Message}.
+ * {@link Message#data} is stored in {@link #array} and {@link Message#headers} are stored in {@link #headers}.
+ */
+public class ScaleCubeMessage implements NetworkMessage {
+    /** Direct type. */
+    public static final short TYPE = 100;

Review comment:
       100?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try(ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {

Review comment:
       Should we assert this instead of checking?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,159 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return Future that resolves when the server is successfully started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch) {
+                    ch.pipeline().addLast(
+                        /**

Review comment:
       Again, why not /* ?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try(ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {
+            ScaleCubeMessage msg = (ScaleCubeMessage) networkMessage;
+
+            Map<String, String> headers = msg.getHeaders();
+
+            Object obj;
+
+            try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(msg.getArray()))) {

Review comment:
       formatting

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try(ObjectOutputStream oos = new ObjectOutputStream(stream)) {

Review comment:
       formatting

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();

Review comment:
       So we don't wait for acks here, but why?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try(ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {
+            ScaleCubeMessage msg = (ScaleCubeMessage) networkMessage;
+
+            Map<String, String> headers = msg.getHeaders();
+
+            Object obj;
+
+            try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(msg.getArray()))) {
+                obj = ois.readObject();
+            }
+            catch (Exception e) {
+                throw new IgniteInternalException(e);

Review comment:
       How will scalecube react to IgniteInternalException? I think we need something else.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630792025



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -87,6 +92,35 @@ public void test(long seed) throws Exception {
         assertEquals(msg, output);
     }
 
+    /**
+     * Tests that an {@link InboundDecoder} doesn't hang if it encounters a byte buffer with only partially written
+     * header.
+     *
+     * @throws InterruptedException If failed.
+     */
+    @Test
+    public void testPartialHeader() throws InterruptedException {
+        var registry = new MessageSerializationRegistry();
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        ByteBuf buffer = allocator.buffer();
+
+        buffer.writeByte(1);
+
+        var latch = new CountDownLatch(1);
+
+        new Thread(() -> {
+            channel.writeInbound(buffer);

Review comment:
       why do you need to run this in a separate thread?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623875658



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();

Review comment:
       Sure




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623745503



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/direct/DirectUtils.java
##########
@@ -0,0 +1,49 @@
+/*
+ * 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.network.internal.direct;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Direct marshalling utils.
+ */
+public class DirectUtils {

Review comment:
       Should it be called `DirectMarshallingUtils`?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/direct/DirectUtils.java
##########
@@ -0,0 +1,49 @@
+/*
+ * 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.network.internal.direct;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Direct marshalling utils.
+ */
+public class DirectUtils {
+    /**
+     * Reads a direct message type from a byte buffer.
+     *
+     * @param buffer Byte buffer.
+     * @return Direct message type.
+     */
+    public static short getMessageType(ByteBuffer buffer) {

Review comment:
       This method is only used in one place, why do you need a whole class for it?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {

Review comment:
       missing comment =)

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.compute(address, (addr, sender) -> {
+            if (sender == null || !sender.isOpen())
+                return null;
+
+            return sender;
+        });
+
+        if (channel == null) {

Review comment:
       I would suggest writing:
   ```
   if (channel != null) 
       return CompletableFuture.completedFuture(channel);
   ```
   
   I think it's more readable   

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.compute(address, (addr, sender) -> {
+            if (sender == null || !sender.isOpen())
+                return null;
+
+            return sender;
+        });
+
+        if (channel == null) {
+            return clients.compute(address, (addr, client) -> {

Review comment:
       can you extract the `compute` result into a variable? I think it would be more readable

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(

Review comment:
       missing comment =)

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.stream.ChunkedInput;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for a Netty {@link Channel}, that uses {@link ChunkedInput} and {@link DirectMessageWriter} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final SocketChannel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(SocketChannel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public CompletableFuture<Void> send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+
+        ChannelFuture future = channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));
+
+        CompletableFuture<Void> fut = new CompletableFuture<>();
+
+        future.addListener(sent -> {
+           if (sent.isSuccess())
+               fut.complete(null);
+           else
+               fut.completeExceptionally(sent.cause());
+        });
+
+        return fut;
+    }
+
+    /**
+     * Close channel.
+     */
+    public void close() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return Gets the remote address of the channel.
+     */
+    public InetSocketAddress remoteAddress() {
+        return this.channel.remoteAddress();
+    }
+
+    /**
+     * @return {@code true} if channel is open, {@code false} otherwise.
+     */
+    public boolean isOpen() {
+        return this.channel.isOpen();
+    }
+
+    /**
+     * Chunked input for network message.
+     */
+    private static class NetworkMessageChunkedInput implements ChunkedInput<ByteBuf> {
+        /** Network message. */
+        private final NetworkMessage msg;
+
+        /** Message serializer. */
+        private final MessageSerializer<NetworkMessage> serializer;
+
+        /** Message writer. */
+        private final DirectMessageWriter writer = new DirectMessageWriter(ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        /** Whether the message was fully written. */
+        boolean finished = false;

Review comment:
       ```suggestion
           private boolean finished = false;
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,117 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.internal.direct.DirectUtils;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.attr(READER_KEY);

Review comment:
       `attr` is deprecated =( Looks like you should use the channel attributes as you originally did, sorry...

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,242 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                return Mono.fromFuture(client.send(fromMessage(message)));
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try (ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {

Review comment:
       ```suggestion
       @Nullable
       private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /**
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        CompletableFuture<Void> serverStartFuture = new CompletableFuture<>();
+
+        ChannelFuture bindFuture = bootstrap.bind(port);
+
+        bindFuture.addListener(bind -> {
+            this.channel = (ServerSocketChannel) bindFuture.channel();
+
+            if (bind.isSuccess())
+                serverStartFuture.complete(null);
+            else {
+                Throwable cause = bind.cause();
+                serverStartFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loops on server stop.
+            channel.closeFuture().addListener(close -> {
+                workerGroup.shutdownGracefully();

Review comment:
       > (and need)
   
   I think there might be some need for that, since there can still be some event processing happening...

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.compute(address, (addr, sender) -> {
+            if (sender == null || !sender.isOpen())
+                return null;
+
+            return sender;
+        });
+
+        if (channel == null) {
+            return clients.compute(address, (addr, client) -> {
+                if (client != null && !client.failedToConnect() && !client.isDisconnected())
+                    return client;
+
+                return this.connect(addr);

Review comment:
       ```suggestion
                   return connect(addr);
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());

Review comment:
       You can write `new CopyOnWriteArrayList<>()`, passing an empty list is redundant

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.compute(address, (addr, sender) -> {

Review comment:
       Can we use a ternary operator? Or is it prohibited by the code style? For example:
   ```
           NettySender channel = channels.compute(
               address,
               (addr, sender) -> sender == null || !sender.isOpen() ? null : sender
           );
   ```        

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.

Review comment:
       ```suggestion
        * @return {@code true} if the client has failed to connect to the remote server, {@code false} otherwise.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.
+     */
+    public boolean failedToConnect() {
+        return clientFuture.isCompletedExceptionally();
+    }
+
+    /**
+     * @return {@code true} if client lost connection, {@code false} otherwise.

Review comment:
       ```suggestion
        * @return {@code true} if the client has lost the connection, {@code false} otherwise.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.

Review comment:
       ```suggestion
        * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.
+     */
+    public boolean failedToConnect() {
+        return clientFuture.isCompletedExceptionally();
+    }
+
+    /**
+     * @return {@code true} if client lost connection, {@code false} otherwise.
+     */
+    public boolean isDisconnected() {
+        return channel != null && !channel.isOpen();
+    }
+
+    /**
+     * Creates {@link Bootstrap} for clients, providing channel handlers and options.

Review comment:
       ```suggestion
        * Creates a {@link Bootstrap} for clients, providing channel handlers and options.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,242 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                return Mono.fromFuture(client.send(fromMessage(message)));
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try (ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {
+            ScaleCubeMessage msg = (ScaleCubeMessage) networkMessage;
+
+            Map<String, String> headers = msg.getHeaders();
+
+            Object obj;
+
+            try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(msg.getArray()))) {
+                obj = ois.readObject();
+            }
+            catch (Exception e) {
+                throw new IgniteInternalException(e);
+            }
+
+            return Message.withHeaders(headers).data(obj).build();
+        }
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Message> requestResponse(Address address, final Message request) {

Review comment:
       ```suggestion
       @Override public Mono<Message> requestResponse(Address address, Message request) {
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Start server.

Review comment:
       ```suggestion
        * Starts the server.
   ```

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/NettyTestRunner.java
##########
@@ -0,0 +1,88 @@
+/*
+ * 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.network.scalecube;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.TestMessage;
+import org.apache.ignite.network.TestMessageSerializationFactory;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Deprecated
+// Only for WIP purposes

Review comment:
       let's remove this class

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+    }
+
+    /**
+     * Create an unresolved {@link InetSocketAddress} with "localhost" as a host.
+     *
+     * @param port Port.
+     * @return Address.
+     */
+    private InetSocketAddress address(int port) {
+        return InetSocketAddress.createUnresolved("localhost", port);

Review comment:
       can you simply use `new InetSocketAddress(port)` instead?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.
+     */
+    public boolean failedToConnect() {
+        return clientFuture.isCompletedExceptionally();
+    }
+
+    /**
+     * @return {@code true} if client lost connection, {@code false} otherwise.
+     */
+    public boolean isDisconnected() {
+        return channel != null && !channel.isOpen();
+    }
+
+    /**
+     * Creates {@link Bootstrap} for clients, providing channel handlers and options.
+     *
+     * @param eventLoopGroup Event loop group for channel handling.
+     * @param serializationRegistry Serialization registry.
+     * @param messageListener Message listener.
+     * @return Bootstrap for clients.
+     */
+    public static Bootstrap createBootstrap(

Review comment:
       I think this method should be moved to the `ConnectionManager`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,242 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {

Review comment:
       I would suggest handling the futures first and the wrap them in `Mono`, it looks quite strange otherwise. For example:
   ```
   return Mono.fromFuture(() ->
       connectionManager.channel(addr)
           .thenCompose(client -> client.send(fromMessage(message)))
   );
   ```

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.

Review comment:
       ```suggestion
        * Tests that a message is sent successfully using the ConnectionManager.
   ```

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();

Review comment:
       No need to call `clear` JUnit classes get re-instantiated on every test

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       what's this for?

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))

Review comment:
       maybe it would be better to use a `CompletableFuture` instead of a latch, so that we can check the message using an assertion?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622154360



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessage.java
##########
@@ -0,0 +1,62 @@
+/*
+ * 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.network.scalecube.message;
+
+import io.scalecube.cluster.transport.api.Message;
+import java.util.Map;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for ScaleCube's {@link Message}.
+ * {@link Message#data} is stored in {@link #array} and {@link Message#headers} are stored in {@link #headers}.
+ */
+public class ScaleCubeMessage implements NetworkMessage {
+    /** Direct type. */
+    public static final short TYPE = 100;

Review comment:
       Why not? We will generate these things later anyway




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623080313



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for a Netty {@link Channel}, that uses {@link ChunkedInput} and {@link DirectMessageWriter} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));
+    }
+
+    /**
+     * Close channel.
+     */
+    public void close() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * Chunked input for network message.
+     */
+    private static class NetworkMessageChunkedInput implements ChunkedInput<ByteBuf> {
+        /** Network message. */
+        private final NetworkMessage msg;
+
+        /** Message serializer. */
+        private final MessageSerializer<NetworkMessage> serializer;
+
+        /** Message writer. */
+        private final DirectMessageWriter writer = new DirectMessageWriter(ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        /** Whether the message was fully written. */
+        boolean finished = false;
+
+        /**
+         * Constructor.
+         *
+         * @param msg Network message.
+         * @param serializer Serializer.
+         */
+        private NetworkMessageChunkedInput(NetworkMessage msg, MessageSerializer<NetworkMessage> serializer) {
+            this.msg = msg;
+            this.serializer = serializer;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isEndOfInput() throws Exception {
+            return finished;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() throws Exception {
+
+        }
+
+        /** {@inheritDoc} */
+        @Deprecated
+        @Override public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
+            return readChunk(ctx.alloc());
+        }
+
+        /** {@inheritDoc} */
+        @Override public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception {
+            ByteBuf buffer = allocator.buffer(4096);

Review comment:
       I think it would still be useful to address this comment now so that we wouldn't forget to make it configurable during future refactoring.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619056025



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();

Review comment:
       Well, I don't really like it as it throws unchecked exception, but ok




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623913234



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.
+     */
+    public boolean failedToConnect() {
+        return clientFuture.isCompletedExceptionally();
+    }
+
+    /**
+     * @return {@code true} if client lost connection, {@code false} otherwise.
+     */
+    public boolean isDisconnected() {
+        return channel != null && !channel.isOpen();
+    }
+
+    /**
+     * Creates {@link Bootstrap} for clients, providing channel handlers and options.
+     *
+     * @param eventLoopGroup Event loop group for channel handling.
+     * @param serializationRegistry Serialization registry.
+     * @param messageListener Message listener.
+     * @return Bootstrap for clients.
+     */
+    public static Bootstrap createBootstrap(

Review comment:
       I'm not sure if this method can be considered as client implementation details, since the Bootstrap is created in the ConnectionManager and then passed back into the client instance. So the actual client does not care about the particular Bootstrap implementation, since it is injected through the constuctor




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628200895



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/AllTypesMessage.java
##########
@@ -0,0 +1,154 @@
+/*
+ * 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.network.internal;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Message with all types supported by Direct Marshalling.
+ */
+public class AllTypesMessage implements NetworkMessage {
+    /** */
+    @TestFieldType(MessageCollectionItemType.BYTE)
+    byte a;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.SHORT)
+    short b;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.INT)
+    int c;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.LONG)
+    long d;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.FLOAT)
+    float e;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.DOUBLE)
+    double f;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.CHAR)
+    char g;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BOOLEAN)
+    boolean h;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BYTE_ARR)
+    byte[] i;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.SHORT_ARR)
+    short[] j;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.INT_ARR)
+    int[] k;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.LONG_ARR)
+    long[] l;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.FLOAT_ARR)
+    float[] m;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.DOUBLE_ARR)
+    double[] n;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.CHAR_ARR)
+    char[] o;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BOOLEAN_ARR)
+    boolean[] p;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.STRING)
+    String q;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.BIT_SET)
+    BitSet r;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.UUID)
+    UUID s;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.IGNITE_UUID)
+    IgniteUuid t;
+
+    /** */
+    @TestFieldType(MessageCollectionItemType.MSG)
+    NetworkMessage u;
+
+    /** */
+    Object[] v;
+
+    /** */
+    Collection<?> w;
+
+    /** */
+    Map<?, ?> x;
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 5555;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o1) {
+        if (this == o1) return true;
+        if (o1 == null || getClass() != o1.getClass()) return false;
+        AllTypesMessage message = (AllTypesMessage) o1;
+        return a == message.a && b == message.b && c == message.c && d == message.d && Float.compare(message.e, e) == 0 && Double.compare(message.f, f) == 0 && g == message.g && h == message.h && Arrays.equals(i, message.i) && Arrays.equals(j, message.j) && Arrays.equals(k, message.k) && Arrays.equals(l, message.l) && Arrays.equals(m, message.m) && Arrays.equals(n, message.n) && Arrays.equals(o, message.o) && Arrays.equals(p, message.p) && Objects.equals(q, message.q) && Objects.equals(r, message.r) && Objects.equals(s, message.s) && Objects.equals(t, message.t) && Objects.equals(u, message.u) && Arrays.equals(v, message.v) && Objects.equals(w, message.w) && Objects.equals(x, message.x);

Review comment:
       It's auto-generated!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r618382459



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.

Review comment:
       ```suggestion
    * Class that manages both incoming and outgoing connections.
   ```

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITScaleCubeNetworkMessagingTest.java
##########
@@ -179,11 +190,17 @@ private static void stopForcefully(ClusterService cluster) throws Exception {
      * Wrapper for cluster.
      */
     private static final class Cluster {
-        /** */
-        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry();
         /** */
         private static final ClusterServiceFactory NETWORK_FACTORY = new ScaleCubeClusterServiceFactory();
 
+        /** */
+        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry();
+
+        static {
+            SERIALIZATION_REGISTRY.registerFactory(ScaleCubeMessage.TYPE, new ScaleCubeMessageSerializationFactory());

Review comment:
       `registerFactory` returns `MessageSerializationRegistry`, so you can get rid of the static block

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITScaleCubeNetworkMessagingTest.java
##########
@@ -48,8 +52,15 @@
 
 /** */
 class ITScaleCubeNetworkMessagingTest {
+    /** */
     private Cluster testCluster;
 
+    /** */
+    private final Map<String, NetworkMessage> messageStorage = new ConcurrentHashMap<>();
+
+    /** */
+    private final List<ClusterService> startedMembers = new ArrayList<>();

Review comment:
       Looks like an incorrect git merge

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();

Review comment:
       should we use `join` instead?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */

Review comment:
       sorry, I don't understand what you mean here

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {

Review comment:
       isn't this a race? Should we use a lock?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(String host, int port, MessageSerializationRegistry serializationRegistry, BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+        this.messageListener = listener;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start() {
+        bootstrap.group(workerGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */
+            .option(ChannelOption.SO_KEEPALIVE, true)
+            .handler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {

Review comment:
       this exception is not thrown and can be removed

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {
+            consumer.accept(from, message);
+        });
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+        // TODO: there might be outgoing connection already
+        channels.put(remoteAddress, new NettySender(channel, serializationRegistry));
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(InetSocketAddress address) {
+        NettyClient client = new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, (src, message) -> {
+            this.onMessage(src, message);
+        });
+
+        client.start().whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stop server and all clients.
+     */
+    public void stop() {
+        // TODO: maybe add some flag that prohibts opening new connections from this moment?

Review comment:
       I think that this problem will be solved when you will have a single Bootstrap and worker group. Then you won't be able to accept connections if you close them.

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();

Review comment:
       Are you sure we should create a single `NioEventLoopGroup` per client channel? This is effectively a thread pool, so I would expect it to be shared. See https://github.com/netty/netty/issues/5038 for example (looks like the same applies to the `Bootstrap` class)

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {
+            consumer.accept(from, message);
+        });
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+        // TODO: there might be outgoing connection already
+        channels.put(remoteAddress, new NettySender(channel, serializationRegistry));
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(InetSocketAddress address) {
+        NettyClient client = new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, (src, message) -> {

Review comment:
       `new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, this::onMessage);`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;

Review comment:
       all of this class' members can be `final`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {
+            consumer.accept(from, message);
+        });
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+        // TODO: there might be outgoing connection already
+        channels.put(remoteAddress, new NettySender(channel, serializationRegistry));
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(InetSocketAddress address) {
+        NettyClient client = new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, (src, message) -> {
+            this.onMessage(src, message);
+        });
+
+        client.start().whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stop server and all clients.
+     */
+    public void stop() {
+        // TODO: maybe add some flag that prohibts opening new connections from this moment?
+        this.server.stop();
+        HashMap<InetSocketAddress, NettyClient> map = new HashMap<>(clients);

Review comment:
       why do you need the copy?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {

Review comment:
       `listeners.forEach(consumer -> consumer.accept(from, message));`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());

Review comment:
       I think `CopyOnWriteArrayList` is better here

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {
+            consumer.accept(from, message);
+        });
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+        // TODO: there might be outgoing connection already
+        channels.put(remoteAddress, new NettySender(channel, serializationRegistry));
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(InetSocketAddress address) {
+        NettyClient client = new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, (src, message) -> {
+            this.onMessage(src, message);
+        });
+
+        client.start().whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stop server and all clients.
+     */
+    public void stop() {
+        // TODO: maybe add some flag that prohibts opening new connections from this moment?
+        this.server.stop();
+        HashMap<InetSocketAddress, NettyClient> map = new HashMap<>(clients);
+        map.values().forEach(client -> {

Review comment:
       `map.values().forEach(NettyClient::stop);`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(String host, int port, MessageSerializationRegistry serializationRegistry, BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+        this.messageListener = listener;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start() {
+        bootstrap.group(workerGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */
+            .option(ChannelOption.SO_KEEPALIVE, true)
+            .handler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        new InboundDecoder(serializationRegistry),
+                        new MessageHandler(messageListener),
+                        new ChunkedWriteHandler()
+                    );
+                }
+        });
+
+        ChannelFuture connectFuture = bootstrap.connect(host, port);
+
+        connectFuture.addListener(connect -> {

Review comment:
       this can be written a little bit shorter:
   ```
   bootstrap.connect(host, port)
       .addListener((ChannelFutureListener)future -> {
           this.channel = future.channel();
           if (future.isSuccess())
               clientFuture.complete(new NettySender(channel, serializationRegistry));
           else
               clientFuture.completeExceptionally(future.cause());
   
           // Shutdown event loop group when channel is closed.
           channel.closeFuture().addListener(close -> {
               workerGroup.shutdownGracefully();
           });
       });
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {
+            consumer.accept(from, message);
+        });
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+        // TODO: there might be outgoing connection already
+        channels.put(remoteAddress, new NettySender(channel, serializationRegistry));
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(InetSocketAddress address) {
+        NettyClient client = new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, (src, message) -> {
+            this.onMessage(src, message);
+        });
+
+        client.start().whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);

Review comment:
       why don't you clean the `channels` map as well?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619081248



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();

Review comment:
       On the other hand, it will be impossible to configure a listener per client, because listener is set in channel pipeline




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623138618



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for a Netty {@link Channel}, that uses {@link ChunkedInput} and {@link DirectMessageWriter} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));

Review comment:
       Ok, very interesting. I wonder how we will exhaust buffers then :)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r621072055



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.

Review comment:
       Absolutely correct, even a comma after "connections" and before "both" is entirely optional (and I hate it)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630833407



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -82,12 +85,34 @@ public NettyClient(
         if (clientFuture != null)
             throw new IgniteInternalException("Attempted to start an already started NettyClient");
 
-        clientFuture = NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
-            .thenApply(ch -> {
-                clientCloseFuture = NettyUtils.toCompletableFuture(ch.closeFuture());
-                channel = ch;
-
-                return new NettySender(channel, serializationRegistry);
+        clientFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
+            .whenComplete((channel, throwable) -> {
+                synchronized (this) {
+                    if (throwable == null) {
+                        CompletableFuture<Void> closeFuture = NettyUtils.toCompletableFuture(channel.closeFuture());
+
+                        if (stopped) {
+                            // Close channel in case if client has been stopped prior to this moment.
+                            channel.close();
+
+                            // Wait for channel to close and then cancel the client future.
+                            closeFuture.whenComplete((unused, ignored) -> {
+                                clientFuture.cancel(true);

Review comment:
       We can pass any argument, why not `true`?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628198590



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -0,0 +1,99 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.apache.ignite.network.internal.AllTypesMessage;
+import org.apache.ignite.network.internal.AllTypesMessageGenerator;
+import org.apache.ignite.network.internal.AllTypesMessageSerializationFactory;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Tests for {@link InboundDecoder}.
+ */
+public class InboundDecoderTest {
+    /**
+     * Tests that an {@link InboundDecoder} can successfully read a message with all types supported
+     * by direct marshalling.
+     *
+     * @param seed Random seed.
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @MethodSource("messageGenerationSeed")
+    public void test(long seed) throws Exception {
+        var registry = new MessageSerializationRegistry();
+
+        AllTypesMessage msg = AllTypesMessageGenerator.generate(seed, true);
+
+        registry.registerFactory(msg.directType(), new AllTypesMessageSerializationFactory());
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        var writer = new DirectMessageWriter(registry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        MessageSerializer<NetworkMessage> serializer = registry.createSerializer(msg.directType());
+
+        UnpooledByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
+
+        ByteBuffer buf = ByteBuffer.allocate(10_000);
+
+        AllTypesMessage output;
+
+        do {
+            buf.clear();
+
+            writer.setBuffer(buf);

Review comment:
       True




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628281404



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/DelegatingTransportFactory.java
##########
@@ -35,50 +34,66 @@
     /** */
     private final ScaleCubeMessagingService messagingService;
 
+    /** Delegate transport factory. */
+    private final TransportFactory factory;
+
     /**
      * @param messagingService Messaging service.
+     * @param factory Delegate transport factory.
      */
-    DelegatingTransportFactory(ScaleCubeMessagingService messagingService) {
+    DelegatingTransportFactory(ScaleCubeMessagingService messagingService, TransportFactory factory) {
         this.messagingService = messagingService;
+        this.factory = factory;
     }
 
     /** {@inheritDoc} */
     @Override public Transport createTransport(TransportConfig config) {
-        var delegateFactory = TransportFactory.INSTANCE == null ? new TcpTransportFactory() : TransportFactory.INSTANCE;
-
-        Transport delegate = delegateFactory.createTransport(config);
+        Transport delegate = factory.createTransport(config);

Review comment:
       I don't see any difference, but it's ok, since this code will be removed anyway




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r620098413



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOGGER.info("[{}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOGGER.info("[{}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return Mono.defer(() -> {
+            stop.onComplete();
+            return onStop;
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        return Mono.defer(() -> {

Review comment:
       Returnin `null` in either `map` or `flatMap` counts like an error signal




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623856714



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/direct/DirectUtils.java
##########
@@ -0,0 +1,49 @@
+/*
+ * 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.network.internal.direct;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Direct marshalling utils.
+ */
+public class DirectUtils {
+    /**
+     * Reads a direct message type from a byte buffer.
+     *
+     * @param buffer Byte buffer.
+     * @return Direct message type.
+     */
+    public static short getMessageType(ByteBuffer buffer) {

Review comment:
       I think in future there'll be more methods

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/direct/DirectUtils.java
##########
@@ -0,0 +1,49 @@
+/*
+ * 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.network.internal.direct;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Direct marshalling utils.
+ */
+public class DirectUtils {

Review comment:
       Ok, why not




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628204254



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -0,0 +1,99 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.apache.ignite.network.internal.AllTypesMessage;
+import org.apache.ignite.network.internal.AllTypesMessageGenerator;
+import org.apache.ignite.network.internal.AllTypesMessageSerializationFactory;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Tests for {@link InboundDecoder}.
+ */
+public class InboundDecoderTest {
+    /**
+     * Tests that an {@link InboundDecoder} can successfully read a message with all types supported
+     * by direct marshalling.
+     *
+     * @param seed Random seed.
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @MethodSource("messageGenerationSeed")
+    public void test(long seed) throws Exception {
+        var registry = new MessageSerializationRegistry();
+
+        AllTypesMessage msg = AllTypesMessageGenerator.generate(seed, true);
+
+        registry.registerFactory(msg.directType(), new AllTypesMessageSerializationFactory());
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        var writer = new DirectMessageWriter(registry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        MessageSerializer<NetworkMessage> serializer = registry.createSerializer(msg.directType());
+
+        UnpooledByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
+
+        ByteBuffer buf = ByteBuffer.allocate(10_000);
+
+        AllTypesMessage output;
+
+        do {
+            buf.clear();
+
+            writer.setBuffer(buf);

Review comment:
       Whoops, I forgot, we actually do need to do that: the buffer is cleared after every write




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630975719



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -87,6 +92,35 @@ public void test(long seed) throws Exception {
         assertEquals(msg, output);
     }
 
+    /**
+     * Tests that an {@link InboundDecoder} doesn't hang if it encounters a byte buffer with only partially written
+     * header.
+     *
+     * @throws InterruptedException If failed.
+     */
+    @Test
+    public void testPartialHeader() throws InterruptedException {
+        var registry = new MessageSerializationRegistry();
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        ByteBuf buffer = allocator.buffer();
+
+        buffer.writeByte(1);
+
+        var latch = new CountDownLatch(1);
+
+        new Thread(() -> {
+            channel.writeInbound(buffer);

Review comment:
       ok, then it can be written a little bit shorter:
   ```
   CompletableFuture
       .runAsync(() -> {
           channel.writeInbound(buffer);
   
           channel.readInbound();
       })
       .get(3, TimeUnit.SECONDS);
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623136204



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITScaleCubeNetworkMessagingTest.java
##########
@@ -179,11 +183,14 @@ private static void stopForcefully(ClusterService cluster) throws Exception {
      * Wrapper for cluster.
      */
     private static final class Cluster {
-        /** */
-        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry();
         /** */
         private static final ClusterServiceFactory NETWORK_FACTORY = new ScaleCubeClusterServiceFactory();
 
+        /** */
+        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry()

Review comment:
       What's the hard of making them non-static? This collection won't be garbage collected, for example, so I consider it a bad practice.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622138661



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null) {
+            reader = new DirectMessageReader(serializationRegistry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+            readerAttr.set(reader);
+        }
+
+        Attribute<MessageDeserializer<NetworkMessage>> messageAttr = ctx.attr(DESERIALIZER_KEY);
+
+        while (buffer.hasRemaining()) {
+            MessageDeserializer<NetworkMessage> msg = messageAttr.get();
+
+            try {
+                // Read message type.
+                if (msg == null && buffer.remaining() >= NetworkMessage.DIRECT_TYPE_SIZE) {
+                    byte b0 = buffer.get();
+                    byte b1 = buffer.get();
+
+                    msg = serializationRegistry.createDeserializer(makeMessageType(b0, b1));

Review comment:
       Right, with message generation




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622152791



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();

Review comment:
       We don't actually have any acks, so there is no way to tell if message was received by the remote server. We can, though, implement default message handlers, that sends acks in response to any message




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623750156



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessage.java
##########
@@ -0,0 +1,62 @@
+/*
+ * 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.network.scalecube.message;
+
+import io.scalecube.cluster.transport.api.Message;
+import java.util.Map;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for ScaleCube's {@link Message}.
+ * {@link Message#data} is stored in {@link #array} and {@link Message#headers} are stored in {@link #headers}.
+ */
+public class ScaleCubeMessage implements NetworkMessage {
+    /** Direct type. */
+    public static final short TYPE = 100;

Review comment:
       Ok, why not




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630834208



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -35,6 +37,15 @@
  * Tests for {@link NettyServer}.
  */
 public class NettyServerTest {
+    /** Server. */
+    private NettyServer server;
+
+    /** */
+    @AfterEach
+    final void tearDown() {

Review comment:
       Why should it be public? No one calls it anyway (except for JUnit)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622154090



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try(ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {

Review comment:
       No, I don't think so. We receive every single message here




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628188168



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -0,0 +1,123 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyServer}.
+ */
+public class NettyServerTest {
+    /**
+     * Tests a successfull server start scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSuccessfullServerStart() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertTrue(server.isRunning());
+    }
+
+    /**
+     * Tests a graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerGracefulShutdown() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        server.stop().join();
+
+        assertTrue(server.getBossGroup().isTerminated());
+        assertTrue(server.getWorkerGroup().isTerminated());
+    }
+
+    /**
+     * Tests a non-graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerChannelClosedAbruptly() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        channel.close();
+
+        assertTrue(server.getBossGroup().isShuttingDown());
+        assertTrue(server.getWorkerGroup().isShuttingDown());
+    }
+
+    /**
+     * Tests that a {@link NettyServer#start} method can be called only once.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testStartTwice() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertThrows(IgniteInternalException.class, () -> {
+            server.start();
+        });
+    }
+
+    /**
+     * Creates a server from a backing {@link ChannelFuture}.
+     *
+     * @param future Channel future.
+     * @return NettyServer.
+     * @throws Exception If failed.
+     */
+    private NettyServer getServer(ChannelFuture future) throws Exception {

Review comment:
       That's actually a good point, I forgot to test the case where the server failed to start!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r620098413



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOGGER.info("[{}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOGGER.info("[{}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return Mono.defer(() -> {
+            stop.onComplete();
+            return onStop;
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        return Mono.defer(() -> {

Review comment:
       Returning `null` in either `map` or `flatMap` counts like an error signal




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619183314



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();

Review comment:
       if you really need to catch and wrap it in `IgniteInternalException` then there's no difference. But I thought that you can simply let it be thrown as-is...




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623856495



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       okay, that's highly subjective, so you can leave the current code, if you like it more




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619702462



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));

Review comment:
       I think there isn't, but no harm in changing it, sure




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630833607



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -87,6 +92,35 @@ public void test(long seed) throws Exception {
         assertEquals(msg, output);
     }
 
+    /**
+     * Tests that an {@link InboundDecoder} doesn't hang if it encounters a byte buffer with only partially written
+     * header.
+     *
+     * @throws InterruptedException If failed.
+     */
+    @Test
+    public void testPartialHeader() throws InterruptedException {
+        var registry = new MessageSerializationRegistry();
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        ByteBuf buffer = allocator.buffer();
+
+        buffer.writeByte(1);
+
+        var latch = new CountDownLatch(1);
+
+        new Thread(() -> {
+            channel.writeInbound(buffer);

Review comment:
       To check if it hangs




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619043258



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(String host, int port, MessageSerializationRegistry serializationRegistry, BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+        this.messageListener = listener;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start() {
+        bootstrap.group(workerGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */
+            .option(ChannelOption.SO_KEEPALIVE, true)
+            .handler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        new InboundDecoder(serializationRegistry),
+                        new MessageHandler(messageListener),
+                        new ChunkedWriteHandler()
+                    );
+                }
+        });
+
+        ChannelFuture connectFuture = bootstrap.connect(host, port);
+
+        connectFuture.addListener(connect -> {
+            this.channel = connectFuture.channel();
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else {
+                Throwable cause = connect.cause();
+                clientFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loop group when channel is closed.
+            channel.closeFuture().addListener(close -> {

Review comment:
       `channel.closeFuture().addListener(close -> workerGroup.shutdownGracefully());`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(String host, int port, MessageSerializationRegistry serializationRegistry, BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+        this.messageListener = listener;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start() {
+        bootstrap.group(workerGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */
+            .option(ChannelOption.SO_KEEPALIVE, true)
+            .handler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        new InboundDecoder(serializationRegistry),
+                        new MessageHandler(messageListener),
+                        new ChunkedWriteHandler()
+                    );
+                }
+        });
+
+        ChannelFuture connectFuture = bootstrap.connect(host, port);
+
+        connectFuture.addListener(connect -> {
+            this.channel = connectFuture.channel();
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else {
+                Throwable cause = connect.cause();
+                clientFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loop group when channel is closed.
+            channel.closeFuture().addListener(close -> {
+               workerGroup.shutdownGracefully();
+            });
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();

Review comment:
       `await` will not rethrow any exceptions, should we use `sync` instead?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));
+
+        Attribute<MessageDeserializer<NetworkMessage>> messageAttr = ctx.channel().attr(DESERIALIZER_KEY);
+
+        while (buffer.hasRemaining()) {
+            MessageDeserializer<NetworkMessage> msg = messageAttr.get();
+
+            try {
+                // Read message type.
+                if (msg == null && buffer.remaining() >= NetworkMessage.DIRECT_TYPE_SIZE) {
+                    byte b0 = buffer.get();
+                    byte b1 = buffer.get();
+
+                    msg = serializationRegistry.createDeserializer(makeMessageType(b0, b1));
+                }
+
+                boolean finished = false;
+
+                // Read message if buffer has remaining data.
+                if (msg != null && buffer.hasRemaining()) {
+                    reader.setCurrentReadClass(msg.klass());
+                    reader.setBuffer(buffer);
+
+                    finished = msg.readMessage(reader);
+                }
+
+                // Set read position to Netty's ByteBuf.
+                in.readerIndex(buffer.position());
+
+                if (finished) {
+                    reader.reset();
+                    messageAttr.set(null);
+
+                    out.add(msg.getMessage());
+                }
+                else
+                    messageAttr.set(msg);
+            }
+            catch (Throwable e) {
+                System.err.println("Failed to read message [msg=" + msg +

Review comment:
       I think using a log would be more appropriate here

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set

Review comment:
       Nice comments!

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for channel, that uses {@link ChunkedInput} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));
+    }
+
+    /**
+     * Chunked input for network message.
+     */
+    private static class NetworkMessageChunkedInput implements ChunkedInput<ByteBuf> {
+        /** Network message. */
+        private final NetworkMessage msg;
+
+        /** Message serializer. */
+        private final MessageSerializer<NetworkMessage> serializer;
+
+        /** Message writer. */
+        private final DirectMessageWriter writer = new DirectMessageWriter((byte) 1);
+
+        /** Whether the message was fully written. */
+        boolean finished = false;
+
+        /**
+         * Constructor.
+         *
+         * @param msg Network message.
+         * @param serializer Serializer.
+         */
+        private NetworkMessageChunkedInput(NetworkMessage msg, MessageSerializer<NetworkMessage> serializer) {
+            this.msg = msg;
+            this.serializer = serializer;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isEndOfInput() throws Exception {
+            return finished;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() throws Exception {
+
+        }
+
+
+        /** {@inheritDoc} */
+        @Deprecated
+        @Override public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
+            return readChunk(ctx.alloc());
+        }
+
+        /** {@inheritDoc} */
+        @Override public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception {
+            ByteBuf buffer = allocator.buffer(4096);
+            final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096);

Review comment:
       Are you sure this should be a direct byte buffer? I can see that it is only used as an intermediate storage between `writer` and `buffer`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOGGER.info("[{}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOGGER.info("[{}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return Mono.defer(() -> {
+            stop.onComplete();
+            return onStop;
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        return Mono.defer(() -> {
+            return Mono.fromFuture(connectionManager.channel(InetSocketAddress.createUnresolved(address.host(), address.port())));
+        }).flatMap(client -> {
+            client.send(fromMessage(message));
+            return Mono.empty().then();
+        });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        ObjectOutputStream o;
+
+        try {

Review comment:
       Same as below about closing the streams

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /**
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        CompletableFuture<Void> serverStartFuture = new CompletableFuture<>();
+
+        ChannelFuture bindFuture = bootstrap.bind(port);
+
+        bindFuture.addListener(bind -> {

Review comment:
       same stuff as in `NettyClient`:
   ```
   bootstrap.bind(port)
       .addListener((ChannelFutureListener) bindFuture -> {
           this.channel = (ServerSocketChannel) bindFuture.channel();
           ....
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));

Review comment:
       I'm personally not very fond of such style of assignments. Or is it allowed be the code style?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /**
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        CompletableFuture<Void> serverStartFuture = new CompletableFuture<>();
+
+        ChannelFuture bindFuture = bootstrap.bind(port);
+
+        bindFuture.addListener(bind -> {
+            this.channel = (ServerSocketChannel) bindFuture.channel();
+
+            if (bind.isSuccess())
+                serverStartFuture.complete(null);
+            else {
+                Throwable cause = bind.cause();
+                serverStartFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loops on server stop.
+            channel.closeFuture().addListener(close -> {
+                workerGroup.shutdownGracefully();
+                bossGroup.shutdownGracefully();
+            });
+        });
+
+        return serverStartFuture;
+    }
+
+    /**
+     * @return Gets server address.

Review comment:
       ```suggestion
        * @return Gets the server address.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /**
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        CompletableFuture<Void> serverStartFuture = new CompletableFuture<>();
+
+        ChannelFuture bindFuture = bootstrap.bind(port);
+
+        bindFuture.addListener(bind -> {
+            this.channel = (ServerSocketChannel) bindFuture.channel();
+
+            if (bind.isSuccess())
+                serverStartFuture.complete(null);
+            else {
+                Throwable cause = bind.cause();
+                serverStartFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loops on server stop.
+            channel.closeFuture().addListener(close -> {
+                workerGroup.shutdownGracefully();

Review comment:
       `shutdownGracefully` returns a future, should we wait for it to complete?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));

Review comment:
       I think it would be nice to extract `(byte) 1` into a constant

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for channel, that uses {@link ChunkedInput} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));
+    }
+
+    /**
+     * Chunked input for network message.
+     */
+    private static class NetworkMessageChunkedInput implements ChunkedInput<ByteBuf> {
+        /** Network message. */
+        private final NetworkMessage msg;
+
+        /** Message serializer. */
+        private final MessageSerializer<NetworkMessage> serializer;
+
+        /** Message writer. */
+        private final DirectMessageWriter writer = new DirectMessageWriter((byte) 1);
+
+        /** Whether the message was fully written. */
+        boolean finished = false;
+
+        /**
+         * Constructor.
+         *
+         * @param msg Network message.
+         * @param serializer Serializer.
+         */
+        private NetworkMessageChunkedInput(NetworkMessage msg, MessageSerializer<NetworkMessage> serializer) {
+            this.msg = msg;
+            this.serializer = serializer;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isEndOfInput() throws Exception {
+            return finished;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void close() throws Exception {
+
+        }
+
+
+        /** {@inheritDoc} */
+        @Deprecated
+        @Override public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception {
+            return readChunk(ctx.alloc());
+        }
+
+        /** {@inheritDoc} */
+        @Override public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception {
+            ByteBuf buffer = allocator.buffer(4096);
+            final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096);

Review comment:
       ```suggestion
               ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096);
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.

Review comment:
       ```suggestion
        * Starts the server.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for channel, that uses {@link ChunkedInput} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));
+    }
+
+    /**
+     * Chunked input for network message.
+     */
+    private static class NetworkMessageChunkedInput implements ChunkedInput<ByteBuf> {
+        /** Network message. */
+        private final NetworkMessage msg;
+
+        /** Message serializer. */
+        private final MessageSerializer<NetworkMessage> serializer;
+
+        /** Message writer. */
+        private final DirectMessageWriter writer = new DirectMessageWriter((byte) 1);

Review comment:
       I think `(byte) 1` should be extracted into a constant

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.

Review comment:
       ```suggestion
        * @return Future that gets resolved when the server is successfully started.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);

Review comment:
       BTW, `ctx` itself is already an `AttributeMap`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessage.java
##########
@@ -0,0 +1,47 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.message.NetworkMessage;
+
+public class ScaleCubeMessage implements NetworkMessage {

Review comment:
       There are no javadocs in this class

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessageSerializationFactory.java
##########
@@ -0,0 +1,110 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageMappingException;
+import org.apache.ignite.network.message.MessageSerializationFactory;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+public class ScaleCubeMessageSerializationFactory implements MessageSerializationFactory<ScaleCubeMessage> {
+    /** {@inheritDoc} */
+    @Override public MessageDeserializer<ScaleCubeMessage> createDeserializer() {
+        return new MessageDeserializer<ScaleCubeMessage>() {

Review comment:
       ```suggestion
           return new MessageDeserializer<>() {
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /**
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        CompletableFuture<Void> serverStartFuture = new CompletableFuture<>();
+
+        ChannelFuture bindFuture = bootstrap.bind(port);
+
+        bindFuture.addListener(bind -> {
+            this.channel = (ServerSocketChannel) bindFuture.channel();
+
+            if (bind.isSuccess())
+                serverStartFuture.complete(null);
+            else {
+                Throwable cause = bind.cause();
+                serverStartFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loops on server stop.
+            channel.closeFuture().addListener(close -> {
+                workerGroup.shutdownGracefully();
+                bossGroup.shutdownGracefully();
+            });
+        });
+
+        return serverStartFuture;
+    }
+
+    /**
+     * @return Gets server address.
+     */
+    public InetSocketAddress address() {
+        return channel.localAddress();
+    }
+
+    /**
+     * Stop server.

Review comment:
       ```suggestion
        * Stops the server.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())

Review comment:
       Looks like this check is not needed, localhost patterns are handled by the `Address.from`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessageSerializationFactory.java
##########
@@ -0,0 +1,110 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageMappingException;
+import org.apache.ignite.network.message.MessageSerializationFactory;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+public class ScaleCubeMessageSerializationFactory implements MessageSerializationFactory<ScaleCubeMessage> {
+    /** {@inheritDoc} */
+    @Override public MessageDeserializer<ScaleCubeMessage> createDeserializer() {
+        return new MessageDeserializer<ScaleCubeMessage>() {
+
+            ScaleCubeMessage obj;
+
+            byte[] array;
+
+            Map<String, String> headers;
+
+            /** {@inheritDoc} */
+            @Override public boolean readMessage(MessageReader reader) throws MessageMappingException {
+                if (!reader.beforeMessageRead())
+                    return false;
+
+                switch (reader.state()) {
+                    case 0:
+                        array = reader.readByteArray("array");
+
+                        if (!reader.isLastRead())
+                            return false;
+
+                        reader.incrementState();
+
+                    //noinspection fallthrough
+                    case 1:
+                        headers = reader.readMap("headers", MessageCollectionItemType.STRING, MessageCollectionItemType.STRING, false);

Review comment:
       I don't understand, shouldn't we read the headers first?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));
+
+        Attribute<MessageDeserializer<NetworkMessage>> messageAttr = ctx.channel().attr(DESERIALIZER_KEY);
+
+        while (buffer.hasRemaining()) {
+            MessageDeserializer<NetworkMessage> msg = messageAttr.get();
+
+            try {
+                // Read message type.
+                if (msg == null && buffer.remaining() >= NetworkMessage.DIRECT_TYPE_SIZE) {
+                    byte b0 = buffer.get();
+                    byte b1 = buffer.get();
+
+                    msg = serializationRegistry.createDeserializer(makeMessageType(b0, b1));
+                }
+
+                boolean finished = false;
+
+                // Read message if buffer has remaining data.
+                if (msg != null && buffer.hasRemaining()) {
+                    reader.setCurrentReadClass(msg.klass());
+                    reader.setBuffer(buffer);
+
+                    finished = msg.readMessage(reader);
+                }
+
+                // Set read position to Netty's ByteBuf.
+                in.readerIndex(buffer.position());
+
+                if (finished) {
+                    reader.reset();
+                    messageAttr.set(null);
+
+                    out.add(msg.getMessage());
+                }
+                else
+                    messageAttr.set(msg);
+            }
+            catch (Throwable e) {
+                System.err.println("Failed to read message [msg=" + msg +
+                    ", buf=" + buffer +
+                    ", reader=" + reader + "]: " + e.getMessage());
+
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * Concatenates the two parameter bytes to form a message type value.
+     *
+     * @param b0 The first byte.
+     * @param b1 The second byte.
+     */
+    public static short makeMessageType(byte b0, byte b1) {

Review comment:
       ```suggestion
       private static short makeMessageType(byte b0, byte b1) {
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);

Review comment:
       should we use the `IgniteLogger` here?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOGGER.info("[{}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOGGER.info("[{}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return Mono.defer(() -> {
+            stop.onComplete();
+            return onStop;
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        return Mono.defer(() -> {

Review comment:
       I would suggest reformatting this part, as it looks quite ugly to me, for example:
   ```
   var inetAddr = InetSocketAddress.createUnresolved(address.host(), address.port());
   return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(inetAddr)))
       .map(client -> {
           client.send(fromMessage(message));
           return null;
       });
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOGGER.info("[{}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOGGER.info("[{}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return Mono.defer(() -> {
+            stop.onComplete();
+            return onStop;
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        return Mono.defer(() -> {
+            return Mono.fromFuture(connectionManager.channel(InetSocketAddress.createUnresolved(address.host(), address.port())));
+        }).flatMap(client -> {
+            client.send(fromMessage(message));
+            return Mono.empty().then();
+        });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        ObjectOutputStream o;
+
+        try {
+            o = new ObjectOutputStream(stream);
+            o.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {
+            ScaleCubeMessage msg = (ScaleCubeMessage) networkMessage;
+
+            Map<String, String> headers = msg.getHeaders();
+
+            Object obj;
+
+            try {
+                obj = new ObjectInputStream(new ByteArrayInputStream(msg.getArray())).readObject();

Review comment:
       I understand that it is not strictly necessary here, but let's handle resources properly:
   ```
   try (var in = new ObjectInputStream(new ByteArrayInputStream(msg.getArray()))) {
       obj = in.readObject();
   }
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOGGER.info("[{}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOGGER.info("[{}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return Mono.defer(() -> {
+            stop.onComplete();
+            return onStop;
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        return Mono.defer(() -> {
+            return Mono.fromFuture(connectionManager.channel(InetSocketAddress.createUnresolved(address.host(), address.port())));
+        }).flatMap(client -> {
+            client.send(fromMessage(message));
+            return Mono.empty().then();

Review comment:
       why do you need `then` here? I also think that `flatMap` is redundant, see my suggestion above




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619694520



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /**
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        CompletableFuture<Void> serverStartFuture = new CompletableFuture<>();
+
+        ChannelFuture bindFuture = bootstrap.bind(port);
+
+        bindFuture.addListener(bind -> {
+            this.channel = (ServerSocketChannel) bindFuture.channel();
+
+            if (bind.isSuccess())
+                serverStartFuture.complete(null);
+            else {
+                Throwable cause = bind.cause();
+                serverStartFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loops on server stop.
+            channel.closeFuture().addListener(close -> {
+                workerGroup.shutdownGracefully();

Review comment:
       I don't think so, as it happens in a listener and there is no place (and need) to actually wait for the event loop to shutdown

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));

Review comment:
       I think there isn't, but no harm in changing it, sure

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));

Review comment:
       Sure, I'll move it to ConnectionManager (just like it was in GridIoManager in 2.x)

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessageSerializationFactory.java
##########
@@ -0,0 +1,110 @@
+/*
+ * 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.network.scalecube.message;
+
+import java.util.Map;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageMappingException;
+import org.apache.ignite.network.message.MessageSerializationFactory;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+public class ScaleCubeMessageSerializationFactory implements MessageSerializationFactory<ScaleCubeMessage> {
+    /** {@inheritDoc} */
+    @Override public MessageDeserializer<ScaleCubeMessage> createDeserializer() {
+        return new MessageDeserializer<ScaleCubeMessage>() {
+
+            ScaleCubeMessage obj;
+
+            byte[] array;
+
+            Map<String, String> headers;
+
+            /** {@inheritDoc} */
+            @Override public boolean readMessage(MessageReader reader) throws MessageMappingException {
+                if (!reader.beforeMessageRead())
+                    return false;
+
+                switch (reader.state()) {
+                    case 0:
+                        array = reader.readByteArray("array");
+
+                        if (!reader.isLastRead())
+                            return false;
+
+                        reader.incrementState();
+
+                    //noinspection fallthrough
+                    case 1:
+                        headers = reader.readMap("headers", MessageCollectionItemType.STRING, MessageCollectionItemType.STRING, false);

Review comment:
       No, why? The order is lexicographic

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())

Review comment:
       I'm not sure if it's suitable for IPv6 addresses.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630808725



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -82,12 +85,34 @@ public NettyClient(
         if (clientFuture != null)
             throw new IgniteInternalException("Attempted to start an already started NettyClient");
 
-        clientFuture = NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
-            .thenApply(ch -> {
-                clientCloseFuture = NettyUtils.toCompletableFuture(ch.closeFuture());
-                channel = ch;
-
-                return new NettySender(channel, serializationRegistry);
+        clientFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.connect(address))
+            .whenComplete((channel, throwable) -> {
+                synchronized (this) {
+                    if (throwable == null) {
+                        CompletableFuture<Void> closeFuture = NettyUtils.toCompletableFuture(channel.closeFuture());
+
+                        if (stopped) {
+                            // Close channel in case if client has been stopped prior to this moment.
+                            channel.close();

Review comment:
       can be written shorter:
   ```
   // Close channel in case if client has been stopped prior to this moment.
   channel.close()
       // Wait for channel to close and then cancel the client future.
       .addListener(fut -> clientFuture.cancel(true));
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -105,8 +130,11 @@ public NettyClient(
      *
      * @return Future that is resolved when the client's channel has closed.
      */
-    public CompletableFuture<Void> stop() {
-        channel.close();
+    public synchronized CompletableFuture<Void> stop() {
+        stopped = true;
+
+        if (channel != null)

Review comment:
       you can get rid of the `clientCloseFuture` and simply write:
   ```
   return channel == null ?
       CompletableFuture.completedFuture(null) :
       NettyUtils.toCompletableFuture(channel.close());
   ```    




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628174653



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,117 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMarshallingUtils;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null) {
+            reader = new DirectMessageReader(serializationRegistry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+            readerAttr.set(reader);

Review comment:
       No, not really, one channel can't be read from multiple threads




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619694520



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,164 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.ServerSocketChannel;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link ServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Server socket channel. */
+    private ServerSocketChannel channel;
+
+    /** New connections listener. */
+    private final Consumer<SocketChannel> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<SocketChannel> newConnectionListener,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start server.
+     *
+     * @return Future that resolves when server is successfuly started.
+     */
+    public CompletableFuture<Void> start() {
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        /**
+                         * Decoder that uses {@link org.apache.ignite.network.internal.MessageReader}
+                         *  to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        /** Handles decoded {@link NetworkMessage}s. */
+                        new MessageHandler(messageListener),
+                        /**
+                         * Encoder that uses {@link org.apache.ignite.network.internal.MessageWriter}
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(ch);
+                }
+            })
+            /**
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /**
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        CompletableFuture<Void> serverStartFuture = new CompletableFuture<>();
+
+        ChannelFuture bindFuture = bootstrap.bind(port);
+
+        bindFuture.addListener(bind -> {
+            this.channel = (ServerSocketChannel) bindFuture.channel();
+
+            if (bind.isSuccess())
+                serverStartFuture.complete(null);
+            else {
+                Throwable cause = bind.cause();
+                serverStartFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loops on server stop.
+            channel.closeFuture().addListener(close -> {
+                workerGroup.shutdownGracefully();

Review comment:
       I don't think so, as it happens in a listener and there is no place (and need) to actually wait for the event loop to shutdown




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623856714



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/direct/DirectUtils.java
##########
@@ -0,0 +1,49 @@
+/*
+ * 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.network.internal.direct;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Direct marshalling utils.
+ */
+public class DirectUtils {
+    /**
+     * Reads a direct message type from a byte buffer.
+     *
+     * @param buffer Byte buffer.
+     * @return Direct message type.
+     */
+    public static short getMessageType(ByteBuffer buffer) {

Review comment:
       I think in the future there'll be more methods




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623137733



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try(ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {

Review comment:
       I see, forgot about it




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r629246142



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,234 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.SocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link NioServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap;
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<SocketAddress, NetworkMessage> messageListener;
+
+    /** Server start future. */
+    private CompletableFuture<Void> serverStartFuture;
+
+    /** Server socket channel. */
+    private volatile ServerChannel channel;
+
+    /** Server close future. */
+    private CompletableFuture<Void> serverCloseFuture;
+
+    /** New connections listener. */
+    private final Consumer<NettySender> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this(new ServerBootstrap(), port, newConnectionListener, messageListener, serializationRegistry);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param bootstrap Server bootstrap.
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        ServerBootstrap bootstrap,
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.bootstrap = bootstrap;
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return Future that resolves when the server is successfully started.
+     */
+    public CompletableFuture<Void> start() {
+        if (serverStartFuture != null)
+            throw new IgniteInternalException("Attempted to start an already started server");
+
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch) {
+                    ch.pipeline().addLast(
+                        /*
+                         * Decoder that uses org.apache.ignite.network.internal.MessageReader
+                         * to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        // Handles decoded NetworkMessages.
+                        new MessageHandler(messageListener),
+                        /*
+                         * Encoder that uses org.apache.ignite.network.internal.MessageWriter
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(new NettySender(ch, serializationRegistry));
+                }
+            })
+            /*
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /*
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        serverStartFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.bind(port)).whenComplete((ch, throwable) -> {

Review comment:
       We have to wait until all of the event loops are terminated though, so I changed your suggestion a little bit




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov merged pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov merged pull request #102:
URL: https://github.com/apache/ignite-3/pull/102


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619182711



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.

Review comment:
       yes, but I think that your variant is not grammatically correct. Or am I wrong?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628191624



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/AllTypesMessageGenerator.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.network.internal;
+
+import java.lang.reflect.Field;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.lang.IgniteUuidGenerator;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Generator for an {@link AllTypesMessage}.
+ */
+public class AllTypesMessageGenerator {
+    /**
+     * Generate a new {@link AllTypesMessage}.
+     *
+     * @param seed Random seed.
+     * @param nestedMsg {@code true} if nested messages should be generated, {@code false} otherwise.
+     * @return Message.
+     * @throws Exception If failed.
+     */
+    public static AllTypesMessage generate(long seed, boolean nestedMsg) {
+        try {
+            var random = new Random(seed);
+
+            var message = new AllTypesMessage();
+
+            Field[] fields = AllTypesMessage.class.getDeclaredFields();
+
+            for (Field field : fields) {
+                field.setAccessible(true);
+
+                TestFieldType annotation = field.getAnnotation(TestFieldType.class);
+
+                if (annotation != null) {
+                    field.set(message, randomValue(random, annotation.value(), nestedMsg));
+                }
+            }
+
+            if (nestedMsg) {
+                Field objectArrayField = AllTypesMessage.class.getDeclaredField("v");
+                objectArrayField.setAccessible(true);
+
+                Field collectionField = AllTypesMessage.class.getDeclaredField("w");
+                collectionField.setAccessible(true);
+
+                Field mapField = AllTypesMessage.class.getDeclaredField("x");
+                mapField.setAccessible(true);
+
+                Object[] array = IntStream.range(0, 10).mapToObj(unused -> generate(seed, false)).toArray();
+
+                objectArrayField.set(message, array);

Review comment:
       true :D




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623881412



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))

Review comment:
       Sure, that looks better




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628278292



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -0,0 +1,99 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.apache.ignite.network.internal.AllTypesMessage;
+import org.apache.ignite.network.internal.AllTypesMessageGenerator;
+import org.apache.ignite.network.internal.AllTypesMessageSerializationFactory;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Tests for {@link InboundDecoder}.
+ */
+public class InboundDecoderTest {
+    /**
+     * Tests that an {@link InboundDecoder} can successfully read a message with all types supported
+     * by direct marshalling.
+     *
+     * @param seed Random seed.
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @MethodSource("messageGenerationSeed")
+    public void test(long seed) throws Exception {
+        var registry = new MessageSerializationRegistry();
+
+        AllTypesMessage msg = AllTypesMessageGenerator.generate(seed, true);
+
+        registry.registerFactory(msg.directType(), new AllTypesMessageSerializationFactory());
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        var writer = new DirectMessageWriter(registry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        MessageSerializer<NetworkMessage> serializer = registry.createSerializer(msg.directType());
+
+        UnpooledByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
+
+        ByteBuffer buf = ByteBuffer.allocate(10_000);
+
+        AllTypesMessage output;
+
+        do {
+            buf.clear();
+
+            writer.setBuffer(buf);

Review comment:
       how is it related? Shouldn't the buffer reference stay the same regardless of the `clear` called or not?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623878993



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+    }
+
+    /**
+     * Create an unresolved {@link InetSocketAddress} with "localhost" as a host.
+     *
+     * @param port Port.
+     * @return Address.
+     */
+    private InetSocketAddress address(int port) {
+        return InetSocketAddress.createUnresolved("localhost", port);

Review comment:
       Not really, I just don't think we need it. If it's too cryptic, I can remove it tho




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622139636



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null) {
+            reader = new DirectMessageReader(serializationRegistry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+            readerAttr.set(reader);
+        }
+
+        Attribute<MessageDeserializer<NetworkMessage>> messageAttr = ctx.attr(DESERIALIZER_KEY);
+
+        while (buffer.hasRemaining()) {
+            MessageDeserializer<NetworkMessage> msg = messageAttr.get();
+
+            try {
+                // Read message type.
+                if (msg == null && buffer.remaining() >= NetworkMessage.DIRECT_TYPE_SIZE) {
+                    byte b0 = buffer.get();

Review comment:
       That's how it was in 2.x, I think we will change it later




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628176822



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/DelegatingTransportFactory.java
##########
@@ -35,50 +34,66 @@
     /** */
     private final ScaleCubeMessagingService messagingService;
 
+    /** Delegate transport factory. */
+    private final TransportFactory factory;
+
     /**
      * @param messagingService Messaging service.
+     * @param factory Delegate transport factory.
      */
-    DelegatingTransportFactory(ScaleCubeMessagingService messagingService) {
+    DelegatingTransportFactory(ScaleCubeMessagingService messagingService, TransportFactory factory) {
         this.messagingService = messagingService;
+        this.factory = factory;
     }
 
     /** {@inheritDoc} */
     @Override public Transport createTransport(TransportConfig config) {
-        var delegateFactory = TransportFactory.INSTANCE == null ? new TcpTransportFactory() : TransportFactory.INSTANCE;
-
-        Transport delegate = delegateFactory.createTransport(config);
+        Transport delegate = factory.createTransport(config);

Review comment:
       Not sure if it's a good idea: a ScaleCube cluster node doesn't send messages to itself, so I would prefer to keep the custom transport implementation clean. Delegating transport is only needed until we implement a messaging service on top of the connection manager




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r621072055



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.

Review comment:
       Absolutely correct, even the comma after "connections" and before "both" is entirely optional (and I hate it for that)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623749773



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();

Review comment:
       Not sure why it should. ScaleCube's default transport also doesn't have any acks. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623852870



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       I would suggest simply not reassigning the `sender` variable for the second time, current approach looks a little bit cryptic to me (and `final` is no needed, btw)

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       I would suggest simply not reassigning the `sender` variable for the second time, current approach looks a little bit cryptic to me (and `final` is not needed, btw)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622139899



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,119 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = connect.channel();
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    public static Bootstrap setupBootstrap(
+        EventLoopGroup eventLoopGroup,
+        MessageSerializationRegistry serializationRegistry,
+        BiConsumer<InetSocketAddress, NetworkMessage> messageListener
+    ) {
+        Bootstrap clientBootstrap = new Bootstrap();
+
+        clientBootstrap.group(eventLoopGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */

Review comment:
       Yes, why not?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619702815



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ *
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null)
+            readerAttr.set(reader = new DirectMessageReader(serializationRegistry, (byte) 1));

Review comment:
       Sure, I'll move it to ConnectionManager (just like it was in GridIoManager in 2.x)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628231212



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.
+     */
+    public boolean failedToConnect() {
+        return clientFuture.isCompletedExceptionally();
+    }
+
+    /**
+     * @return {@code true} if client lost connection, {@code false} otherwise.
+     */
+    public boolean isDisconnected() {
+        return channel != null && !channel.isOpen();
+    }
+
+    /**
+     * Creates {@link Bootstrap} for clients, providing channel handlers and options.
+     *
+     * @param eventLoopGroup Event loop group for channel handling.
+     * @param serializationRegistry Serialization registry.
+     * @param messageListener Message listener.
+     * @return Bootstrap for clients.
+     */
+    public static Bootstrap createBootstrap(

Review comment:
       Ok then




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619075440



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {
+            NettyClient client = clients.computeIfAbsent(address, this::connect);
+            return client.sender();
+        }
+
+        return CompletableFuture.completedFuture(channel);
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(InetSocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> {
+            consumer.accept(from, message);
+        });
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(SocketChannel channel) {
+        InetSocketAddress remoteAddress = channel.remoteAddress();
+        // TODO: there might be outgoing connection already
+        channels.put(remoteAddress, new NettySender(channel, serializationRegistry));
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(InetSocketAddress address) {
+        NettyClient client = new NettyClient(address.getHostName(), address.getPort(), serializationRegistry, (src, message) -> {
+            this.onMessage(src, message);
+        });
+
+        client.start().whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);

Review comment:
       There is no associated channel for this client, it only gets put in a map if there were no errors




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628022267



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITScaleCubeNetworkMessagingTest.java
##########
@@ -251,13 +254,16 @@ private static void stopForcefully(ClusterService cluster) throws Exception {
     }
 
     /**
-     * Wrapper for cluster.
+     * Wrapper for a cluster.
      */
     private static final class Cluster {
         /** */
-        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry();
+        private final ClusterServiceFactory NETWORK_FACTORY = new ScaleCubeClusterServiceFactory();

Review comment:
       These variables are no longer static and should be renamed

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(manager -> manager.stop());
+    }
+
+    /**
+     * Tests that a message is sent successfully using the ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {
+            fut.complete(message);
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        NetworkMessage receivedMessage = fut.get(3, TimeUnit.SECONDS);
+
+        assertEquals(TestMessage.class, receivedMessage.getClass());
+        assertEquals(msgText, ((TestMessage) receivedMessage).msg());
+    }
+
+    /**
+     * Tests that the resources of a connection manager are closed after a shutdown.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testShutdown() throws Exception {
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        NettySender sender1 = manager1.channel(address(port2)).get();
+        NettySender sender2 = manager2.channel(address(port1)).get();
+
+        assertNotNull(sender1);
+        assertNotNull(sender2);
+
+        Stream.of(manager1, manager2).forEach(manager -> {
+            NettyServer server = manager.server();
+            Collection<NettyClient> clients = manager.clients();
+
+            manager.stop();
+
+            assertFalse(server.isRunning());
+
+            boolean clientsStopped = clients.stream().allMatch(NettyClient::isDisconnected);
+
+            assertTrue(clientsStopped);
+        });
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());

Review comment:
       ```suggestion
           TestMessage testMessage = new TestMessage(msgText);
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(SocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(NettySender channel) {
+        SocketAddress remoteAddress = channel.remoteAddress();
+        channels.put(remoteAddress, channel);
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(SocketAddress address) {
+        NettyClient client = new NettyClient(

Review comment:
       this fits on one line:
   ```
   NettyClient client = new NettyClient(address, serializationRegistry);
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(SocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(NettySender channel) {
+        SocketAddress remoteAddress = channel.remoteAddress();
+        channels.put(remoteAddress, channel);
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(SocketAddress address) {
+        NettyClient client = new NettyClient(
+            address,
+            serializationRegistry
+        );
+
+        client.start(clientBootstrap).whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<SocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stops the server and all clients.
+     */
+    public void stop() {
+         var stream = Stream.concat(
+            clients.values().stream().map(NettyClient::stop),
+            Stream.of(server.stop())
+        );
+
+         var stopFut = CompletableFuture.allOf(stream.toArray(CompletableFuture<?>[]::new));
+
+         try {
+             stopFut.join();
+             clientWorkerGroup.shutdownGracefully().sync();
+         }
+         catch (Exception e) {
+             LOG.warn("Failed to stop ConnectionManager: " + e.getMessage());

Review comment:
       ```suggestion
                LOG.warn("Failed to stop the ConnectionManager: " + e.getMessage());
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/DelegatingTransportFactory.java
##########
@@ -35,50 +34,66 @@
     /** */
     private final ScaleCubeMessagingService messagingService;
 
+    /** Delegate transport factory. */
+    private final TransportFactory factory;
+
     /**
      * @param messagingService Messaging service.
+     * @param factory Delegate transport factory.
      */
-    DelegatingTransportFactory(ScaleCubeMessagingService messagingService) {
+    DelegatingTransportFactory(ScaleCubeMessagingService messagingService, TransportFactory factory) {
         this.messagingService = messagingService;
+        this.factory = factory;
     }
 
     /** {@inheritDoc} */
     @Override public Transport createTransport(TransportConfig config) {
-        var delegateFactory = TransportFactory.INSTANCE == null ? new TcpTransportFactory() : TransportFactory.INSTANCE;
-
-        Transport delegate = delegateFactory.createTransport(config);
+        Transport delegate = factory.createTransport(config);

Review comment:
       This factory is no longer needed, you can simply handle the self-request case correctly inside our custom transport

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,117 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMarshallingUtils;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.channel().attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null) {
+            reader = new DirectMessageReader(serializationRegistry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+            readerAttr.set(reader);

Review comment:
       should we use a more thread-safe approach here (e.g. `compareAndSet`)?

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/AllTypesMessageGenerator.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.network.internal;
+
+import java.lang.reflect.Field;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.lang.IgniteUuidGenerator;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Generator for an {@link AllTypesMessage}.
+ */
+public class AllTypesMessageGenerator {
+    /**
+     * Generate a new {@link AllTypesMessage}.
+     *
+     * @param seed Random seed.
+     * @param nestedMsg {@code true} if nested messages should be generated, {@code false} otherwise.
+     * @return Message.
+     * @throws Exception If failed.
+     */
+    public static AllTypesMessage generate(long seed, boolean nestedMsg) {
+        try {
+            var random = new Random(seed);
+
+            var message = new AllTypesMessage();
+
+            Field[] fields = AllTypesMessage.class.getDeclaredFields();
+
+            for (Field field : fields) {
+                field.setAccessible(true);

Review comment:
       Aren't these fields already accessible?

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyClientTest.java
##########
@@ -0,0 +1,173 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyClient}.
+ */
+class NettyClientTest {
+    /** */
+    private final SocketAddress address = InetSocketAddress.createUnresolved("", 0);
+
+    /**
+     * Tests a scenario where NettyClient connects successfully.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSuccessfullConnect() throws InterruptedException, ExecutionException, TimeoutException {

Review comment:
       ```suggestion
       public void testSuccessfulConnect() throws Exception {
   ```

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(manager -> manager.stop());
+    }
+
+    /**
+     * Tests that a message is sent successfully using the ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {
+            fut.complete(message);
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        NetworkMessage receivedMessage = fut.get(3, TimeUnit.SECONDS);
+
+        assertEquals(TestMessage.class, receivedMessage.getClass());
+        assertEquals(msgText, ((TestMessage) receivedMessage).msg());
+    }
+
+    /**
+     * Tests that the resources of a connection manager are closed after a shutdown.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testShutdown() throws Exception {
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        NettySender sender1 = manager1.channel(address(port2)).get();
+        NettySender sender2 = manager2.channel(address(port1)).get();
+
+        assertNotNull(sender1);
+        assertNotNull(sender2);
+
+        Stream.of(manager1, manager2).forEach(manager -> {
+            NettyServer server = manager.server();
+            Collection<NettyClient> clients = manager.clients();
+
+            manager.stop();
+
+            assertFalse(server.isRunning());
+
+            boolean clientsStopped = clients.stream().allMatch(NettyClient::isDisconnected);
+
+            assertTrue(clientsStopped);
+        });
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (Exception e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {
+            fut.complete(message);
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+
+        NetworkMessage receivedMessage = fut.get(3, TimeUnit.SECONDS);
+
+        assertEquals(TestMessage.class, receivedMessage.getClass());

Review comment:
       same here

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(manager -> manager.stop());
+    }
+
+    /**
+     * Tests that a message is sent successfully using the ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {

Review comment:
       `manager2.addListener((address, message) -> fut.complete(message));`

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(manager -> manager.stop());
+    }
+
+    /**
+     * Tests that a message is sent successfully using the ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {
+            fut.complete(message);
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());

Review comment:
       ```suggestion
           TestMessage testMessage = new TestMessage(msgText);
   ```

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(manager -> manager.stop());
+    }
+
+    /**
+     * Tests that a message is sent successfully using the ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {
+            fut.complete(message);
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        NetworkMessage receivedMessage = fut.get(3, TimeUnit.SECONDS);
+
+        assertEquals(TestMessage.class, receivedMessage.getClass());

Review comment:
       strictly speaking, this assert is redundant, since you will get a ClassCastException below in case of an incorrect message class

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(manager -> manager.stop());

Review comment:
       ```suggestion
           startedManagers.forEach(ConnectionManager::stop);
   ```

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,201 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettyClient;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.internal.netty.NettyServer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(manager -> manager.stop());
+    }
+
+    /**
+     * Tests that a message is sent successfully using the ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {
+            fut.complete(message);
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        NetworkMessage receivedMessage = fut.get(3, TimeUnit.SECONDS);
+
+        assertEquals(TestMessage.class, receivedMessage.getClass());
+        assertEquals(msgText, ((TestMessage) receivedMessage).msg());
+    }
+
+    /**
+     * Tests that the resources of a connection manager are closed after a shutdown.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testShutdown() throws Exception {
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        NettySender sender1 = manager1.channel(address(port2)).get();
+        NettySender sender2 = manager2.channel(address(port1)).get();
+
+        assertNotNull(sender1);
+        assertNotNull(sender2);
+
+        Stream.of(manager1, manager2).forEach(manager -> {
+            NettyServer server = manager.server();
+            Collection<NettyClient> clients = manager.clients();
+
+            manager.stop();
+
+            assertFalse(server.isRunning());
+
+            boolean clientsStopped = clients.stream().allMatch(NettyClient::isDisconnected);
+
+            assertTrue(clientsStopped);
+        });
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        ConnectionManager manager1 = startManager(port1);
+        ConnectionManager manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (Exception e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        var fut = new CompletableFuture<NetworkMessage>();
+
+        manager2.addListener((address, message) -> {

Review comment:
       `manager2.addListener((address, message) -> fut.complete(message));`

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())

Review comment:
       Can be written a little bit shorter:
   ```
   NettyClient client = clients.compute(address, (addr, existingClient) ->
       existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected() ?
           existingClient :
           connect(addr)
   );
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(SocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(NettySender channel) {
+        SocketAddress remoteAddress = channel.remoteAddress();
+        channels.put(remoteAddress, channel);
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(SocketAddress address) {
+        NettyClient client = new NettyClient(
+            address,
+            serializationRegistry
+        );
+
+        client.start(clientBootstrap).whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<SocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stops the server and all clients.
+     */
+    public void stop() {
+         var stream = Stream.concat(

Review comment:
       Wouldn't the following approach be shorter and more correct?
   ```
   var stream = Stream.concat(
       clients.values().stream().map(NettyClient::stop),
       Stream.of(server.stop(), clientWorkerGroup.shutdownGracefully())
   );
    
    stream.forEach(fut -> {
        try {
           fut.get();
        } catch (Exception e) {
            LOG.warn("Failed to stop the ConnectionManager: " + e.getMessage());
        }
    });
    ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyUtils.java
##########
@@ -0,0 +1,56 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * Netty utilities.
+ */
+public class NettyUtils {
+    /**
+     * Convert a Netty {@link Future} to a {@link CompletableFuture}.
+     *
+     * @param nettyFuture Netty future.
+     * @param mapper Function that maps successfully resolved Netty future to a value for a CompletableFuture.
+     * @param <T> Resulting future type.
+     * @param <R> Netty future result type.
+     * @param <F> Netty future type.
+     * @return CompletableFuture.
+     */
+    public static <T, R, F extends Future<R>> CompletableFuture<T> toCompletableFuture(

Review comment:
       I think that the current approach is too generic. Looking at the actual use cases, I would suggest using two methods instead:
   1. One that accepts a `Future` and returns a `CompletableFuture`: `<R> CompletableFuture<R> toCompletableFuture(Future<R> future)`
   2. One that accepts a `ChannelFuture`: `CompletableFuture<Channel> toCompletableFuture(ChannelFuture future)`
   
   What do you think?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeClusterServiceFactory.java
##########
@@ -39,7 +41,10 @@
     @Override public ClusterService createClusterService(ClusterLocalConfiguration context) {
         var topologyService = new ScaleCubeTopologyService();
         var messagingService = new ScaleCubeMessagingService(topologyService);
-        var transportFactory = new DelegatingTransportFactory(messagingService);
+        MessageSerializationRegistry registry = context.getSerializationRegistry();
+        ConnectionManager connectionManager = new ConnectionManager(context.getPort(), registry);
+        connectionManager.start();

Review comment:
       I think that the connection manager should be started inside the `ClusterService#start` method.

##########
File path: modules/network/src/test/java/org/apache/ignite/network/message/MessageSerializationRegistryTest.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.message;
+
+import org.apache.ignite.network.NetworkConfigurationException;
+import org.apache.ignite.network.internal.MessageReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * {@link MessageSerializationRegistry} tests.
+ */
+class MessageSerializationRegistryTest {
+    /**
+     * Tests that a serialization factory can be registered.
+     */
+    @Test
+    public void testRegisterFactory() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+    }
+
+    /**
+     * Tests that a serialization factory can't be registered if there is an already registered serialization factory
+     * with the same direct type.
+     */
+    @Test
+    public void testRegisterFactoryWithSameType() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+
+        assertThrows(NetworkConfigurationException.class, () -> {
+            registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+        });
+    }
+
+    /**
+     * Tests that a {@link MessageSerializer} and a {@link MessageDeserializer} can be created if a
+     * {@link MessageSerializationFactory} was registered.
+     */
+    @Test
+    public void testCreateSerializers() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+
+        assertNotNull(registry.createSerializer(Msg.TYPE));
+        assertNotNull(registry.createDeserializer(Msg.TYPE));
+    }
+
+    /**
+     * Tests that creation of a {@link MessageSerializer} or a {@link MessageDeserializer} fails if a
+     * {@link MessageSerializationFactory} was not registered.
+     */
+    @Test
+    public void testCreateSerializersIfNotRegistered() {
+        var registry = new MessageSerializationRegistry();
+
+        assertThrows(AssertionError.class, () -> registry.createSerializer(Msg.TYPE));
+        assertThrows(AssertionError.class, () -> registry.createDeserializer(Msg.TYPE));
+    }
+
+    /** */
+    static class Msg implements NetworkMessage {

Review comment:
       although your current approach is fine, all these test classes can be replaced with mocks, resulting in less code:
   ```
   private static final byte TYPE = 0;
   
   private MessageSerializationFactory<?> mockFactory() {
       return new MessageSerializationFactory<>() {
           @Override public MessageDeserializer<NetworkMessage> createDeserializer() {
               return mock(MessageDeserializer.class);
           }
   
           @Override public MessageSerializer<NetworkMessage> createSerializer() {
               return mock(MessageSerializer.class);
           }
       };
   }
   ```

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -0,0 +1,123 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyServer}.
+ */
+public class NettyServerTest {
+    /**
+     * Tests a successfull server start scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSuccessfullServerStart() throws Exception {

Review comment:
       ```suggestion
       public void testSuccessfulServerStart() throws Exception {
   ```

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -0,0 +1,123 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyServer}.
+ */
+public class NettyServerTest {
+    /**
+     * Tests a successfull server start scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSuccessfullServerStart() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertTrue(server.isRunning());
+    }
+
+    /**
+     * Tests a graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerGracefulShutdown() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        server.stop().join();
+
+        assertTrue(server.getBossGroup().isTerminated());
+        assertTrue(server.getWorkerGroup().isTerminated());
+    }
+
+    /**
+     * Tests a non-graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerChannelClosedAbruptly() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        channel.close();
+
+        assertTrue(server.getBossGroup().isShuttingDown());
+        assertTrue(server.getWorkerGroup().isShuttingDown());
+    }
+
+    /**
+     * Tests that a {@link NettyServer#start} method can be called only once.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testStartTwice() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertThrows(IgniteInternalException.class, () -> {
+            server.start();
+        });
+    }
+
+    /**
+     * Creates a server from a backing {@link ChannelFuture}.
+     *
+     * @param future Channel future.
+     * @return NettyServer.
+     * @throws Exception If failed.
+     */
+    private NettyServer getServer(ChannelFuture future) throws Exception {

Review comment:
       ```suggestion
       private static NettyServer getServer(ChannelFuture future) throws Exception {
   ```

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -0,0 +1,123 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyServer}.
+ */
+public class NettyServerTest {
+    /**
+     * Tests a successfull server start scenario.

Review comment:
       ```suggestion
        * Tests a successful server start scenario.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.

Review comment:
       ```suggestion
        * Callback that is called upon receiving a new message.
   ```

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -0,0 +1,123 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyServer}.
+ */
+public class NettyServerTest {
+    /**
+     * Tests a successfull server start scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSuccessfullServerStart() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertTrue(server.isRunning());
+    }
+
+    /**
+     * Tests a graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerGracefulShutdown() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        server.stop().join();
+
+        assertTrue(server.getBossGroup().isTerminated());
+        assertTrue(server.getWorkerGroup().isTerminated());
+    }
+
+    /**
+     * Tests a non-graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerChannelClosedAbruptly() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        channel.close();
+
+        assertTrue(server.getBossGroup().isShuttingDown());
+        assertTrue(server.getWorkerGroup().isShuttingDown());
+    }
+
+    /**
+     * Tests that a {@link NettyServer#start} method can be called only once.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testStartTwice() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertThrows(IgniteInternalException.class, () -> {

Review comment:
       `assertThrows(IgniteInternalException.class, server::start);`

##########
File path: modules/network/src/test/java/org/apache/ignite/network/message/MessageSerializationRegistryTest.java
##########
@@ -0,0 +1,120 @@
+/*
+ * 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.network.message;
+
+import org.apache.ignite.network.NetworkConfigurationException;
+import org.apache.ignite.network.internal.MessageReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * {@link MessageSerializationRegistry} tests.
+ */
+class MessageSerializationRegistryTest {
+    /**
+     * Tests that a serialization factory can be registered.
+     */
+    @Test
+    public void testRegisterFactory() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+    }
+
+    /**
+     * Tests that a serialization factory can't be registered if there is an already registered serialization factory
+     * with the same direct type.
+     */
+    @Test
+    public void testRegisterFactoryWithSameType() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+
+        assertThrows(NetworkConfigurationException.class, () -> {
+            registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+        });
+    }
+
+    /**
+     * Tests that a {@link MessageSerializer} and a {@link MessageDeserializer} can be created if a
+     * {@link MessageSerializationFactory} was registered.
+     */
+    @Test
+    public void testCreateSerializers() {
+        var registry = new MessageSerializationRegistry();
+
+        registry.registerFactory(Msg.TYPE, new MsgSerializationFactory());
+
+        assertNotNull(registry.createSerializer(Msg.TYPE));
+        assertNotNull(registry.createDeserializer(Msg.TYPE));
+    }
+
+    /**
+     * Tests that creation of a {@link MessageSerializer} or a {@link MessageDeserializer} fails if a
+     * {@link MessageSerializationFactory} was not registered.
+     */
+    @Test
+    public void testCreateSerializersIfNotRegistered() {
+        var registry = new MessageSerializationRegistry();
+
+        assertThrows(AssertionError.class, () -> registry.createSerializer(Msg.TYPE));
+        assertThrows(AssertionError.class, () -> registry.createDeserializer(Msg.TYPE));
+    }
+
+    /** */
+    static class Msg implements NetworkMessage {
+        /** */
+        static final byte TYPE = 0;
+
+        /** {@inheritDoc} */
+        @Override public short directType() {
+            return TYPE;
+        }
+    }
+
+    /** */
+    static class MsgSerializationFactory implements MessageSerializationFactory<Msg> {
+        /** {@inheritDoc} */
+        @Override public MessageDeserializer<Msg> createDeserializer() {
+            return new MessageDeserializer<Msg>() {
+                /** {@inheritDoc} */
+                @Override public boolean readMessage(MessageReader reader) throws MessageMappingException {
+                    return false;
+                }
+
+                /** {@inheritDoc} */
+                @Override public Class<Msg> klass() {
+                    return null;
+                }
+
+                /** {@inheritDoc} */
+                @Override public Msg getMessage() {
+                    return null;
+                }
+            };
+        }
+
+        /** {@inheritDoc} */
+        @Override public MessageSerializer<Msg> createSerializer() {
+            return (message, writer) -> false;
+        }
+    }
+}

Review comment:
       Missing newline

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/NettyServerTest.java
##########
@@ -0,0 +1,123 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link NettyServer}.
+ */
+public class NettyServerTest {
+    /**
+     * Tests a successfull server start scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSuccessfullServerStart() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertTrue(server.isRunning());
+    }
+
+    /**
+     * Tests a graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerGracefulShutdown() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        server.stop().join();
+
+        assertTrue(server.getBossGroup().isTerminated());
+        assertTrue(server.getWorkerGroup().isTerminated());
+    }
+
+    /**
+     * Tests a non-graceful server shutdown scenario.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testServerChannelClosedAbruptly() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        channel.close();
+
+        assertTrue(server.getBossGroup().isShuttingDown());
+        assertTrue(server.getWorkerGroup().isShuttingDown());
+    }
+
+    /**
+     * Tests that a {@link NettyServer#start} method can be called only once.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testStartTwice() throws Exception {
+        var channel = new EmbeddedServerChannel();
+
+        NettyServer server = getServer(channel.newSucceededFuture());
+
+        assertThrows(IgniteInternalException.class, () -> {
+            server.start();
+        });
+    }
+
+    /**
+     * Creates a server from a backing {@link ChannelFuture}.
+     *
+     * @param future Channel future.
+     * @return NettyServer.
+     * @throws Exception If failed.
+     */
+    private NettyServer getServer(ChannelFuture future) throws Exception {

Review comment:
       you always pass `new EmbeddedServerChannel().newSucceededFuture()` as the parameter, so you can pass the Channel directly instead

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/AllTypesMessageGenerator.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.network.internal;
+
+import java.lang.reflect.Field;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.lang.IgniteUuidGenerator;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Generator for an {@link AllTypesMessage}.
+ */
+public class AllTypesMessageGenerator {
+    /**
+     * Generate a new {@link AllTypesMessage}.
+     *
+     * @param seed Random seed.
+     * @param nestedMsg {@code true} if nested messages should be generated, {@code false} otherwise.
+     * @return Message.
+     * @throws Exception If failed.
+     */
+    public static AllTypesMessage generate(long seed, boolean nestedMsg) {
+        try {
+            var random = new Random(seed);
+
+            var message = new AllTypesMessage();
+
+            Field[] fields = AllTypesMessage.class.getDeclaredFields();
+
+            for (Field field : fields) {
+                field.setAccessible(true);
+
+                TestFieldType annotation = field.getAnnotation(TestFieldType.class);
+
+                if (annotation != null) {
+                    field.set(message, randomValue(random, annotation.value(), nestedMsg));
+                }
+            }
+
+            if (nestedMsg) {
+                Field objectArrayField = AllTypesMessage.class.getDeclaredField("v");
+                objectArrayField.setAccessible(true);
+
+                Field collectionField = AllTypesMessage.class.getDeclaredField("w");
+                collectionField.setAccessible(true);
+
+                Field mapField = AllTypesMessage.class.getDeclaredField("x");
+                mapField.setAccessible(true);
+
+                Object[] array = IntStream.range(0, 10).mapToObj(unused -> generate(seed, false)).toArray();
+
+                objectArrayField.set(message, array);

Review comment:
       I think you got a little bit too excited using reflection here: `message.v = array;`. Same applies to all other field

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/AllTypesMessageGenerator.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.network.internal;
+
+import java.lang.reflect.Field;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.lang.IgniteUuidGenerator;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Generator for an {@link AllTypesMessage}.
+ */
+public class AllTypesMessageGenerator {
+    /**
+     * Generate a new {@link AllTypesMessage}.
+     *
+     * @param seed Random seed.
+     * @param nestedMsg {@code true} if nested messages should be generated, {@code false} otherwise.
+     * @return Message.
+     * @throws Exception If failed.
+     */
+    public static AllTypesMessage generate(long seed, boolean nestedMsg) {
+        try {
+            var random = new Random(seed);
+
+            var message = new AllTypesMessage();
+
+            Field[] fields = AllTypesMessage.class.getDeclaredFields();
+
+            for (Field field : fields) {
+                field.setAccessible(true);
+
+                TestFieldType annotation = field.getAnnotation(TestFieldType.class);
+
+                if (annotation != null) {
+                    field.set(message, randomValue(random, annotation.value(), nestedMsg));
+                }
+            }
+
+            if (nestedMsg) {
+                Field objectArrayField = AllTypesMessage.class.getDeclaredField("v");
+                objectArrayField.setAccessible(true);
+
+                Field collectionField = AllTypesMessage.class.getDeclaredField("w");
+                collectionField.setAccessible(true);
+
+                Field mapField = AllTypesMessage.class.getDeclaredField("x");
+                mapField.setAccessible(true);
+
+                Object[] array = IntStream.range(0, 10).mapToObj(unused -> generate(seed, false)).toArray();
+
+                objectArrayField.set(message, array);
+
+                List<Object> collection = IntStream.range(0, 10)
+                    .mapToObj(unused -> generate(seed, false))
+                    .collect(Collectors.toList());
+
+                collectionField.set(message, collection);
+
+                Map<String, Object> map = IntStream.range(0, 10)
+                    .boxed()
+                    .collect(Collectors.toMap(String::valueOf, unused -> generate(seed, false)));
+
+                mapField.set(message, map);
+            }
+
+            return message;
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Generate random value.
+     *
+     * @param random Seeded random.
+     * @param type Value type.
+     * @param nestedMsg {@code true} if nested messages should be generated, {@code false} otherwise.
+     * @return Random value.
+     * @throws Exception If failed.
+     */
+    private static Object randomValue(Random random, MessageCollectionItemType type, boolean nestedMsg) throws Exception {
+        Object value = null;

Review comment:
       This method will be much shorter if you remove this variable and use `return` inside branches instead

##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/netty/InboundDecoderTest.java
##########
@@ -0,0 +1,99 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.apache.ignite.network.internal.AllTypesMessage;
+import org.apache.ignite.network.internal.AllTypesMessageGenerator;
+import org.apache.ignite.network.internal.AllTypesMessageSerializationFactory;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Tests for {@link InboundDecoder}.
+ */
+public class InboundDecoderTest {
+    /**
+     * Tests that an {@link InboundDecoder} can successfully read a message with all types supported
+     * by direct marshalling.
+     *
+     * @param seed Random seed.
+     * @throws Exception If failed.
+     */
+    @ParameterizedTest
+    @MethodSource("messageGenerationSeed")
+    public void test(long seed) throws Exception {
+        var registry = new MessageSerializationRegistry();
+
+        AllTypesMessage msg = AllTypesMessageGenerator.generate(seed, true);
+
+        registry.registerFactory(msg.directType(), new AllTypesMessageSerializationFactory());
+
+        var channel = new EmbeddedChannel(new InboundDecoder(registry));
+
+        var writer = new DirectMessageWriter(registry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+
+        MessageSerializer<NetworkMessage> serializer = registry.createSerializer(msg.directType());
+
+        UnpooledByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
+
+        ByteBuffer buf = ByteBuffer.allocate(10_000);
+
+        AllTypesMessage output;
+
+        do {
+            buf.clear();
+
+            writer.setBuffer(buf);

Review comment:
       Do you need to call this method on every iteration?

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.
+     */
+    public boolean failedToConnect() {
+        return clientFuture.isCompletedExceptionally();
+    }
+
+    /**
+     * @return {@code true} if client lost connection, {@code false} otherwise.
+     */
+    public boolean isDisconnected() {
+        return channel != null && !channel.isOpen();
+    }
+
+    /**
+     * Creates {@link Bootstrap} for clients, providing channel handlers and options.
+     *
+     * @param eventLoopGroup Event loop group for channel handling.
+     * @param serializationRegistry Serialization registry.
+     * @param messageListener Message listener.
+     * @return Bootstrap for clients.
+     */
+    public static Bootstrap createBootstrap(

Review comment:
       What about this comment?

##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+    }
+
+    /**
+     * Create an unresolved {@link InetSocketAddress} with "localhost" as a host.
+     *
+     * @param port Port.
+     * @return Address.
+     */
+    private InetSocketAddress address(int port) {
+        return InetSocketAddress.createUnresolved("localhost", port);

Review comment:
       will you fix this comment or should we resolve it?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628171206



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(ConnectionManager.class);
+
+    /** Latest version of the direct marshalling protocol. */
+    public static final byte DIRECT_PROTOCOL_VERSION = 1;
+
+    /** Client bootstrap. */
+    private final Bootstrap clientBootstrap;
+
+    /** Client socket channel handler event loop group. */
+    private final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
+
+    /** Server. */
+    private final NettyServer server;
+
+    /** Channels. */
+    private final Map<SocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private final Map<SocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private final List<BiConsumer<SocketAddress, NetworkMessage>> listeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param registry Serialization registry.
+     */
+    public ConnectionManager(int port, MessageSerializationRegistry registry) {
+        this.serializationRegistry = registry;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+        this.clientBootstrap = NettyClient.createBootstrap(clientWorkerGroup, serializationRegistry, this::onMessage);
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().join();
+        }
+        catch (CompletionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public SocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Gets a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(SocketAddress address) {
+        NettySender channel = channels.compute(
+            address,
+            (addr, sender) -> (sender == null || !sender.isOpen()) ? null : sender
+        );
+
+        if (channel != null)
+            return CompletableFuture.completedFuture(channel);
+
+        NettyClient client = clients.compute(address, (addr, existingClient) -> {
+            if (existingClient != null && !existingClient.failedToConnect() && !existingClient.isDisconnected())
+                return existingClient;
+
+            return connect(addr);
+        });
+
+        return client.sender();
+    }
+
+    /**
+     * Callback that is called upon receiving of a new message.
+     *
+     * @param from Source of the message.
+     * @param message New message.
+     */
+    private void onMessage(SocketAddress from, NetworkMessage message) {
+        listeners.forEach(consumer -> consumer.accept(from, message));
+    }
+
+    /**
+     * Callback that is called upon new client connected to the server.
+     *
+     * @param channel Channel from client to this {@link #server}.
+     */
+    private void onNewIncomingChannel(NettySender channel) {
+        SocketAddress remoteAddress = channel.remoteAddress();
+        channels.put(remoteAddress, channel);
+    }
+
+    /**
+     * Create new client from this node to specified address.
+     *
+     * @param address Target address.
+     * @return New netty client.
+     */
+    private NettyClient connect(SocketAddress address) {
+        NettyClient client = new NettyClient(
+            address,
+            serializationRegistry
+        );
+
+        client.start(clientBootstrap).whenComplete((sender, throwable) -> {
+            if (throwable != null)
+                clients.remove(address);
+            else
+                channels.put(address, sender);
+        });
+
+        return client;
+    }
+
+    /**
+     * Add incoming message listener.
+     *
+     * @param listener Message listener.
+     */
+    public void addListener(BiConsumer<SocketAddress, NetworkMessage> listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Stops the server and all clients.
+     */
+    public void stop() {
+         var stream = Stream.concat(

Review comment:
       Sadly, no. We have to stop clients before the clients' event loop group




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r618417177



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();
+        }
+        catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            throw new IgniteInternalException("Failed to start server: " + cause.getMessage(), cause);
+        }
+        catch (InterruptedException e) {
+            throw new IgniteInternalException(
+                "Got interrupted while waiting for server to start: " + e.getMessage(),
+                e
+            );
+        }
+    }
+
+    /**
+     * @return Server local address.
+     */
+    public InetSocketAddress getLocalAddress() {
+        return server.address();
+    }
+
+    /**
+     * Get a {@link NettySender}, that sends data from this node to another node with the specified address.
+     * @param address Another node's address.
+     * @return Sender.
+     */
+    public CompletableFuture<NettySender> channel(InetSocketAddress address) {
+        NettySender channel = channels.get(address);
+
+        if (channel == null) {

Review comment:
       isn't this a race? Should we use a lock?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623081270



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(String host, int port, MessageSerializationRegistry serializationRegistry, BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+        this.messageListener = listener;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start() {
+        bootstrap.group(workerGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */
+            .option(ChannelOption.SO_KEEPALIVE, true)
+            .handler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        new InboundDecoder(serializationRegistry),
+                        new MessageHandler(messageListener),
+                        new ChunkedWriteHandler()
+                    );
+                }
+        });
+
+        ChannelFuture connectFuture = bootstrap.connect(host, port);
+
+        connectFuture.addListener(connect -> {
+            this.channel = connectFuture.channel();
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else {
+                Throwable cause = connect.cause();
+                clientFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loop group when channel is closed.
+            channel.closeFuture().addListener(close -> {
+               workerGroup.shutdownGracefully();
+            });
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();

Review comment:
       why did you resolve this message&

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Socket channel bootstrapper. */
+    private final Bootstrap bootstrap = new Bootstrap();
+
+    /** Socket channel handler event loop group. */
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Incoming message listener. */
+    private final BiConsumer<InetSocketAddress, NetworkMessage> messageListener;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client socket channel. */
+    private Channel channel;
+
+    public NettyClient(String host, int port, MessageSerializationRegistry serializationRegistry, BiConsumer<InetSocketAddress, NetworkMessage> listener) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+        this.messageListener = listener;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start() {
+        bootstrap.group(workerGroup)
+            .channel(NioSocketChannel.class)
+            /** See {@link NettyServer#start} for netty configuration details. */
+            .option(ChannelOption.SO_KEEPALIVE, true)
+            .handler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch)
+                    throws Exception {
+                    ch.pipeline().addLast(
+                        new InboundDecoder(serializationRegistry),
+                        new MessageHandler(messageListener),
+                        new ChunkedWriteHandler()
+                    );
+                }
+        });
+
+        ChannelFuture connectFuture = bootstrap.connect(host, port);
+
+        connectFuture.addListener(connect -> {
+            this.channel = connectFuture.channel();
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else {
+                Throwable cause = connect.cause();
+                clientFuture.completeExceptionally(cause);
+            }
+
+            // Shutdown event loop group when channel is closed.
+            channel.closeFuture().addListener(close -> {
+               workerGroup.shutdownGracefully();
+            });
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();

Review comment:
       why did you resolve this message?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622141526



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettySender.java
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.stream.ChunkedInput;
+import java.nio.ByteBuffer;
+import org.apache.ignite.network.internal.direct.DirectMessageWriter;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.MessageSerializer;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for a Netty {@link Channel}, that uses {@link ChunkedInput} and {@link DirectMessageWriter} to send data.
+ */
+public class NettySender {
+    /** Netty channel. */
+    private final Channel channel;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param channel Netty channel.
+     * @param registry Serialization registry.
+     */
+    public NettySender(Channel channel, MessageSerializationRegistry registry) {
+        this.channel = channel;
+        serializationRegistry = registry;
+    }
+
+    /**
+     * Send message.
+     *
+     * @param msg Network message.
+     */
+    public void send(NetworkMessage msg) {
+        MessageSerializer<NetworkMessage> serializer = serializationRegistry.createSerializer(msg.directType());
+        channel.writeAndFlush(new NetworkMessageChunkedInput(msg, serializer));

Review comment:
       I believe none, as we don't know the size of the message




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623741508



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITScaleCubeNetworkMessagingTest.java
##########
@@ -179,11 +183,14 @@ private static void stopForcefully(ClusterService cluster) throws Exception {
      * Wrapper for cluster.
      */
     private static final class Cluster {
-        /** */
-        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry();
         /** */
         private static final ClusterServiceFactory NETWORK_FACTORY = new ScaleCubeClusterServiceFactory();
 
+        /** */
+        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry()

Review comment:
       Ok then




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623136546



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/InboundDecoder.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.MessageReader;
+import org.apache.ignite.network.internal.direct.DirectMessageReader;
+import org.apache.ignite.network.message.MessageDeserializer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Decodes {@link ByteBuf}s into {@link NetworkMessage}s.
+ */
+public class InboundDecoder extends ByteToMessageDecoder {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(InboundDecoder.class);
+
+    /** Message reader channel attribute key. */
+    private static final AttributeKey<MessageReader> READER_KEY = AttributeKey.valueOf("READER");
+
+    /** Message deserializer channel attribute key. */
+    private static final AttributeKey<MessageDeserializer<NetworkMessage>> DESERIALIZER_KEY = AttributeKey.valueOf("DESERIALIZER");
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationRegistry Serialization registry.
+     */
+    public InboundDecoder(MessageSerializationRegistry serializationRegistry) {
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        ByteBuffer buffer = in.nioBuffer();
+
+        Attribute<MessageReader> readerAttr = ctx.attr(READER_KEY);
+        MessageReader reader = readerAttr.get();
+
+        if (reader == null) {
+            reader = new DirectMessageReader(serializationRegistry, ConnectionManager.DIRECT_PROTOCOL_VERSION);
+            readerAttr.set(reader);
+        }
+
+        Attribute<MessageDeserializer<NetworkMessage>> messageAttr = ctx.attr(DESERIALIZER_KEY);
+
+        while (buffer.hasRemaining()) {
+            MessageDeserializer<NetworkMessage> msg = messageAttr.get();
+
+            try {
+                // Read message type.
+                if (msg == null && buffer.remaining() >= NetworkMessage.DIRECT_TYPE_SIZE) {
+                    byte b0 = buffer.get();

Review comment:
       Cool!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622143818



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())

Review comment:
       Sure, what to?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628199603



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/TestMessageSerializationFactory.java
##########
@@ -84,7 +84,7 @@ public TestMessage getMessage() {
     @Override public MessageSerializer<TestMessage> createSerializer() {
         return (message, writer) -> {
             if (!writer.isHeaderWritten()) {
-                if (!writer.writeHeader(message.directType(), (byte) 1))
+                if (!writer.writeHeader(message.directType(), (byte) 2))

Review comment:
       :D It was at the beginning of this pull request! We decided that it's not needed as these factories will be generated




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623140912



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/message/ScaleCubeMessage.java
##########
@@ -0,0 +1,62 @@
+/*
+ * 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.network.scalecube.message;
+
+import io.scalecube.cluster.transport.api.Message;
+import java.util.Map;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Wrapper for ScaleCube's {@link Message}.
+ * {@link Message#data} is stored in {@link #array} and {@link Message#headers} are stored in {@link #headers}.
+ */
+public class ScaleCubeMessage implements NetworkMessage {
+    /** Direct type. */
+    public static final short TYPE = 100;

Review comment:
       Why not 1? I suppose we will have some order in these constants




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623828926



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       This variable is used in a lambda below




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r630821093



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,254 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.SocketAddress;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link NioServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap;
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<SocketAddress, NetworkMessage> messageListener;
+
+    /** Server start future. */
+    private CompletableFuture<Void> serverStartFuture;
+
+    /** Server socket channel. */
+    private volatile ServerChannel channel;
+
+    /** Server close future. */
+    private CompletableFuture<Void> serverCloseFuture = CompletableFuture.allOf(
+        NettyUtils.toCompletableFuture(bossGroup.terminationFuture()),
+        NettyUtils.toCompletableFuture(workerGroup.terminationFuture())
+    );
+
+    /** New connections listener. */
+    private final Consumer<NettySender> newConnectionListener;
+
+    /** Flag indicating if {@link #stop()} has been called. */
+    private boolean stopped = false;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this(new ServerBootstrap(), port, newConnectionListener, messageListener, serializationRegistry);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param bootstrap Server bootstrap.
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        ServerBootstrap bootstrap,
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.bootstrap = bootstrap;
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return Future that resolves when the server is successfully started.
+     */
+    public CompletableFuture<Void> start() {
+        if (serverStartFuture != null)
+            throw new IgniteInternalException("Attempted to start an already started server");
+
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch) {
+                    ch.pipeline().addLast(
+                        /*
+                         * Decoder that uses org.apache.ignite.network.internal.MessageReader
+                         * to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        // Handles decoded NetworkMessages.
+                        new MessageHandler(messageListener),
+                        /*
+                         * Encoder that uses org.apache.ignite.network.internal.MessageWriter
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(new NettySender(ch, serializationRegistry));
+                }
+            })
+            /*
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /*
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        serverStartFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.bind(port))
+            .whenComplete((channel, err) -> {
+                synchronized (this) {
+                    // Shutdown event loops if the server has failed to start of has been stopped.

Review comment:
       ```suggestion
                       // Shutdown event loops if the server has failed to start or has been stopped.
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,254 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.SocketAddress;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link NioServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap;
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<SocketAddress, NetworkMessage> messageListener;
+
+    /** Server start future. */
+    private CompletableFuture<Void> serverStartFuture;
+
+    /** Server socket channel. */
+    private volatile ServerChannel channel;

Review comment:
       ```suggestion
       @Nullable
       private volatile ServerChannel channel;
   ```

##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,254 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.SocketAddress;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link NioServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap;
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<SocketAddress, NetworkMessage> messageListener;
+
+    /** Server start future. */
+    private CompletableFuture<Void> serverStartFuture;
+
+    /** Server socket channel. */
+    private volatile ServerChannel channel;
+
+    /** Server close future. */
+    private CompletableFuture<Void> serverCloseFuture = CompletableFuture.allOf(
+        NettyUtils.toCompletableFuture(bossGroup.terminationFuture()),
+        NettyUtils.toCompletableFuture(workerGroup.terminationFuture())
+    );
+
+    /** New connections listener. */
+    private final Consumer<NettySender> newConnectionListener;
+
+    /** Flag indicating if {@link #stop()} has been called. */
+    private boolean stopped = false;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this(new ServerBootstrap(), port, newConnectionListener, messageListener, serializationRegistry);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param bootstrap Server bootstrap.
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        ServerBootstrap bootstrap,
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.bootstrap = bootstrap;
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return Future that resolves when the server is successfully started.
+     */
+    public CompletableFuture<Void> start() {
+        if (serverStartFuture != null)
+            throw new IgniteInternalException("Attempted to start an already started server");
+
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch) {
+                    ch.pipeline().addLast(
+                        /*
+                         * Decoder that uses org.apache.ignite.network.internal.MessageReader
+                         * to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        // Handles decoded NetworkMessages.
+                        new MessageHandler(messageListener),
+                        /*
+                         * Encoder that uses org.apache.ignite.network.internal.MessageWriter
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(new NettySender(ch, serializationRegistry));
+                }
+            })
+            /*
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /*
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        serverStartFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.bind(port))
+            .whenComplete((channel, err) -> {
+                synchronized (this) {
+                    // Shutdown event loops if the server has failed to start of has been stopped.
+                    if (err != null || stopped) {
+                        this.channel = null;
+
+                        shutdownEventLoopGroups();
+
+                        serverCloseFuture.whenComplete((unused, throwable) -> {
+                            Throwable stopErr = err != null ? err : new CancellationException();

Review comment:
       1. Why `CancellationException`?
   2. Add a message to the exception that the server has been stopped.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623863593



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyClient.java
##########
@@ -0,0 +1,141 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Netty client channel wrapper.
+ */
+public class NettyClient {
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Destination host. */
+    private final String host;
+
+    /** Destination port. */
+    private final int port;
+
+    /** Future that resolves when client channel is opened. */
+    private final CompletableFuture<NettySender> clientFuture = new CompletableFuture<>();
+
+    /** Client channel. */
+    private SocketChannel channel;
+
+    public NettyClient(
+        String host,
+        int port,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.host = host;
+        this.port = port;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Start client.
+     *
+     * @return Future that resolves when client channel is opened.
+     */
+    public CompletableFuture<NettySender> start(Bootstrap bootstrap) {
+        bootstrap.connect(host, port).addListener((ChannelFutureListener) connect -> {
+            this.channel = (SocketChannel) connect.channel();
+
+            if (connect.isSuccess())
+                clientFuture.complete(new NettySender(channel, serializationRegistry));
+            else
+                clientFuture.completeExceptionally(connect.cause());
+        });
+
+        return clientFuture;
+    }
+
+    /**
+     * @return Client start future.
+     */
+    public CompletableFuture<NettySender> sender() {
+        return clientFuture;
+    }
+
+    /**
+     * Stop client.
+     */
+    public void stop() {
+        this.channel.close().awaitUninterruptibly();
+    }
+
+    /**
+     * @return {@code true} if client failed to connect to remote server, {@code false} otherwise.
+     */
+    public boolean failedToConnect() {
+        return clientFuture.isCompletedExceptionally();
+    }
+
+    /**
+     * @return {@code true} if client lost connection, {@code false} otherwise.
+     */
+    public boolean isDisconnected() {
+        return channel != null && !channel.isOpen();
+    }
+
+    /**
+     * Creates {@link Bootstrap} for clients, providing channel handlers and options.
+     *
+     * @param eventLoopGroup Event loop group for channel handling.
+     * @param serializationRegistry Serialization registry.
+     * @param messageListener Message listener.
+     * @return Bootstrap for clients.
+     */
+    public static Bootstrap createBootstrap(

Review comment:
       I'm not sure if the `ConnectionManager` should configure client's implementation details




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619055192



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());

Review comment:
       True




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628106752



##########
File path: modules/network/src/test/java/org/apache/ignite/network/internal/AllTypesMessageGenerator.java
##########
@@ -0,0 +1,256 @@
+/*
+ * 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.network.internal;
+
+import java.lang.reflect.Field;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.lang.IgniteUuidGenerator;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+
+/**
+ * Generator for an {@link AllTypesMessage}.
+ */
+public class AllTypesMessageGenerator {
+    /**
+     * Generate a new {@link AllTypesMessage}.
+     *
+     * @param seed Random seed.
+     * @param nestedMsg {@code true} if nested messages should be generated, {@code false} otherwise.
+     * @return Message.
+     * @throws Exception If failed.
+     */
+    public static AllTypesMessage generate(long seed, boolean nestedMsg) {
+        try {
+            var random = new Random(seed);
+
+            var message = new AllTypesMessage();
+
+            Field[] fields = AllTypesMessage.class.getDeclaredFields();
+
+            for (Field field : fields) {
+                field.setAccessible(true);
+
+                TestFieldType annotation = field.getAnnotation(TestFieldType.class);
+
+                if (annotation != null) {
+                    field.set(message, randomValue(random, annotation.value(), nestedMsg));
+                }
+            }
+
+            if (nestedMsg) {
+                Field objectArrayField = AllTypesMessage.class.getDeclaredField("v");
+                objectArrayField.setAccessible(true);
+
+                Field collectionField = AllTypesMessage.class.getDeclaredField("w");
+                collectionField.setAccessible(true);
+
+                Field mapField = AllTypesMessage.class.getDeclaredField("x");
+                mapField.setAccessible(true);
+
+                Object[] array = IntStream.range(0, 10).mapToObj(unused -> generate(seed, false)).toArray();
+
+                objectArrayField.set(message, array);

Review comment:
       I think you got a little bit too excited using reflection here: `message.v = array;`. Same applies to all other fields




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623829876



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+    }
+
+    /**
+     * Create an unresolved {@link InetSocketAddress} with "localhost" as a host.
+     *
+     * @param port Port.
+     * @return Address.
+     */
+    private InetSocketAddress address(int port) {
+        return InetSocketAddress.createUnresolved("localhost", port);

Review comment:
       Do we need hostname resolving though?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622074129



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.

Review comment:
       ok =(




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623757668



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())
+            return Address.create(Address.getLocalIpAddress().getHostAddress(), port);
+        else
+            return Address.create(address.getHostAddress(), port);
+    }
+
+    /**
+     * Cleanup resources on stop.
+     *
+     * @return A mono, that resolves when the stop operation is finished.
+     */
+    private Mono<Void> doStop() {
+        return Mono.defer(() -> {
+            LOG.info("[{0}][doStop] Stopping", address);
+
+            // Complete incoming messages observable
+            sink.complete();
+
+            LOG.info("[{0}][doStop] Stopped", address);
+            return Mono.empty();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public Address address() {
+        return address;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Transport> start() {
+        return Mono.just(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> stop() {
+        return doStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isStopped() {
+        return onStop.isDisposed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Mono<Void> send(Address address, Message message) {
+        var addr = InetSocketAddress.createUnresolved(address.host(), address.port());
+        return Mono.defer(() -> Mono.fromFuture(connectionManager.channel(addr)))
+            .flatMap(client -> {
+                client.send(fromMessage(message));
+                return Mono.empty();
+            });
+    }
+
+    /**
+     * Handles new network messages from {@link #connectionManager}.
+     *
+     * @param source Message source.
+     * @param msg Network message.
+     */
+    private void onMessage(InetSocketAddress source, NetworkMessage msg) {
+        Message message = fromNetworkMessage(msg);
+
+        if (message != null)
+            sink.next(message);
+    }
+
+    /**
+     * Wrap ScaleCube {@link Message} with {@link NetworkMessage}.
+     *
+     * @param message ScaleCube message.
+     * @return Netowork message that wraps ScaleCube message.
+     * @throws IgniteInternalException If failed to write message to ObjectOutputStream.
+     */
+    private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
+        Object dataObj = message.data();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try(ObjectOutputStream oos = new ObjectOutputStream(stream)) {
+            oos.writeObject(dataObj);
+        }
+        catch (IOException e) {
+            throw new IgniteInternalException(e);
+        }
+
+        return new ScaleCubeMessage(stream.toByteArray(), message.headers());
+    }
+
+    /**
+     * Unwrap ScaleCube {@link Message} from {@link NetworkMessage}.
+     *
+     * @param networkMessage Network message.
+     * @return ScaleCube message.
+     * @throws IgniteInternalException If failed to read ScaleCube message byte array.
+     */
+    private Message fromNetworkMessage(NetworkMessage networkMessage) throws IgniteInternalException {
+        if (networkMessage instanceof ScaleCubeMessage) {
+            ScaleCubeMessage msg = (ScaleCubeMessage) networkMessage;
+
+            Map<String, String> headers = msg.getHeaders();
+
+            Object obj;
+
+            try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(msg.getArray()))) {
+                obj = ois.readObject();
+            }
+            catch (Exception e) {
+                throw new IgniteInternalException(e);

Review comment:
       Weeeell, it shouldn't happen in the first place, because all ScaleCube messages are serializable. I don't really know what we can do here. It's a failfast thingy, should fail our tests




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623855097



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;

Review comment:
       Why not? Without it would look even more cryptic




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628201606



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeClusterServiceFactory.java
##########
@@ -39,7 +41,10 @@
     @Override public ClusterService createClusterService(ClusterLocalConfiguration context) {
         var topologyService = new ScaleCubeTopologyService();
         var messagingService = new ScaleCubeMessagingService(topologyService);
-        var transportFactory = new DelegatingTransportFactory(messagingService);
+        MessageSerializationRegistry registry = context.getSerializationRegistry();
+        ConnectionManager connectionManager = new ConnectionManager(context.getPort(), registry);

Review comment:
       Right




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r621072055



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.

Review comment:
       Absolutely correct, even a comma after "connections" and before "both" is entirely optional (and I hate it for that)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r623139823



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,243 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final IgniteLogger LOG = IgniteLogger.forClass(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOG.warn("[{0}][doStop] Exception occurred: {1}", address, ex.toString())

Review comment:
       So it won't look foreign in our logs. On the other hand, we don't have any rules about logs right now




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628165523



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/ConnectionManagerTest.java
##########
@@ -0,0 +1,156 @@
+/*
+ * 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.network;
+
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.internal.netty.NettySender;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link ConnectionManager}.
+ */
+public class ConnectionManagerTest {
+    /** Started connection managers. */
+    private final List<ConnectionManager> startedManagers = new ArrayList<>();
+
+    /** */
+    @AfterEach
+    void tearDown() {
+        startedManagers.forEach(ConnectionManager::stop);
+
+        startedManagers.clear();
+    }
+
+    /**
+     * Tests that a message is sent successfuly using ConnectionManager.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSentSuccessfully() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        sender.send(testMessage).join();
+
+        latch.await(3, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Tests that after a channel was closed, a new channel is opened upon a request.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCanReconnectAfterFail() throws Exception {
+        String msgText = "test";
+
+        var latch = new CountDownLatch(1);
+
+        int port1 = 4000;
+        int port2 = 4001;
+
+        var manager1 = startManager(port1);
+        var manager2 = startManager(port2);
+
+        NettySender sender = manager1.channel(address(port2)).get();
+
+        TestMessage testMessage = new TestMessage(msgText, new HashMap<>());
+
+        manager2.stop();
+
+        final var finalSender = sender;
+
+        assertThrows(ClosedChannelException.class, () -> {
+            try {
+                finalSender.send(testMessage).join();
+            }
+            catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+
+        manager2 = startManager(port2);
+
+        manager2.addListener((address, message) -> {
+            if (message instanceof TestMessage && msgText.equals(((TestMessage) message).msg()))
+                latch.countDown();
+        });
+
+        sender = manager1.channel(address(port2)).get();
+
+        sender.send(testMessage).join();
+    }
+
+    /**
+     * Create an unresolved {@link InetSocketAddress} with "localhost" as a host.
+     *
+     * @param port Port.
+     * @return Address.
+     */
+    private InetSocketAddress address(int port) {
+        return InetSocketAddress.createUnresolved("localhost", port);

Review comment:
       Let's fix it, yes




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r622137932



##########
File path: modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ITScaleCubeNetworkMessagingTest.java
##########
@@ -179,11 +183,14 @@ private static void stopForcefully(ClusterService cluster) throws Exception {
      * Wrapper for cluster.
      */
     private static final class Cluster {
-        /** */
-        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry();
         /** */
         private static final ClusterServiceFactory NETWORK_FACTORY = new ScaleCubeClusterServiceFactory();
 
+        /** */
+        private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry()

Review comment:
       Why tho? There is no harm




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r628774051



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/NettyServer.java
##########
@@ -0,0 +1,234 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import java.net.SocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * Netty server channel wrapper.
+ */
+public class NettyServer {
+    /** {@link NioServerSocketChannel} bootstrapper. */
+    private final ServerBootstrap bootstrap;
+
+    /** Socket accepter event loop group. */
+    private final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
+
+    /** Socket handler event loop group. */
+    private final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+    /** Server port. */
+    private final int port;
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Incoming message listener. */
+    private final BiConsumer<SocketAddress, NetworkMessage> messageListener;
+
+    /** Server start future. */
+    private CompletableFuture<Void> serverStartFuture;
+
+    /** Server socket channel. */
+    private volatile ServerChannel channel;
+
+    /** Server close future. */
+    private CompletableFuture<Void> serverCloseFuture;
+
+    /** New connections listener. */
+    private final Consumer<NettySender> newConnectionListener;
+
+    /**
+     * Constructor.
+     *
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this(new ServerBootstrap(), port, newConnectionListener, messageListener, serializationRegistry);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param bootstrap Server bootstrap.
+     * @param port Server port.
+     * @param newConnectionListener New connections listener.
+     * @param messageListener Message listener.
+     * @param serializationRegistry Serialization registry.
+     */
+    public NettyServer(
+        ServerBootstrap bootstrap,
+        int port,
+        Consumer<NettySender> newConnectionListener,
+        BiConsumer<SocketAddress, NetworkMessage> messageListener,
+        MessageSerializationRegistry serializationRegistry
+    ) {
+        this.bootstrap = bootstrap;
+        this.port = port;
+        this.newConnectionListener = newConnectionListener;
+        this.messageListener = messageListener;
+        this.serializationRegistry = serializationRegistry;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return Future that resolves when the server is successfully started.
+     */
+    public CompletableFuture<Void> start() {
+        if (serverStartFuture != null)
+            throw new IgniteInternalException("Attempted to start an already started server");
+
+        bootstrap.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .childHandler(new ChannelInitializer<SocketChannel>() {
+                /** {@inheritDoc} */
+                @Override public void initChannel(SocketChannel ch) {
+                    ch.pipeline().addLast(
+                        /*
+                         * Decoder that uses org.apache.ignite.network.internal.MessageReader
+                         * to read chunked data.
+                         */
+                        new InboundDecoder(serializationRegistry),
+                        // Handles decoded NetworkMessages.
+                        new MessageHandler(messageListener),
+                        /*
+                         * Encoder that uses org.apache.ignite.network.internal.MessageWriter
+                         * to write chunked data.
+                         */
+                        new ChunkedWriteHandler()
+                    );
+
+                    newConnectionListener.accept(new NettySender(ch, serializationRegistry));
+                }
+            })
+            /*
+             * The maximum queue length for incoming connection indications (a request to connect) is set
+             * to the backlog parameter. If a connection indication arrives when the queue is full,
+             * the connection is refused.
+             */
+            .option(ChannelOption.SO_BACKLOG, 128)
+            /*
+             * When the keepalive option is set for a TCP socket and no data has been exchanged across the socket
+             * in either direction for 2 hours (NOTE: the actual value is implementation dependent),
+             * TCP automatically sends a keepalive probe to the peer.
+             */
+            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+        serverStartFuture = new CompletableFuture<>();
+
+        NettyUtils.toChannelCompletableFuture(bootstrap.bind(port)).whenComplete((ch, throwable) -> {

Review comment:
       I have can see multiple problems with this code:
   1. It's quite complicated. Some complications come from the fact that you suddenly start to care about possible exceptions inside `shutdownGracefully` inside the error path, while you still don't care about them in the normal path (see the `Shutdown event loops on server stop` comment).
   2. `serverCloseFuture` is not initialized if `bind` throws an exception.
   
   Here's my attempt on improving this code:
   
   ```
   serverCloseFuture = CompletableFuture.allOf(
       NettyUtils.toCompletableFuture(bossGroup.terminationFuture()),
       NettyUtils.toCompletableFuture(workerGroup.terminationFuture())
   );
   
   serverStartFuture = NettyUtils.toChannelCompletableFuture(bootstrap.bind(port))
       .thenAccept(ch -> {
           CompletableFuture<Void> channelCloseFuture = NettyUtils.toCompletableFuture(ch.closeFuture())
               // Shutdown event loops on server stop.
               .whenComplete((v, err) -> shutdownEventLoopGroups());
   
           serverCloseFuture = CompletableFuture.allOf(serverCloseFuture, channelCloseFuture);
   
           channel = (ServerChannel) ch;
       })
       .whenComplete((v, err) -> {
           if (err != null)
               shutdownEventLoopGroups();
       });
   
   return serverStartFuture;
   ```
   
   What do you think?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619705669



##########
File path: modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.network.scalecube;
+
+import io.scalecube.cluster.transport.api.Message;
+import io.scalecube.cluster.transport.api.Transport;
+import io.scalecube.net.Address;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.internal.netty.ConnectionManager;
+import org.apache.ignite.network.message.NetworkMessage;
+import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Disposables;
+import reactor.core.publisher.DirectProcessor;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoProcessor;
+
+/**
+ * ScaleCube transport over {@link ConnectionManager}.
+ */
+public class ScaleCubeDirectMarshallerTransport implements Transport {
+    /** Logger. */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Transport.class);
+
+    /** Message subject. */
+    private final DirectProcessor<Message> subject = DirectProcessor.create();
+
+    /** Message sink. */
+    private final FluxSink<Message> sink = subject.sink();
+
+    /** Close handler */
+    private final MonoProcessor<Void> stop = MonoProcessor.create();
+
+    /** On stop. */
+    private final MonoProcessor<Void> onStop = MonoProcessor.create();
+
+    /** Connection manager. */
+    private final ConnectionManager connectionManager;
+
+    /** Node address. */
+    private final Address address;
+
+    /**
+     * Constructor.
+     *
+     * @param connectionManager Connection manager.
+     */
+    public ScaleCubeDirectMarshallerTransport(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+        this.connectionManager.addListener(this::onMessage);
+        this.address = prepareAddress(connectionManager.getLocalAddress());
+        // Setup cleanup
+        stop.then(doStop())
+            .doFinally(s -> onStop.onComplete())
+            .subscribe(
+                null,
+                ex -> LOGGER.warn("[{}][doStop] Exception occurred: {}", address, ex.toString())
+            );
+    }
+
+    /**
+     * Convert {@link InetSocketAddress} to {@link Address}.
+     *
+     * @param addr Address.
+     * @return ScaleCube address.
+     */
+    private static Address prepareAddress(InetSocketAddress addr) {
+        InetAddress address = addr.getAddress();
+
+        int port = addr.getPort();
+
+        if (address.isAnyLocalAddress())

Review comment:
       I'm not sure if it's suitable for IPv6 addresses.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] sashapolo commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
sashapolo commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619183314



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.
+ */
+public class ConnectionManager {
+    /** Server. */
+    private NettyServer server;
+
+    /** Senders that wrap channels both incoming and outgoing. */
+    private Map<InetSocketAddress, NettySender> channels = new ConcurrentHashMap<>();
+
+    /** Clients. */
+    private Map<InetSocketAddress, NettyClient> clients = new ConcurrentHashMap<>();
+
+    /** Serialization registry. */
+    private final MessageSerializationRegistry serializationRegistry;
+
+    /** Message listeners. */
+    private List<BiConsumer<InetSocketAddress, NetworkMessage>> listeners = Collections.synchronizedList(new ArrayList<>());
+
+    public ConnectionManager(int port, MessageSerializationRegistry provider) {
+        this.serializationRegistry = provider;
+        this.server = new NettyServer(port, this::onNewIncomingChannel, this::onMessage, serializationRegistry);
+    }
+
+    /**
+     * Start server.
+     *
+     * @throws IgniteInternalException If failed to start.
+     */
+    public void start() throws IgniteInternalException {
+        try {
+            server.start().get();

Review comment:
       if you really need to catch and wrap it in `IgniteInternalException` then there's no difference, you can leave the current approach. But I thought that you can simply let it be thrown as-is...




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes commented on a change in pull request #102: IGNITE-14088 ScaleCube transport API over Netty

Posted by GitBox <gi...@apache.org>.
SammyVimes commented on a change in pull request #102:
URL: https://github.com/apache/ignite-3/pull/102#discussion_r619053262



##########
File path: modules/network/src/main/java/org/apache/ignite/network/internal/netty/ConnectionManager.java
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.network.internal.netty;
+
+import io.netty.channel.socket.SocketChannel;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.network.message.MessageSerializationRegistry;
+import org.apache.ignite.network.message.NetworkMessage;
+
+/**
+ * Class that manages connections both incoming and outgoing.

Review comment:
       These are the same sentences :)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org